I was following the Symfony tutorial and was creating a repository for my product entity by hand and not using the console to create it. Originally the annotation looked like this:
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="AppBundle\Entity\ProductRepository")
*/
but as I am learning to use the console to create the Entities and Repositories I have tried changing it so that it is in the correct place. The console placed all the console created ones in the Repository directory instead. My product entity now looks like this (changed by hand):
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
However when I issue this command
# php bin/console doctrine:generate:entities AppBundle
OR
# php bin/console doctrine:generate:entities AppBundle/Entity/Product
the repository ends up in the Entity directory, not the Repository one. I have looked up this issue and looks like Doctrine caches but I have tried the following commands to clear it with no luck:
php bin/console doctrine:cache:clear-metadata
php bin/console doctrine:cache:clear-query
php bin/console doctrine:cache:clear-result
I have even tried recreating the Entity via the console but it continues to create the file in the wrong place. Occasionally it places it in the correct place for one refresh of the browser but then complains it cant find it (since it is looking for it in the Entity directory.
Anyone help me straighten out Doctrine? Here are my classes so you can see fully:
<?php
namespace AppBundle\Entity; //<--- This is one of the issues. See answer
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
If anyone tried but didn't comment back on this, thanks to anyone who took time to look this over.
The answer was stupidity on my part. I created the project while I was logged in as root instead of the web user so the cached metadata files were not able to be overwritten. I found this out by running the following command:
# php bin/console cache:clear
and got ....
[InvalidArgumentException]
The directory "/path/to/testapp/var/cache/dev/annotations" is not writable.
(path above modified to mask real path). Secondly the name of the namespace in my class was clearly wrong. It should have been:
namespace AppBundle\Repository;
Anyhow thanks.
Related
I am developing an TYPO3 extbase extension to connect to my external application that has a REST-API. All I want is to retrieve the data from my REST-API and pass this data to the controller. Since I am quite new to extbase development, I didn't really find any resources about Repository interacting with a webservice. Only documentation about Repository that interacts with a database (MySQL, PostgreSQL, ..)
I would like to know, where should I place the cURL-Request Function to connect to the API? In the Model? In the Repository? How can the Controller in my Extension access that data? From Model or Repository?
What would be the best practice for retrieving the data from my external application/database ? (the data retrieved from the application is JSON-formatted)
thanks for any advice / help!
What we usually do is create a Service (in Classes/Service) and use that to connect to the webservice and fetch the data. If you want Models, you can create them there as well.
However, now that I think about it, technically it should be a Repository. It shouldn't matter where the Repository gets its data. Extbase shouldn't have any problem with a completely custom Repository (not extending any other class).
Lets take a simple list -> detail view and make it as simple as possible in order to get some results. Then you can add some helper classes and include them via namespaces etc.
ListAction (yourExtension/Classes/Controller/EventController)
/**
* action list
*
* #return void
*/
public function listAction()
{
$events = (some function which gets all the available events and results to a json output)
$decodedEvents = json_decode($events , true);
(in case you need to see what you got back)
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($decodedEvents);
$this->view->assign('events', $decodedEvents);
}
List HTML (yourExtension/Resources/Private/Templates/Event/List)
<f:for each="{events}" as="event">
<h2>{event.title}</h2>
</for>
Now if i am not wrong, every event has a uid or something that identifies it. So on the same template you can do the following:
<f:for each="{events}" as="event">
<h2>{event.title}</h2>
<f:link.action action="detail" arguments="{eventId: event.uid}">More</f:link.action>
</f:for>
What this does, is to generate a link which links to your detail action with an extra parameter, the eventId.
Bedore TYPO3 renders the detail page it will go through your detail action to get the information for this specific event in order to display them.
DetailAction (yourExtension/Classes/Controller/EventController)
/**
* action detail
*
* #return void
*/
public function detailAction()
{
$args = $this-request->getArguments();
$eventId = $args['eventId'];
$getEvent = (some function that gets a specific id and results to a json output)
$decodedEvent = json_decode($getEvent , true);
(in case you need to see what you got back)
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($decodedEvent );
$this->view->assign('event', $decodedEvent );
}
What this does, it to retrieve the argument that we specified in order to get the identifier of the event and include it on your api request to get the event back. Then we just just decode it to a normal array and we send the results to the FrontEnd.
Detail HTML (yourExtension/Resources/Private/Templates/Event/Detail)
<h2>{event.title}</h2>
Thats a simple list -> detail proccess on TYPO3. You will oft see that the detail view as show but is basically the same thing.
You can replace the variables with a dummy json values and play around. I tested the code while i was writing this answer and it works.
Classes/Service
As for the Classes/Service that mentioned by Rudy Gnodde, you can put your library in there (assuming that you already have coded some functions like getEvent, getAllEvents, getPersons etc.) and then call them in the controller.
use \Vendor\YourExtension\Service\RestApiClass;
/**
* action list
*
* #return void
*/
public function listAction()
{
$events = RestApiClass::getAllEvents();
$decodedEvents = json_decode($events , true);
(in case you need to see what you got back)
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($decodedEvents);
$this->view->assign('events', $decodedEvents);
}
/**
* action detail
*
* #return void
*/
public function detailAction()
{
$args = $this-request->getArguments();
$eventId = $args['eventId'];
$getEvent = RestApiClass::getEvents($eventId);
$decodedEvent = json_decode($getEvent , true);
(in case you need to see what you got back)
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($decodedEvent );
$this->view->assign('event', $decodedEvent );
}
If you need more information feel free to ask.
Best regards
I am working to create a custom WMI class for use with SCCM's device collection membership query rules to expose some organization specific information to SCCM for collection creation. I have managed to add a new class in with the MOF file below, and it shows up in the GUI as expected with the desired display name.
The only issue I believe I have left to solve is how to make the generated query work as expected. I know by default joins are not supported in WQL. However Microsoft seems to be using them regularly in these queries, and I have verified they work as expected outside of the application. Is there some sort of joining class I need to create to make this work? Is there some attribute to designate a property as a forigen key??
I have searched through the System Center Configuration Manager SDK documentation, and the WMI reference on the Windows Dev Center site for a few hours without much luck and would really appreciate any helpful input.
MOF File:
#pragma namespace ("\\\\.\\root\\sms\\site_lab")
[DisplayName("CMDB CI Computer")]
class SMS_G_System_MTS_cmdb_ci_computer : SMS_G_System
{
[key] uint32 ResourceID;
string asset_tag;
string dv_company;
string dv_cost_center;
string dv_location;
};
SCCM Generated Query:
select * from SMS_R_System inner join SMS_G_System_MTS_cmdb_ci_computer on SMS_G_System_MTS_cmdb_ci_computer.ResourceID = SMS_R_System.ResourceId
I am testing these commands with the Get-CimInstance command in powershell with the below results
Powershell:
This command returns the joined objects as expected.
Get-CimInstance -Namespace 'root\sms\site_lab' -Query 'select SMS_G_System_PROCESSOR.Architecture from SMS_R_System inner join SMS_G_System_PROCESSOR on SMS_G_System_PROCESSOR.ResourceId = SMS_R_System.ResourceId'
This command throws an "Invalid Query" Error.
Get-CimInstance -Namespace 'root\sms\site_lab' -Query 'select asset_tag from SMS_R_System inner join SMS_G_System_MTS_cmdb_ci_computer on SMS_G_System_MTS_cmdb_ci_computer.ResourceID = SMS_R_System.ResourceId'
I have a cached entity Product. I retrieve the entity and update some properties including adding price into it.
Relationship is set up like this
class Product
{
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Price", mappedBy="product", fetch="EXTRA_LAZY")
*/
private $prices;
}
class Price
{
/**
* #var Product
*
* #ORM\ManyToOne(targetEntity="Product", inversedBy="prices")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
}
I am trying to save properties like this.
// Here $em is Entity Manager
$cacheDriver = $em->getConfiguration()->getResultCacheImpl();
$product = $cacheDriver->fetch($key)
$product->setUpdatedAt(new \DateTime());
$price = new Price;
$label = $em->getReference(PriceLabelEntity::class, $labelId);
$price->setLabel($label)
->setPriceLabelId($labelId)
->setProduct($productEntity)
->setProductId($product->getId());
$em->persist($price);
$product->addPrice($price);
$em->flush();
But whenever I do that I get the exception saying.
A new entity was found through the relationship 'Price#product' that was not
configured to cascade persist operations for entity:
Product#0000000043c0cdc400007fec6b41ca76. To solve this issue: Either
explicitly call EntityManager#persist() on this unknown entity or configure
cascade persist this association in the mapping for example
#ManyToOne(..,cascade={"persist"}). If you cannot find out which entity
causes the problem implement 'Product#__toString()' to get a clue.
Even more strange issue if I do just this.
$product->setUpdatedAt($timeStamp);
$em->flush();
It does not throw any error but no data is saved in DB. Not sure whether this issues are related or not either.
I tried to put cascade too but it gives different error. Is there any way to resolve this issue.
It is my understanding that Doctrine entity managers are fairly adept (at least, one would hope) at managing their result cache, so fiddling with it directly may be the issue.
Instead of hitting the result cache directly, try using:
$product = $entityManager->find('Product',$key);
Then, you also seem to have a typo on the last line - missing an "e" in "entityManager" - but I am sure that would throw an error, so it's probably a copy/paste issue when creating this question.
EDIT: You are also using both $product and $productEntity. If these are meant to be the same variable, you should pick one name and stick with it.
#Rushing thank you very much for your reply. You are right there are some typos because I had to do some changes in code to paste in StackOverflow but the actual code is correct. I am sorry I was bit late to reply on this topic. The solution you provided was good but even better solution was to use merge like this.
$productEntity = $em->merge($productEntity);
....rest of the logic
In this way we did not have to query the database again and entity was fresh.
Something is wrong with documentation or me. I do all what documentation says.
When i put in terminal :
$ php vendor/bin/doctrine orm:schema-tool:create
Output is :
No Metadata Classes to process
I read to many posts, and google and try to many examples but nothing.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html
I think you took the config example from Doctrine2: getting started:
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration([__DIR__."/src"], $isDevMode);
The trick is now that the Setup::createAnnotationMetadataConfiguration method uses a SimpleAnnotationReader by default. You can change this behaviour by changing the fifth parameter to false:
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration([__DIR__."/src"], $isDevMode, null, null, false);
This will force Doctrine to use the not-simple AnnotationReader which can handle your models now!
TL;DR: Make sure the type of metadata you created matches the "create metadata configuration" method you used.
I encountered the same problem while working through the Doctrine "Getting Started" guide. After looking through the Doctrine code a bit I figured out what was going wrong. Basically, the code in the tutorial in the "Obtaining the EntityManager" section is:
$config = Setup::createAnnotationMetadataConfiguration([__DIR__ . "/src"], $isDevMode);
A little further in the tutorial, in the "Starting with the Product" section, it shows us how to set up the metadata, with an example for each of the possible options for this. The tutorial says:
Metadata for entities are configured using a XML, YAML or Docblock Annotations. This Getting Started Guide will show the mappings for all Mapping Drivers. References in the text will be made to the XML mapping.
Because of this statement, I decided to use the XML configuration. Unfortunately, the createAnnotationMetadataConfiguration method in the tutorial code did not seem to be using the XML file I had created. (In hindsight, it seems much more obvious that this method is specifically referring to annotation metadata, not XML metadata!)
After I changed it to createXMLMetadataConfiguration instead, it worked as expected. So it looks like one possible source of this problem is that the "create metadata configuration" method you used may not match the type of metadata you created.
Try clearing the cache:
sudo -u www-data php app/console cache:clear --env=dev
Uncomment the one of the following lines in bootstrap.php:
// or if you prefer yaml or XML
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
Which depends if you created yaml or xml meta config files...
Hope this helps.
Had the same issue with custom Doctrine installation. My solution was to re-set metadata driver:
$config->setMetadataDriverImpl(
new Doctrine\ORM\Mapping\Driver\AnnotationDriver(
new Doctrine\Common\Annotations\CachedReader(
new Doctrine\Common\Annotations\AnnotationReader(),
new Doctrine\Common\Cache\ArrayCache()
),
ENTITY_DIR
)
);
Solution from http://support.skipper18.com/1120/how-use-external-tools-generate-doctrine-getters-and-setters?show=1121#a1121
My scenario was generating entities from existing database
The newDefaultAnnotationDriver adds the namespace and the method comments state the following:
If $useSimpleAnnotationReader is true, the notation #Entity will
work, otherwise, the notation #ORM\Entity will be supported.
I had the same problem when creating a new doctrine config in a new ZF2 module.
problem was caused by
'User\Entity' => 'property_entities'
the user part was from the old entity
'Property\Entity' => 'property_entities'
Changing that fixed the issue
If you're using the XML mapping (using Setup::createXMLMetadataConfiguration()), you might want to pay attention to the following:
That your XML mapping files ends by .dcm.xml, not only by .xml.
That your XML file contains the full entity classname, inclusive of the namespace. For example, for a class Company\Solution\Models\User, you must have the Company.Solution.Models.User.dcm.xml mapping file in your XML path.
In my case, the issue was with the number of asterisk used for the annotation
<?php
namespace Models;
use Doctrine\ORM\Annotation\{Id, Column, GeneratedValue, Entity};
/** // I originally used one asterisk here and kept getting the error in question. Error disappeared after doubling the asterisk as it is in this answer
* #Entity(repositoryClass="Doctrine\ORM\Annotation\Id")
*/
class User {
}
?>
you must add the docstring for example:
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="users")
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
}
Try the following command:
php vendor/bin/doctrine-module orm:schema-tool:create
i need to create a webservices with symfony2 ive read the official article http://symfony.com/doc/current/cookbook/web_services/php_soap_extension.html
in the example it creates a instance of SoapServer with a parameter routing a .wsdl file, what is this file? i didnt found too much documentation about soap in symfony. some help with this please?
public function indexAction()
{
$server = new \SoapServer('/path/to/hello.wsdl');
$server->setObject($this->get('hello_service'));
$response = new Response();
$response->headers->set('Content-Type', 'text/xml; charset=ISO-8859-1');
ob_start();
$server->handle();
$response->setContent(ob_get_clean());
return $response;
}
I'm not sure if you've found your answer or not. Just for anybody else that might run into such a problem:
WSDL is the language in which the Web Services are defined and described. It's basically a XML file containing the input/output parameters of each and every function that are served by a server. it also contains some information about the server itself, that's providing the services.
in order to be able to create a webservice, you need to use the code you've provided, which would in fact prepare Symfony to serve the clients on "/path/to/hello.wsdl" (in my example this path is /YourDesiredPathFor/services.wsdl), and also, you need to provide a valid WSDL document that contains the information mentioned above in correct WSDL format. the problem is that Symfony (or even PHP itself for this matter) has no means of creating the file automatically.
to solve the matter, you need to use an external WSDL generator. I'd suggest using PHP-WSDL-Creator. It uses annotations written inside php files to create the WSDL file and also runs the SoapServer. that means that you won't even need the code that you've provided. it also has proper interfaces and addons that provide you with clients for different protocols and languages.
you need to tweak it a little bit though! if you want it to be by symfony standards, i think you would need to rewrite some parts of it; but if you'd like to use it as an external library, it could also work!
the way i did it was by copying the extracted files into ./vendor/php_wsdl/lib/php_wsdl/src (long it is, isn't it? maybe an easier path would also work!); then defined a php_wsdl.php in ./vender/php_wsdl/lib/php_wsdl:
<?php
require_once __DIR__. '/src/class.phpwsdl.php';
class WSDL_PhpWsdl extends PhpWsdl{
}
next, in the "./app/autoload.php", i added the following line to enable Symfony to use the created extension:
require_once __DIR__. '/../vendor/php_wsdl/lib/php_wsdl/php_wsdl.php';
just one thing! the extension needs a "cache" folder in order to cache the created wsdl files and all. unfortunately because i need to finish the project quickly, i don't have enough time to manage the cache folder as it should be. there are definitely better ways than my way, and i would really be glad to know about them.
anyway, now you need to use the extension's capabilities! to do so, i created a "ServerController" for the bundle i was using:
<?php
namespace Tara\PageControllerBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class ServiceController extends Controller
{
function wsdlAction(){
\PhpWsdlServers::$EnableRest = false;
$soap= \WSDL_PhpWsdl::CreateInstance(
null, // PhpWsdl will determine a good namespace
$this->generateUrl('service_wsdl', array(), true), // Change this to your SOAP endpoint URI (or keep it NULL and PhpWsdl will determine it)
__DIR__. '/cache', // Change this to a folder with write access
Array( // All files with WSDL definitions in comments
dirname(__FILE__). '/../Services/MyService.php'
),
null, // The name of the class that serves the webservice will be determined by PhpWsdl
null, // This demo contains all method definitions in comments
null, // This demo contains all complex types in comments
false, // Don't send WSDL right now
false // Don't start the SOAP server right now
);
if($soap->IsWsdlRequested()) // WSDL requested by the client?
$soap->Optimize=false; // Don't optimize WSDL to send it human readable to the browser
$soap->RunServer();
}
}
as you can see, the path to the cache folder is on a local directory, which means it has to be created manually and in ./src/Tara/PageControllerBundle/Controller (obviously in my case; you will need to change the paths). I'm sure there are better ways to manage the cache folder.
there is a line there:
dirname(__FILE__). '/../Services/MyService.php
this line tells the extension where to look for annotations in order to create the WSDL page. you also need to define a route to "service_wsdl":
service_wsdl:
pattern: /YourDesiredPathFor/services.wsdl
defaults: {_controller: TaraPageControllerBundle:Service:wsdl}
as you can see, the controller is ServiceController and the function responsible for it is wsdlAction; the exact function that was defined!
just as an example, i would provide my own MyService.php:
<?php
namespace Tara\PageControllerBundle\Services;
use Tara\PageControllerBundle\Model\...
/**
* #service Tara\PageControllerBundle\Services\MyService
*/
class MyService
{
/**
* Function Create
*
* #param string $link
* #param string $title
* #param string $image
* #param string $description
* #return boolean Status of the creation
* #pw_rest POST /YourDesiredPathForAction/create Create The Object
*
*/
public function Create($link, $title, $image, $description)
{
// your code that handles the data goes here. this is not a part of the annotations!
return (bool)$result;
}
}
now, you might be able to use a SoapClient to connect to your web service at
http: //your-server.something/YourDesiredPathFor/services.wsdl?wsdl
and call the Create function! you can also check the output of the extension by opening the above written address. the extension also provides a "human-readable" version at
http: //your-server.something/YourDesiredPathFor/services.wsdl.
i would be glad to know if this was any help to anyone! :)
SOAP is a more general concept that Symfony assumes you are familiar with. There is an example WSDL at the bottom of the page that you link to. Take a look at tutorials about SOAP and WSDL and then try to recreate what they're doing in the Symfony page.
SOAP Tutorial
WSDL Tutorial