I'm getting a circular reference error when serializing a component. Usually this can be fixed using
$normalizer->setCircularReferenceHandler()
However, I'm using the SerializerInterface like this:
/**
* #Route("/get/{id}", name="get_order_by_id", methods="GET")
*/
public function getOrderById(SerializerInterface $serializer, OrderRepository $orderRepository, $id): Response
{
return new Response($serializer->serialize(
$orderRepository->find($id),
'json',
array('groups' => array('default')))
);
}
Is it possible to fix a circular reference error when serializing using this interface?
You totally can. Just add this in your framework config.
framework:
serializer:
circular_reference_handler: App\Serializer\MyCustomCircularReferenceHandler
This handler will work globally. Make sure you register it as a service. I does not need to implement any interface. So just a class with an __invoke() will suffice. That invoke will receive the object that is being "circle referenced" as the only argument.
You can either return the id or do some really cool stuff, like creating a uri for the resource. But the implementation details are totally up to you, as long as you don't return the same object, everything will be fine.
:)
According to the Symfony API Reference on the interface there doesn't look to be a way to execute that function or retrieve the normalizer.
Even in the Serializer, there doesn't look to be a way to retrieve the normalizer after creating the serializer.
You're best off creating the normalizer before the serializer to achieve this, rather than injecting the interface via config files. (Relevant docs link)
Related
For those of you that are familiar with the building of the Symfony container, do you know what is the differences (if any) between
Tagged service Collector using a Compiler pass
Tagged service Collector using the supported shortcut
Service Locator especially, one that collects services by tags
Specifically, I am wondering about whether these methods differ on making these collected services available sooner or later in the container build process. Also I am wondering about the ‘laziness’ of any of them.
It can certainly be confusing when trying to understand the differences. Keep in mind that the latter two approaches are fairly new. The documentation has not quite caught up. You might actually consider making a new project and doing some experimenting.
Approach 1 is basically an "old school" style. You have:
class MyCollector {
private $handlers = [];
public function addHandler(MyHandler $hamdler) {
$handlers[] = $handler;
# compiler pass
$myCollectorDefinition->addMethodCall('addHandler', [new Reference($handlerServiceId)]);
So basically the container will instantiate MyCollector then explicitly call addHandler for each handler service. In doing so, the handler services will be instantiated unless you do some proxy stuff. So no lazy creation.
The second approach provides a somewhat similar capability but uses an iterable object instead of a plain php array:
class MyCollection {
public function __construct(iterable $handlers)
# services.yaml
App\MyCollection:
arguments:
- !tagged_iterator my.handler
One nice thing about this approach is that the iterable actually ends up connecting to the container via closures and will only instantiate individual handlers when they are actually accessed. So lazy handler creation. Also, there are some variations on how you can specify the key.
I might point out that typically you auto-tag your individual handlers with:
# services.yaml
services:
_instanceof:
App\MyHandlerInterface:
tags: ['my.handler']
So no compiler pass needed.
The third approach is basically the same as the second except that handler services can be accessed individually by an index. This is useful when you need one out of all the possible services. And of course the service selected is only created when you ask for it.
class MyCollection {
public function __construct(ServiceLocator $locator) {
$this->locator = $locator;
}
public function doSomething($handlerKey) {
/** #var MyHandlerInterface $handler */
$handler = $serviceLocator->get($handlerKey);
# services.yaml
App\MyCollection:
arguments: [!tagged_locator { tag: 'app.handler', index_by: 'key' }]
I should point out that in all these cases, the code does not actually know the class of your handler service. Hence the var comment to keep the IDE happy.
There is another approach which I like in which you make your own ServiceLocator and then specify the type of object being located. No need for a var comment. Something like:
class MyHandlerLocator extends ServiceLocator
{
public function get($id) : MyHandlerInterface
{
return parent::get($id);
}
}
The only way I have been able to get this approach to work is a compiler pass. I won't post the code here as it is somewhat outside the scope of the question. But in exchange for a few lines of pass code you get a nice clean custom locator which can also pick up handlers from other bundles.
I am working on my first Symfony project. I wonder what is the best / recommendet method to write log messages from anywhere in my code.
So far I used Monolog which works great when being used in a controller:
public function indexAction() {
$logger = $this->get('logger');
$logger->info('I just got the logger');
$logger->error('An error occurred');
// ...
}
But how can I use this code from any classe/code from my project? Doctrine entity classes for example cannot use $this->get('logger') to create the logger. How can I access the service in these classes? Or what other methode to log message is recommended in these cases?
EDIT: Of course I could create the logger in any controller and pass it down to all other classes. But this would be quite very cumbersome. There has to be a better way.
IMO, a first approach could be the creation of Event Listeners for specific actions in order to log only what you have decided to.
Have a look to this chapter : http://symfony.com/doc/current/cookbook/event_dispatcher/event_listener.html
Hope it will help you.
I have defined lifecycleCallbacks in yaml as follows:
lifecycleCallbacks:
prePersist: [setCreatedAtValue]
preUpdate: [setUpdatedAtValue]
The above has generated entities with the respective functions as follows:
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
if($this->created_at == null)
{
$this->created_at = new \DateTime();
}
}
Which looks all fine, right? However, when I try to open the sonata admin page, I get the following error
[Semantical Error] The annotation "#ORM\PrePersist" in method AppBundle\Entity\Article::setCreatedAtValue() was never imported. Did you maybe forget to add a "use" statement for this annotation?
I have never encountered this before and a bit confused about what to do. I am using symfony 2.7.6, Doctrine ORM version 2.5.1, Sonata Admin 2.3.7
Any help will be greatly appreciated
Since you defined your callbacks using yaml, you don´t need to define them again using annotations. Just remove the comments with the #ORM\PrePersist block before the function and everything will be fine.
If you wanted to use annotations to define your doctrine properties, you would need to import them before you can use them. To do so you would need to add this line at the beginning of your file:
use Doctrine\ORM\Mapping as ORM;
Same issue came with me.
In my case everything worked well until I did not Serialize my object in JsonResponse.
So problem was that previously I was not using that entity class (which was giving error) for sending JsonResponse, as soon as I tried to prepare JsonResponse containing that class, JsonResponse failed to serialize my class as it hadn't implemented Serializable interface.
This will happen if you will fetch objects with methods like findAll or findBy which returns Entity objects instead of php array.
So I just skipped those native methods and write doctrine query for fetching data.
You can also implement Serializable interface.
Hi I'm trying to use Doctrine inside the default ExeptionController but I get the following error:
Fatal error: Call to undefined method Symfony\Bundle\TwigBundle\Controller\ExceptionController::getDoctrine()
when I try to call:
$manager = $this->getDoctrine()->getManager();
What I'm trying to do is to have a custom 404 page where I can present some items from the database.
Could you please help me? Thank you!
You may also inject the Doctrine service as a dependency in your Controller (in that case you don't need to entend class Controller)
You will have to create your own ExceptionController extending the default one. You'll have to declare it as described here: http://symfony.com/doc/current/cookbook/controller/error_pages.html#custom-exception-controller. Your custom controller must have a constructor with at least an argument of type Registry (Doctrine). You have to declare that controller as a service in your service.yml (or xml depending on your config) Have a look at the symfony doc for further explanation on how to do that. For the moment I can't help you much more as I'm outside with my Android and it's rather difficult to make long answers
My app has an entity called "client" that is injected into every request object.
To improve performance I've set up a custom cache, and am using JMSSerializer to serialize and cache client objects. When a request comes in, the cache returns the serialized Client object, and JMS deserializes it.
My application creates other entities, call them ChildEntities, and associates them with Client objects via a ManyToOne relationship on the ChildEntity.
This was working fine until I started loading these objects via deserializing the cached data, rather than using Doctrine to load them from the MySQL database. Doctrine now throws this error:
A new entity was found through the relationship "\acme\bundle\Entity\ChildEntity#client" that was not configured to cascade persist operations for entity: ClientName. 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"})
So, if I'm understanding this error properly, it seems that Doctrine thinks my Client object which is referenced by a ManyToOne relationship on the ChildEntity object is a new object (even though I'm deserializing it with the ID) that it needs to save, but won't because no cascading is set up.
Is my strategy of deserializing objects wrong? Is there some way to deserialize them as Proxy objects, or some other solution to this problem? I prefer to use my custom cache to a doctrine cache.
Figured this one out when I finally found the right search phrase.
I was using the default object constructor, I needed to be using the Doctrine Object Constructor.
To do so, I added this to my services.yml:
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
I use this module too and keep a serialized doctrine entity in session in json format.
So, I figured out that I had to configure Spea/JMSSerializerModule to use DoctrineObjectConstructor instead of UnserializeObjectConstructor.
But, contrary to UnserializeObjectConstructor, DoctrineObjectConstructor is not a simple invokable class. And the constructor needs two objects:
interface Doctrine\Common\Persistence\ManagerRegistry $managerRegistry
interface JMS\Serializer\Construction\ObjectConstructorInterface $fallbackConstructor
The problem is that there is no implementation of interface ManagerRegistry and obviously, no factory for it.
I resolved this by adding this dependency https://github.com/eddiejaoude/zf2-doctrine2-manager-registry-service in my composer.json. This module provides an implementation of interface ManagerRegistry through the service manager entry Doctrine\ManagerRegistry.
Once installed I created a factory for DoctrineObjectConstructor:
<?php
namespace Example\Service;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use JMS\Serializer\Construction\DoctrineObjectConstructor;
class DoctrineObjectConstructorFactory implements FactoryInterface
{
/* (non-PHPdoc)
* #see \Zend\ServiceManager\FactoryInterface::createService()
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serv1 = $serviceLocator->get('Doctrine\ManagerRegistry');
$serv2 = $serviceLocator->get('jms_serializer.unserialize_object_constructor');
$service = new DoctrineObjectConstructor($serv1, $serv2);
return $service;
}
}
And added these entries in my module.config.php:
'service_manager' => array(
'factories' => array(
'jms_serializer.doctrine_object_constructor' => 'Example\Service\DoctrineObjectConstructorFactory'
),
'aliases' => array(
'jms_serializer.object_constructor' => 'jms_serializer.doctrine_object_constructor'
),
),
That's it. Now, it works.
I wrote an issue for this here : https://github.com/Spea/JMSSerializerModule/issues/12 because I think we should not have to add external module to do this.