Using custom ParamConverter for POST Request in Symfony 2 - php

I'm using Symfony 2.6 and the FOS Rest Bundle.
Param converters for PATCH , DELETE and GET requests work nicely and reduce the code in the controller actions. However for POST requests I have a problem. The default \Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter gets called every time. This results in an exception:
Unable to guess how to get a Doctrine instance from the request information.
I checked the \Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener and saw that it's always including the Doctrine param converter in the onKernelController method. From the documentation it seems that the doctrine param converter is automatically applied for all type hinted controller actions unless you set it to off:
sensio_framework_extra:
request:
converters: true
auto_convert: false
I found a kind of hacky way to prevent this. The array of param converters to be applied will be indexed by the name of the type hinted argument in the controller method (which symfony gets by reflection). If I just name my param converter the same as this type hint then the default doctrine param converter will not be added to the list of param converters. For example:
...
* #ParamConverter(
* "MyEntity",
* class="Foo\Bar\MyEntity",
* converter="my_custom_converter"
* )
*
* #param MyEntity $myEntity
* #return MyEntity
*/
public function postMyEntityAction(MyEntity $myEntity)
{
I sort of wrote this question as I was digging deeper into the code and I'm not even really sure what my question is anymore. I guess it's "Is it logical to apply multiple param converters?" or would also like to know if it's possible to turn off param converters for certain actions. Maybe my logic is completely wrong here and this isn't what param converters were intended for.
I'd appreciate any advice.

Alright, I realized where I was going wrong. It was a simple case of not returning true from my custom paramConverter apply method. If it does return true then the doctrine param converter won't be applied.

Related

Typo3 extension: Transfering an object via arguments doesn't work. What am i doing wrong?

Before i state my problem, please be aware that this is my first time working with Typo3 and/or creating an Extbase extension.
SO basically i want to create an extbase extension for Typo3, but i seem not to be able to wrap my head around the concept of transfering an object (assigned to the view of a specific template) via arguments to an action, with the purpose of attaching the object to another (with a 1:n relation).
My example:
I have an Objekt of the type "Appliance" assigned to the view of a template ("Show.html"). I can list all the properties of it in the Template, so it definitely exists in the view.
Now i want to create an Object of the type "Host" using a form and then attaching it to this specific "Appliance" object.
The problem is: I can't transfer the object of the type "Appliance" to the specific Action of the Controller of the type "Host" which itself should then assign it to the view of another template.
Look at the following code example:
<f:link.action action="new" controller="Host" arguments="{appliance:appliance}" >Add Host X</f:link.action>
This is the specific code line in the "Show.html" template that transfers the "Appliance" object to the Action "new" of the "Host" controller using arguments... The "Host" controller:
public function newAction(\Cjk\Icingaconfgen\Domain\Model\Appliance $appliance, \Cjk\Icingaconfgen\Domain\Model\Host $host = NULL)
{
$this->view->assign('appliance', $appliance);
$this->view->assign('host', $host);
}
At this point i get the following error message:
"Argument 1 passed to
Cjk\Icingaconfgen\Controller\HostController::newAction() must be an
instance of Cjk\Icingaconfgen\Domain\Model\Appliance, none given"
What am i doing wrong?
You need a Docblock that describes these parameters.
What may look like just comments, actually follows the PHPDoc standard. These declarations are interpreted by the TYPO3 ReflectionClass to map your Domain Model and validate parameters and object properties.
Make sure you completely flush the cache whenever you add or update one of these.
/*
* #param \Cjk\Icingaconfgen\Domain\Model\Appliance $appliance
* #param \Cjk\Icingaconfgen\Domain\Model\Host $host
* #return void
*
*/
public function newAction(\Cjk\Icingaconfgen\Domain\Model\Appliance $appliance, \Cjk\Icingaconfgen\Domain\Model\Host $host = NULL)
{
$this->view->assign('appliance', $appliance);
$this->view->assign('host', $host);
}
You need to be sure that there is an Appliance model given in your Fluid template, easily by debugging it before the link with e.g. <f:debug>{appliance}</f:debug>
If this is okay, you should add some doc comments above your newAction because Extbase is referring to that.
An example would be: (just as I am writing this, a good example was posted). :)

PHP Symfony FOSRestBundle and Dependency Injection

Assumed I have a normal Symfony entity "Car", a self written service "Log" and I want to build a rest route
PUT http://mysite.de/api/{version}/cars/{oldcar}
to do something with a car given in "oldcar" stored in my database. In the body of the PUT there comes another Car object in JSON notation with the new data. Then I have following method:
/**
* #Rest\Put("/api/{version}/cars/{oldcar}")
* #ParamConverter("oldcar", class="MyBundle\Entity\Car")
* #ParamConverter("newcar", class="MyBundle\Entity\\Car", converter="fos_rest.request_body")
*/
public function putAction(Log $logger, EntityManagerInterface $em, Car $oldcar, Car $newcar)
{
... update the oldcar in the database with data from the newcar ...
}
I got this allready working!
But now I want to know: Is there a configuration of the FosRestBundle to omit the #ParamConverter annotations? In my opinion there are enough information for the framework to do this automatically without them.
Log and EntityManagerInterface can be found in the service container. oldcar's and newcar's types are defined in the method signature. Primary key for database access for oldcar is given by the route. There is only the JSON data in the body left for newcar.
Does anybody here get this working without the #ParamConverter annotations?
Please tell me if and a short idear how.

Injecting Doctrine Entity into Symfony controller based on route parameters

I want to inject Doctrine entities into controller actions based on the route parameters in an attempt to reduce the insane amount of code duplication inside my controllers.
For example I have the following route
product:
path: /product/edit/{productId}
defaults: { _controller: ExampleBundle:Product:edit }
Instead of my current approach
public function editAction($productId)
{
$manager = $this->getDoctrine()->getManager();
$product = $manager->getRepository('ExampleBundle:Product')
->findOneByProductId($productId);
if (!$product) {
$this->addFlash('error', 'Selected product does not exist');
return $this->redirect($this->generateUrl('products'));
}
// ...
}
I'd like this to be handled else where as it's repeated in at least 6 controller actions currently. So it would be more along the lines of
public function editAction(Product $product)
{
// ...
}
It seems this has in fact been done before and the best example I can find is done by the SensioFrameworkBundle http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
I'd use this but were not using annotations in our Symfony projects so need to look at alternatives. Any suggestions on how this can be achieved?
If you read the docs carefully, you'll learn that param converters actually work without annotations:
To detect which converter is run on a parameter the following process is run:
If an explicit converter choice was made with #ParamConverter(converter="name") the converter with the given name is chosen.
Otherwise all registered parameter converters are iterated by priority. The supports() method is invoked to check if a param converter can convert the request into the required parameter. If it returns true the param converter is invoked.
In other words if you don't specify a param converter in an annotation, Symfony will iterate through all registered converters and find the most appropriate one to handle your argument (based on a type hint).
I prefer to put an annotation in order to:
be explicit
save some processing time

Save entities to a REST API instead of DB using Doctrine 2

This is related to my other question: Persisting entities using a REST API.
For a project in Symfony2 I need to be able to persist entities using an remote (third-party) RESTful API. I also want to be able to retrieve entities with data from that API.
In other words, my objects are saved in the third-party database. They are not saved in my own database. Whenever I need to save data, or find data, I use their REST API.
I have been pointed to several libraries, including one made by Doctrine itself. However, none of them offers me what I'm looking for. The one made by Doctrine is the best option, but uses the Active Record pattern and doesn't offer all the sweet Doctrine 2 stuff. Don't get me wrong, I've been using Active Record implementations for a long time, but I've fallen in love with Doctrine's Data Mapper pattern now.
Ideally, I'd like to be able to use Doctrine's ORM and simply replace the database-specific part with logic that saves entities using an API call. (and of course retrieves them using that same API). This way I can save my entities using roughly the same syntax:
// current way to save $entity in database:
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
// desired way to save $entity using REST API:
// (just an example, it doesn't have to be exactly like this)
$em = $this->getDoctrine()->getManager('rest');
$em->persist($entity);
$em->flush();
Note that I'm not trying to build my own API, I'm simply trying to communicate with a third party API in order to save my entities. I'm relatively new to Doctrine, but I'm liking it so far. I really like the idea of seperating the persistence logic from the entities, but so far I can't find out how I can use that to save them using an API.
There is an article in Symfony's documentation, describing how to work with multiple Entity Managers. I'm looking for a solution similar to this, but with an entity manager that enables me to use REST instead of the DB.
I've been trying to tweak Doctrine's ORM myself, but I only end up rewriting half their code because it (seems to be) too tightly coupled to the Database-specific logic. I might be doing something stupid of course.
So my question is, is there a way to replace / override the database-specific parts of Doctrine's ORM with custom ones? Without rewriting a lot of things that should be common for all persistence methods? Has it been done before? Or is it simply not possible because Doctrine is intended for use with a database and isn't flexible enough for other uses?
My own progress
CakePHP seems to be able to do this, by letting you define a custom DataSource. This way you can save your models using an SQL database, but also using an API, sessions, etc. I want to do roughly the same, but using Doctrine instead of CakePHP.
Update 1
The actual database queries seem to be executed by the
Doctrine\ORM\Persisters\BasicEntityPersister class. There are several other xxxPersister classes, to deal with different types of inheritance. It might be possible to replace the xxxPersister classes with our own, so we can replace the DB code with REST API code.
The persister objects are created within the getEntityPersister() method of the Doctrine\ORM\UnitOfWork class. The classnames are hardcoded so we need to override Doctrine\ORM\UnitOfWork if we want to use our own persisters.
Update 2
Doctrine\ORM\UnitOfWork seems to be hardcoded into Doctrine\ORM\EntityManager, so we need to override that one as well. However, this class seems to contain some database-specific parts. For instance, it's constructor requires a Doctrine\DBAL\Connection object as parameter. Perhaps it's better to create our own EntityManger (implementing the Doctrine\Common\Persistence\ObjectManager interface), as long as that doesn't take too much time / effort.
Update 3
The database-specific code for retrieving/loading/finding objects lives in the same class as the code for persisting / deleting etc: the Doctrine\ORM\Persisters\xxxPersister classes. So if we are able to replace them with our own, in order to persist objects, we can retrieve objects as well. When you call $entityRepository->findAll(), for instance, it will return $entityRepository->findBy(array()) (because findAll() is simply an alias for findBy(array())) which will run the following code:
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->loadAll($criteria, $orderBy, $limit, $offset);
In other words, once we get EntityManager to create the right UnitOfWork and xxxPersister objects, we will be able to use the find methods in the EntityRepository.
Update 4
I discovered that a new feature is developed for Doctrine: custom persisters (also see this). This should make it easier to use a custom persister class. I don't know yet if it will enable us to create a non-DB persister, but it looks promising. However, the last updates were in August, so I'm not sure if it's still in active development.
You might use https://github.com/doctrine/rest to build a REST client, which talks to the target server. The essential part here is the mapping from entity (local) to REST API (target).
In short: Doctrine2 (local DB) -> Rest client (entity to rest mapping) -> Request (target server)
Doctrine/Rest provides also the other way around: a Doctrine Rest Server, to expose your local entities via REST (requests to your server). But thats not what you are looking for.
DoctrineRestDriver is exactly doing what you are looking for.
https://github.com/CircleOfNice/DoctrineRestDriver
Configure Doctrine:
doctrine:
dbal:
driver_class: "Circle\\DoctrineRestDriver\\Driver"
host: "http://www.your-url.com/api"
port: 80
user: "Circle"
password: "CantRenember"
Build entity:
/**
* This annotation marks the class as managed entity:
*
* #ORM\Entity
*
* You can either only use a resource name or the whole url of
* the resource to define your target. In the first case the target
* url will consist of the host, configured in your options and the
* given name. In the second one your argument is used as it is.
* Important: The resource name must begin with its protocol.
*
* #ORM\Table("products|http://www.yourSite.com/api/products")
*/
class Product {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
public function getId() {
return $this->id;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
}
Let's assume we have used the value http://www.yourSite.com/api/products for the product entity's #Table annotation.
Controller:
<?php
namespace CircleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\HttpFoundation\Response;
class UserController extends Controller {
/**
* Sends the following request to the API:
* POST http://www.yourSite.com/api/products HTTP/1.1
* {"name": "Circle"}
*
* Let's assume the API responded with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* Response body is "1"
*/
public function createAction() {
$em = $this->getDoctrine()->getManager();
$entity = new CircleBundle\Entity\Product();
$entity->setName('Circle');
$em->persist($entity);
$em->flush();
return new Response($entity->getId());
}
/**
* Sends the following request to the API by default:
* GET http://www.yourSite.com/api/products/1 HTTP/1.1
*
* which might respond with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* Response body is "Circle"
*/
public function readAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
return new Response($entity->getName());
}
/**
* Sends the following request to the API:
* GET http://www.yourSite.com/api/products HTTP/1.1
*
* Example response:
* HTTP/1.1 200 OK
* [{"id": 1, "name": "Circle"}]
*
* Response body is "Circle"
*/
public function readAllAction() {
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('CircleBundle\Entity\Product')->findAll();
return new Response($entities->first()->getName());
}
/**
* After sending a GET request (readAction) it sends the following
* request to the API by default:
* PUT http://www.yourSite.com/api/products/1 HTTP/1.1
* {"name": "myName"}
*
* Let's assume the API responded the GET request with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* and the PUT request with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "myName"}
*
* Then the response body is "myName"
*/
public function updateAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
$entity->setName('myName');
$em->flush();
return new Response($entity->getName());
}
/**
* After sending a GET request (readAction) it sends the following
* request to the API by default:
* DELETE http://www.yourSite.com/api/products/1 HTTP/1.1
*
* If the response is:
* HTTP/1.1 204 No Content
*
* the response body is ""
*/
public function deleteAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
$em->remove($entity);
$em->flush();
return new Response();
}
}
You can even use DQL or native queries.
As a ready-to-use solution wasn't available, I decided to write my own. I called it RAPL. It's heavily inspired by Doctrine's ORM (in fact, it uses many of the interfaces provided by Doctrine Common).
Using RAPL I can simply write a small YAML file to configure the mapping between my entities and the web service, allowing me to persist/retrieve entities using the custom EntityManager.
I think you are in not right way.
I'm not ready to dig into the documentation now, but I understand doctrine stack as:
ORM -> DQL (doctrine query language) ->dbal ->Some database sql
And point for implementation you feature in DBAL as custom database driver.
I think create common REST-Driver realy interesting feature and it will do easy integration with third-party services.
I'm not sure, but you can try to use lifecycle callback events for entities to perform persisting logic via REST.
I wanted to do a similar thing, so I built this library to help expose doctrine entities as RESTful resources. It has a fair amount of features, and allows you to define exactly what you want to have exposed via both pull (GET) and push (POST/PUT/PATCH) methods.
http://leedavis81.github.io/drest/
https://github.com/leedavis81/drest
Hope it helps

Symfony 2 URL Parameters?

I am used to Yii's URL management:
site.com/controller/action/var1/value1/var2/value2
That way I can put my variables in any order in the URL or omit one and having a default value asigned:
public function actionFoo( $var1=22, $var2 ) { //Var1 is optional, Var2 must come.
}
Is there such thing in Symfony? I've searched but I found only hard-coded URL positions, like CodeIgniter, and that's something I find very annoying, because if I have a parameter in the middle of the URL I need to necessarily give it a value.
Example:
site.com/controller/action/false/false/value3
Maybe what I am used too is a bad practice, and I am open to learn other way.
Thanks
Symfony doesn't work that way.
If you need to map a value to a variable name then you need to use query strings: ?var1=value1&var2=value2.
Then, in your controller you can do $this->get('request')->query->get('var1', false) (false being the default value if var1 isn't set).
Otherwise you can define a default value in your route but it doesn't work the way you expect it to work.
You could extend the Routing component to mimic Yii's routing system but I wouldn't advise it because it would require some time.
All-in-all query strings will do exactly what you expect.
I have also found it to be a bit annoying as well, but with a little bit of work you can make the URLs work in Symfony by setting multiple routes to the same controller method. The catch is the order does matter.
This example use annotation but you could do the same thing with YAML, XML and PHP.
/**
* #Route("/whatever/{var2}/{var1}", name="whatever_allvars")
* #Route("/whatever/{var2}", name="whatever_onevar")
* #Template()
*/
public function actionFoo( $var1=22, $var2 ) {
//Var1 is optional, Var2 must come.
}
this would allow the following URLs:
site.com/whatever/var2/var1
site.com/whatever/var2
if you really want to make it match the sample URL give you could set it up like this.
/**
* #Route("/controller/action/var1/{var1}/var2/{var2}", name="whatever_allvars")
* #Route("/controller/action/var1/{var1}", name="whatever_onevar")
* #Template()
*/
public function actionFoo( $var1, $var2=22 ) {
//Var2 is optional, Var1 must come.
}
This would allow the following URLs:
site.com/controller/action/var1/value1/var2/value2
site.com/controller/action/var1/value1
Hopefully this gives you an idea what can be done in Symfony.

Categories