I am using the #Security annotation to control which roles have access to certain routes in my Symfony 3.4 application, it works when I am logged in however when the user object doesn't exist such as when the session times out I get the following exception thrown.
Unable to get a property on a non-object.
vendor/symfony/symfony/src/Symfony/Component/ExpressionLanguage/ExpressionLanguage.php:78
at
Symfony\Component\ExpressionLanguage\ExpressionLanguage->evaluate('\'ROLE_MANAGER\'
in user.getRoles()',
My method definition looks like this:
/**
* #Route("/club/{id}/trophies", name="club_trophies", methods={"GET","POST"})
* #IsGranted("IS_AUTHENTICATED_FULLY")
* #Security("'ROLE_MANAGER' in user.getRoles()")
* #param Club $club
* #return Response
*/
public function trophies(Club $club): Response
{
Is there a way using the Symfony Expression Language, or similar, that I can check that user exists. Or is there a better way?
When you are not authenticated, the value of user is null, so it's normal that your check is throwing an exception (as you're trying to access the method getRoles() of a null object).
The proper ways to check if a user has a given role using annotations are :
#IsGranted("ROLE_MANAGER")
Or :
#Security("is_granted('ROLE_MANAGER')")
You can see more here : https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html
I have a problem with Api Platform and custom collection operation, when I need to manually require an argument in the route.
My first need is to GET on this route: query/userjob/[USER UUID] and retrieve a collection of all jobs for the given user.
My second need is to be able to GET on query/userjob/[USER UUID]/[JOB UUID] and retrieve details for the given user's job.
It might be important to say that I have no Api resource nor entity User, so I exclude all kind of subresource mapping or query.
So, let's say i have a UserJob ApiResource mapped as below:
App\Domain\User\Projection\UserJob:
itemOperations:
get:
method: 'GET'
path: '/userjob/{userId}/{jobId}'
requirements:
userId: '%uuid_regex%'
jobId: '%uuid_regex%'
collectionOperations:
get:
method: 'GET'
path: '/userjob/{userId}'
requirements:
userId: '%uuid_regex%'
attributes:
route_prefix: "/query"
In the class, I have:
final class UserJob
{
public $id; //int Auto inc
public $userId; //a UUID
public $jobId; //a UUID
public function __construct($userId, $jobId)
{
$this->userId = $userId;
$this->jobId = $jobId;
}
public function getId(): int
{
return $this->id;
}
public function getUserId()
{
return $this->userId;
}
public function getJobId()
{
return $this->jobId
}
I built a custom data provider for this class, in which I wrote the way to get the resource from the giver parameter (userId):
public function getCollection(string $resourceClass, string $operationName = null)
{
$userId = $this->request->getCurrentRequest()->attributes->get('userId');
return $this->repository->entityManager->getRepository($resourceClass)->findByUserId($userId);
}
When i make a GET call to, let's say, query/userjob/148e3200-f793-447b-bde8-af6b7b27372c it throws an exception:
Unable to generate an IRI for App\Domain\User\Projection\UserJob
And if I debug deeper, in the IRIConverter class, I find that the original exception is thrown from Router:
Some mandatory parameters are missing ("userId") to generate a URL for route "api_user_jobs_get_collection".
Nevertheless, if i dump the result of $this->repository->entityManager->getRepository($resourceClass)->findByUserId($userId);, all the elements that i'm looking for are well fetched from database.
So my intuition is that somehow ApiPlatform process fails to build the collection IRI that we usually can find at the beginning of the payload, and which in my case would be query/userjob/148e3200-f793-447b-bde8-af6b7b27372c.
And it fails while on the normalization or serialization process, because the "extra" param of my custom operation (the user UUID) is not passed to the collection normalizer, iri converter classes, so it has no way to give to the router the missing param to build the "api_user_jobs_get_collection" route.
What am I missing here? Is this a well-known problem that has a readymade solution that I missed ?
Or do I have to look for:
decorate the IRI converter?
use a custom normalizer?
do something with composite ids?
something else?
Your use case may have more solutions, and it depends what is preferred:
decorate iri converter, as you are using identifier in collection and this is not supported out of the box by API platform, as per my knowledge. And this is best choice if url like this are the style of your api.
use custom controller action with custom url style (docs: https://api-platform.com/docs/core/controllers/#creating-custom-operations-and-controllers), best if this is rare url in your api
Annotate your ids as identifiers in your api resource class (doc: https://api-platform.com/docs/core/identifiers/#custom-identifier-normalizer)
/**
* #var Uuid
* #ApiProperty(identifier=true)
*/
public $code;
but I haven't tried this with multiple ids, and this may work only for item url.
You may try to use custom data provider (docs: https://api-platform.com/docs/core/data-providers/). This will need to be done per resource or globally (supports() method) and you will need somehow (regex?) extract ids from url from $context array in getCollection() and getItem() methods. But as ApiPlatform will try to generate item iri, you may still end up with decorating iri converter.
Note: Using id in collection url may lead to other problems, like OpenAPI documentation generation. You may consider if what you want is not filtering of collection by "id" field, nicely supported, or retrieving collection of only "your" items. Which can be done by your data provider injecting security or by doctrine query extensions if someone uses doctrine.
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.
class Thing {
/*** loads of properties ***/
/**
* This is a many to many relation from Thing to Tags
* #var \Doctrine\Common\Collections\Collection
*
* #Assert\All({
* #Assert\Uuid()
* })
*/
private $tags;
/*** moar stuff ***/
}
/*** On the controller ***/
protected function validateAndPersist(Request $request, AbstractEntity $entity, $successHttpCode)
{
$form = $this->createForm($this->getFormDefinition(), $entity);
$form->submit($request);
if ($form->isValid() === true) {
$this->getDoctrineManager()->persist($entity);
$this->getDoctrineManager()->flush();
return $this->createView($entity, $successHttpCode);
}
return $form;
}
So I have this entity above, which has a matching Symfony form and it's all tied up nicely by a controller. The payload coming in is meant to send an array of UUIDs on to the tags property. This works just fine, relations are saved correctly.
So there's some validation in place to make sure we're receiving an array of UUIDs.
Problem is, however, precisely when I'm not receiving an array of UUIDs. $form->submit() is trying to resolve the incoming IDs into actual tags via doctrine. The postgres database is complaining that it's not a valid UUID (the relevant field is of type guid), and I get a Doctrine error. It feels as if this is happening BEFORE form validation, or perhaps validation is not performed at all on this field because of the many to many relation.
Either way this results on a Doctrine\DBAL\DBALException on which the message includes details of the SQL query and whatnot and a big juicy 500 error:
{
"code": 500,
"message": "An exception occurred while executing 'SELECT t0_.tag_id AS tag_id_0, t0_.name AS name_1 FROM tag t0_ WHERE t0_.tag_id IN (?)' with params [\"comedy\"]:\n\nSQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for uuid: \"comedy\""
}
What I would obviously want is for validation defined on the entity to actually happen, as that would trigger a proper HTTP response without any boilerplate code to handle this specific case.
Any ideas?
Are you using another an embedded form for Tags in the main form?
In this case, you need to set cascade_validation property to true on it.
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