How to serialize a custom findBy() method? - symfony

When I use findBy(), my data is automatically serialized.
$users = $this->getDoctrine()->getRepository('AppBundle:User')->findBy($params, $orderBy, $limit, $offset);
/**
* #VirtualProperty
* #SerializedName("catagory")
* #Groups({"user"})
*/
public function getCategoryId()
{
return $this->category->getId();
}
But when I use a custom MyfindBy() from my repository, the data is not automatically serialized. How can I automatically serialize the data?
SOLUTION :
Don't use select at the querybuilder.

Please follow the documentation of symfony serializer from visiting this https://symfony.com/doc/current/components/serializer.html
(follow your desired verson documentation from there)
After getting your objecs by custom query u can pass the whole object array named $persons to the $jsonContent = $serializer->serialize($persons, 'json'); and send it to your response . after installing serializer components to your project .
please inform me if it not works for you.Thank you.

Related

What is the best way to create a singleton entity in Symfony 4?

I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.

How do I create a custom exclusion strategy for JMS Serializer that allows me to make run-time decisions about whether to include a particular field?

As the title says, I am trying to make a run-time decision on whether or not to include fields in the serialization. In my case, this decision will be based on permissions.
I am using Symfony 2, so what I'm looking to do is add an additional annotation called #ExcludeIf which accepts a security expression.
I can handle the annotation parsing and storing of the meta data, but I am not able to see how to integrate a custom exclusion strategy with the library.
Any suggestions?
Note: exclusion strategies are an actual construct in the JMS codebase, I just haven't been able to figure out the best way to integrate an extra on top of the others
PS: I had asked about this before and was pointed to using groups. For various reasons this is a very poor solution for my needs.
You just have to create a class that implements JMS\Serializer\Exclusion\ExclusionStrategyInterface
<?php
namespace JMS\Serializer\Exclusion;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Context;
interface ExclusionStrategyInterface
{
/**
* Whether the class should be skipped.
*
* #param ClassMetadata $metadata
*
* #return boolean
*/
public function shouldSkipClass(ClassMetadata $metadata, Context $context);
/**
* Whether the property should be skipped.
*
* #param PropertyMetadata $property
*
* #return boolean
*/
public function shouldSkipProperty(PropertyMetadata $property, Context $context);
}
In your case, you can implement your own custom logic in the shouldSkipProperty method and always return false for shouldSkipClass.
Example of implementation can be found in the JMS/Serializer repository
We will reference the created service as acme.my_exclusion_strategy_service below.
In your controller action:
<?php
use Symfony\Component\HttpFoundation\Response;
use JMS\Serializer\SerializationContext;
// ....
$context = SerializationContext::create()
->addExclusionStrategy($this->get('acme.my_exclusion_strategy_service'));
$serial = $this->get('jms_serializer')->serialize($object, 'json', $context);
return new Response($serial, Response::HTTP_OK, array('Content-Type' => 'application/json'));
Or if you are using FOSRestBundle
<?php
use FOS\RestBundle\View;
use JMS\Serializer\SerializationContext;
// ....
$context = SerializationContext::create()
->addExclusionStrategy($this->get('acme.my_exclusion_strategy_service'))
$view = new View($object);
$view->setSerializationContext($context);
// or you can create your own view factory that handles the creation
// of the context for you
return $this->get('fos_rest.view_handler')->handle($view);
As of jms/serializer 1.4.0, the symfony expression language is integrated in its core.
The feature is documented at http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies#dynamic-exclusion-strategy and this allows to use runtime exclusion strategies.
An example taken from the documentation is:
class MyObject
{
/**
* #Exclude(if="service('user_manager_service').getSomeRuntimeData(object)")
*/
private $name;
/**
* #Expose(if="service('request_stack').getCurrent().has('foo')")
*/
private $name2;
}
I this example, the services user_manager_service and request_stack are invoked at runtime, and depending on the return (true or false), the property will be exposed or not.
With the same expression language, as of 1.6.0 is possible also to use virtual properties via expression language.
Documented at http://jmsyst.com/libs/serializer/master/reference/annotations#virtualproperty allows to add on the fly data coming from external services

When to use Entity Manager in Symfony2

At the moment I am learning how to use Symfony2. I got to the point where they explain how to use Doctrine.
In the examples given they sometimes use the entity manager:
$em = $this->getDoctrine()->getEntityManager();
$products = $em->getRepository('AcmeStoreBundle:Product')
->findAllOrderedByName();
and in other examples the entity manager is not used:
$product = $this->getDoctrine()
->getRepository('AcmeStoreBundle:Product')
->find($id);
So I actually tried the first example without getting the entity manager:
$repository = $this->getDoctrine()
->getRepository('AcmeStoreBundle:Product');
$products = $repository->findAllOrderedByName();
and got the same results.
So when do i actually need the entity manager and when is it OK to just go for the repository at once?
Looking at Controller getDoctrine() equals to $this->get('doctrine'), an instance of Symfony\Bundle\DoctrineBundle\Registry. Registry provides:
getEntityManager() returning Doctrine\ORM\EntityManager, which in turn provides getRepository()
getRepository() returning Doctrine\ORM\EntityRepository
Thus, $this->getDoctrine()->getRepository() equals $this->getDoctrine()->getEntityManager()->getRepository().
Entity manager is useful when you want to persist or remove an entity:
$em = $this->getDoctrine()->getEntityManager();
$em->persist($myEntity);
$em->flush();
If you are just fetching data, you can get only the repository:
$repository = $this->getDoctrine()->getRepository('AcmeStoreBundle:Product');
$product = $repository->find(1);
Or better, if you are using custom repositories, wrap getRepository() in a controller function as you can get auto-completition feature from your IDE:
/**
* #return \Acme\HelloBundle\Repository\ProductRepository
*/
protected function getProductRepository()
{
return $this->getDoctrine()->getRepository('AcmeHelloBundle:Product');
}
I think that the getDoctrine()->getRepository() is simply a shortcut to getDoctrine()->getEntityManager()->getRepository(). Did not check the source code, but sounds rather reasonable to me.
If you plan to do multiple operations with the entity manager (like get a repository, persist an entity, flush, etc), then get the entity manager first and store it in a variable. Otherwise, you can get the repository from the entity manager and call whatever method you want on the repository class all in one line. Both ways will work. It's just a matter of coding style and your needs.

Using distinct Doctrine2

I am developing an application in symfony2 and using doctrine2. I created a custom repository class that has one function:
<?php
namespace Anotatzailea\AnotatzaileaBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* InterpretatzeaRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class InterpretatzeaRepository extends EntityRepository
{
public function getInterpDesberdinak($value)
{
$qb = $this->createQueryBuilder('c')
->select('DISTINCT c.attribute')
->where('c.fer = :Value')
->setParameter('Value', $value);
$Emaitza = $qb->getQuery()->getResult();
return $Emaitza;
}
}
What I want to get with this function is an array of all the "Interpretatzea" objects that have a distinct c.attribute and all have c.fer = value. Is the query correct? I would also want to know how to pass the value parameter to the repository function. Thanks
A cursory look at your repository method suggests it looks okay :) IIRC, I think the use of DISTINCT there is fine. If you do have problems you can always do a GROUP BY instead.
As for calling the repo method in a controller and passing a $value variable to it, that's pretty straightforward; for example:
// in your controller
$value = 'foo';
// get doctrine connection from DI, etc.
$em = $this->getDoctrine()
->getEntityManager();
// get the repository object for your
// entity and call your repository method
$result = $em->getRepository('AnotatzaileaAnotatzaileaBundle:Interpretatzea')
->getInterpDesberdinak($value);
// ... do something with your $result here
Note you use a concatenated version of your namespace and bundle, followed by a colon and the entity; e.g: AcmeTestBundle:User
Hope this helps :)

How to update a Doctrine Entity from a serialized JSON?

We are using Symfony2 to create an API. When updating a record, we expect the JSON input to represent a serialized updated entity. The JSON data will not contain some fields (for instance, CreatedAt should be set only once when the entity is created - and never updated). For instance, here is an example JSON PUT request:
{"id":"1","name":"anyname","description":"anydescription"}
Here is the PHP code on the Controller that should update the entity according to the JSON above (we are using JMS serializer Bundle):
$supplier = $serializer->deserialize(
$this->get('request')->getContent(),
'WhateverEntity',
'json'
);
The EntityManger understands (correctly) that this is an update request (in fact, a SELECT query is implicitly triggered). The EntityManager also guess (not correctly) that CreatedAt property should be NULLified - it should instead keep the previous one.
How to fix this issue?
It's possible as well to do it with Symfony Serializer using object_to_populate option.
Example: I receive JSON request. If record exists in database I want to update fields received in body, if it does not exist I want to create new one.
/**
* #Route("/{id}", methods={"PUT"})
*/
public function upsert(string $id, Request $request, SerializerInterface $serializer)
{
$content = $request->getContent(); // Get json from request
$product = $this->getDoctrine()->getRepository(Product::class)->findOne($id); // Try to find product in database with provided id
if (!$product) { // If product does not exist, create fresh entity
$product = new Product();
}
$product = $serializer->deserialize(
$content,
Product::class,
'json',
['object_to_populate' => $product] // Populate deserialized JSON content into existing/new entity
);
// validation, etc...
$this->getDoctrine()->getManager()->persist($product); // Will produce update/instert statement
$this->getDoctrine()->getManager()->flush($product);
// (...)
using the JMSSerializerBundle follow the install instructions at
http://jmsyst.com/bundles/JMSSerializerBundle
either create your own serializer service or alter the JMSSerializerBundle to use the doctrine object constructor instead of the simple object constructor.
<service id="jms_serializer.object_constructor" alias="jms_serializer.doctrine_object_constructor" public="false"/>
This basically handles exactly what Ocramius solution does but using the JMSSerializerBundles deserialize.
I would use the Doctrine\ORM\Mapping\ClassMetadata API to discover existing fields in your entity.
You can do following (I don't know how JMSSerializerBundle works):
//Unserialize data into $data
$metadata = $em->getMetadataFactory()->getMetadataFor($FQCN);
$id = array();
foreach ($metadata->getIdentifierFieldNames() as $identifier) {
if (!isset($data[$identifier])) {
throw new InvalidArgumentException('Missing identifier');
}
$id[$identifier] = $data[$identifier];
unset($data[$identifier]);
}
$entity = $em->find($metadata->getName(), $id);
foreach ($metadata->getFieldNames() as $field) {
//add necessary checks about field read/write operation feasibility here
if (isset($data[$field])) {
//careful! setters are not being called! Inflection is up to you if you need it!
$metadata->setFieldValue($entity, $field, $data[$field]);
}
}
$em->flush();

Resources