Disable requestBody for custom action in API Platform - api-platform.com

I want to disable openapi's requestBody documentation for a custom POST route action where no parameters are.
For this I have tried to extend the openapi_context like this:
/*
* #ApiResource(
* ...
* itemOperations={
* ...
* "post_clone" = {
* ...
* "openapi_context"={
* "requestBody"=null
* }
* }
* }
* )
*/
Setting the requestBody to null or false is the same as skipping the value and leads to the default documentation for POST routes.
Setting the requestBody to {} writes the empty object in the documentation, but what I need is that the key is skipped in the documentation.

Just add 'defaults' => ['_api_receive' => false] to you custom operation:
#[ApiResource(
itemOperations: [
'post_clone' => [
...
'defaults' => ['_api_receive' => false],
'openapi_context' => [
'requestBody'=> ['required' => false, 'content' => []]
]
]
]
)]

You're going the wrong way to implement your clone operation. POST operations are collection operations, not item operation. Even if today you're able to declare a POST operation as an item operation, it is more likely a side effect of API-Platform, not the expected behaviour.
In order to respect API-Platform lifecyle, i suggest you to do the following:
First, declare your clone operation as a POST collection operation, using as input DTO named MyEntityCloneDto:
use App\Dto\MyEntityCloneDto;
/**
* #ApiResource(
* collectionOperations={
* "get",
* "post",
* "clone" = {
* "method" = "post",
* "path" = "/my_entities/clone",
* "input" = MyEntityCloneDto::class,
* }
* }
* )
* #ORM\Entity(repositoryClass=MyEntityRepository::class)
*/
class MyEntity
Then create the input DTO:
namespace App\Dto;
use App\Entity\MyEntity;
use Symfony\Component\Validator\Constraints as Assert;
class MyEntityCloneDto
{
/**
* The entity to clone.
*
* #Assert\NotNull()
*/
public ?MyEntity $myEntity = null;
}
Finally create the DataTransformer:
namespace App\DataTransformer;
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use App\Entity\MyEntity;
use ApiPlatform\Core\Validator\ValidatorInterface;
class MyEntityCloner implements DataTransformerInterface
{
private ValidatorInterface $validator;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function transform($object, string $to, array $context = [])
{
$this->validator->validate($object);
return clone $object->myEntity; //add more logic if needed of course
}
public function supportsTransformation($data, string $to, array $context = []): bool
{
return MyEntity::class === $to;
}
}
To clone MyEntity with ID 5:
curl --request POST \
--header 'content-type: application/json' \
--header 'accept: application/ld+json' \
--data '{"myEntity": "/my_entities/5"}' \
http://example.com/api/my_entities/clone
This way, the OpenAPI view displays the truth and your operation remains stable.
Note that if you need a clone operation on all your entities, it is possible to re-use the data transformer, since its method are not typed. But in order to make the Serializer works, you need one DTO per entity, because it needs to know the type of the entity to clone.

You can make an item_operation POST, with a specific controller, no problem.
To disable de requestBody in the swagguer doc:
"openapi_context" => [
"requestBody"=> ["required" => false, "content" => []],
]

Related

how to use apiReources method with `only`?

I'm creating an api with Laravel and I am looking for an easy lazy way to to register Api resources.
I'm currently defining my routes like this:
Route::apiResource('categories', 'CategoryController')->only(['index', 'show']);
I checked Laravel's controller documentation and I saw apiResources method which I can create multiple api resources at once.
the goal:
is to be able to use apiResources with only method like this
Route::apiResources(['categories' => 'CategoryController', 'products' => 'ProductController'])->only(['index', 'show']);
current result:
Call to a member function only() on null
long story short (if you don't want to read the whole story) you can just do it like this:
Route::apiResources(['brands' => 'BrandController', 'categories' => 'CategoryController'], ['only' => ['index', 'show']]);
When I was writing the question it passed to my mind to check the apiResources declaration and I found this:
/**
* Register an array of API resource controllers.
*
* #param array $resources
* #param array $options
* #return void
*/
public function apiResources(array $resources, array $options = [])
{
foreach ($resources as $name => $controller) {
$this->apiResource($name, $controller, $options);
}
}
and since it is using apiResource under the hood and it is passing options parameter I can check what are these options
/**
* Route an API resource to a controller.
*
* #param string $name
* #param string $controller
* #param array $options
* #return \Illuminate\Routing\PendingResourceRegistration
*/
public function apiResource($name, $controller, array $options = [])
{
$only = ['index', 'show', 'store', 'update', 'destroy'];
if (isset($options['except'])) {
$only = array_diff($only, (array) $options['except']);
}
return $this->resource($name, $controller, array_merge([
'only' => $only,
], $options));
}

GraphQl - how to add current user to mutation object

I am attempting to add the current user to a create mutation by decorating graphql stages as per the documentation.
It is a feature to allow users to block other users in a message system, fyi.
It should satisfy the following access control:
"access_control"="is_granted('IS_AUTHENTICATED_FULLY') and object.getBlocker() == user"
Meaning that the user that is blocking is the currently authenticated user.
I can get it done if I modify the above to just:
"access_control"="is_granted('IS_AUTHENTICATED_FULLY')"
by decorating the deserialize stage like so:
App/Stage/DeserializeStage
/**
* #param object|null $objectToPopulate
*
* #return object|null
*/
public function __invoke($objectToPopulate, string $resourceClass, string $operationName, array $context)
{
// Call the decorated serialized stage (this syntax calls the __invoke method).
$deserializeObject = ($this->deserializeStage)($objectToPopulate, $resourceClass, $operationName, $context);
if ($resourceClass === 'App\Entity\BlockedUser' && $operationName === 'create') {
$user = $this->tokenStorage->getToken()->getUser();
$deserializeObject->setBlocker($user);
}
return $deserializeObject;
}
As I understand it, in order to get it to work fully satisfying the access control, I would need to decorate the read stage, which comes before the security stage and insert the currently authenticated user to the object.
In that way, it would satisfy the second portion of the access control, ie,
and object.getBlocker() == user
I attempted to do it as follows, but I get a NULL object :
App/Stage/ReadStage
/**
* #return object|iterable|null
*/
public function __invoke(?string $resourceClass, ?string $rootClass, string $operationName, array $context)
{
$readObject = ($this->readStage)($resourceClass, $rootClass, $operationName, $context);
var_dump($readObject->getBlocked()->getUsername()); // throws error 'method getBlocked on NULL
if ($resourceClass === 'App\Entity\BlockedUser' && $operationName === 'create') {
$userId = $this->tokenStorage->getToken()->getUser();
$readObject->setBlocker($user);
}
return $readObject;
}
Well, after restarting the app it seems to be working properly in the deserialize stage. It might have been an issue with cache or something.
I am still not sure why it works in the deserialize stage nor if that's the correct place to modify the object.
In any case, it is working as is, so...
So, I am posting the full code for reference.
App/Stage/DeserializeStage
<?php
namespace App\Stage;
use ApiPlatform\Core\GraphQl\Resolver\Stage\DeserializeStageInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
final class DeserializeStage implements DeserializeStageInterface
{
private $deserializeStage;
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(
DeserializeStageInterface $deserializeStage,
TokenStorageInterface $tokenStorage)
{
$this->deserializeStage = $deserializeStage;
$this->tokenStorage = $tokenStorage;
}
/**
* #param object|null $objectToPopulate
*
* #return object|null
*/
public function __invoke($objectToPopulate, string $resourceClass, string $operationName, array $context)
{
// Call the decorated serialized stage (this syntax calls the __invoke method).
$deserializeObject = ($this->deserializeStage)($objectToPopulate, $resourceClass, $operationName, $context);
if ($resourceClass === 'App\Entity\BlockedUser' && $operationName === 'create') {
$user = $this->tokenStorage->getToken()->getUser();
$deserializeObject->setBlocker($user);
}
return $deserializeObject;
}
}
And you need to add this to config/services.yaml
App\Stage\DeserializeStage:
decorates: api_platform.graphql.resolver.stage.deserialize

How to add facet to the query (solr)

I can't add facets to the query. I try
$query = new Query;
$query->facetBuilders = [ new \eZ\Publish\API\Repository\Values\Content\Query\FacetBuilder\FieldFacetBuilder];
services:
myservice:
class: mynamespace\FacetHandler
tags:
- {name: ezpublish.search.solr.content.facet_builder_visitor}
And I have got the error "Intentionally not implemented: No visitor available for: eZ\Publish\API\Repository\Values\Content\Query\FacetBuilder\FieldFacetBuilder"
Also I have tried tag "ezpublish.search.solr.content.facet_builder_visitor.aggregate"
What I do wrong?
you are required to hand over the field you want to apply the facet on.
In your case it might look like this:
$query = new Query;
$query->facetBuilders = [ new \eZ\Publish\API\Repository\Values\Content\Query\FacetBuilder\FieldFacetBuilder(
[
'fieldPaths' => 'article/title'
]
)];
"article" is the type-identifier of the class to filter by. I have yet to try if you can actually use it without a class-limitation.
"title" defines the field-identifier to use for the facet.
You may also use regex or sort (in addition to the fieldPaths-key to filter and sort the result.
The possible values for sort are listed as constants in the FieldFacetBuilder-class
Hope this helps.
Configure your field class as below
parameters:
ezpublish.search.solr.query.content.facet_builder_visitor.field.class: Your\Bundle\Query\Content\FacetBuilderVisitor\Field
Define your service as below:
ezpublish.search.solr.query.content.facet_builder_visitor.field:
class: "%ezpublish.search.solr.query.content.facet_builder_visitor.field.class%"
tags:
- {name: ezpublish.search.solr.query.content.facet_builder_visitor}
Implement your class
<?php
/**
*
*/
namespace Your\Bundle\Query\Content\FacetBuilderVisitor;
use EzSystems\EzPlatformSolrSearchEngine\Query\FacetBuilderVisitor;
use eZ\Publish\API\Repository\Values\Content\Query\FacetBuilder;
use eZ\Publish\API\Repository\Values\Content\Search\Facet;
/**
* Visits the Field facet builder.
*/
class Field extends FacetBuilderVisitor
{
/**
* CHeck if visitor is applicable to current facet result.
*
* #param string $field
*
* #return bool
*/
public function canMap($field)
{
return $field === 'field_id';
}
/**
* Map Solr facet result back to facet objects.
*
* #param string $field
* #param array $data
*
* #return Facet
*/
public function map($field, array $data)
{
return new Facet\FieldFacet(
array(
'name' => 'field',
'entries' => $this->mapData($data),
)
);
}
/**
* Check if visitor is applicable to current facet builder.
*
* #param FacetBuilder $facetBuilder
*
* #return bool
*/
public function canVisit(FacetBuilder $facetBuilder)
{
return $facetBuilder instanceof FacetBuilder\FieldFacetBuilder;
}
/**
* Map field value to a proper Solr representation.
*
* #param FacetBuilder $facetBuilder;
*
* #return string
*/
public function visit(FacetBuilder $facetBuilder)
{
return array(
'facet.field' => 'field_id',
'f.field_id.facet.limit' => $facetBuilder->limit,
'f.field_id.facet.mincount' => $facetBuilder->minCount,
);
}
}
No more exception now ;) But does not work :'( https://doc.ez.no/display/DEVELOPER/Browsing%2C+finding%2C+viewing#Browsing,finding,viewing-PerformingaFacetedSearch

Yii CValidator Rules : how to validate outside of a model

I need to validate some variables values in Yii;
I dont have a model, and i need a pre build yii public method.
some of them must be integer, other string;
The values are being passed with GET.
I tryed all the validation classes that yii has and none works.
Has anyone tryed this and succeded ?
i need something like:
$validator = new CValidator();
$result = $validator->validate(array($key=>$value));
opened for sugestions
You can do it for specific validators:
$Validator = new CEmailValidator;
if($Validator->validateValue($value))
{
// Valid
}
From the Yii Framework file CEmailValidator.php:
/**
* Validates a static value to see if it is a valid email.
* Note that this method does not respect {#link allowEmpty} property.
* This method is provided so that you can call it directly without going through the model validation rule mechanism.
* #param mixed $value the value to be validated
* #return boolean whether the value is a valid email
* #since 1.1.1
*/
public function validateValue($value)
Yii validators are tightly integrated with models. So, atleast you need a dummy model object.
my suggestion would be like... create a dummy form model class..
class MyValidator extends CFormModel {
public function __get($name) {
return isset($_POST[$name])?$_POST[$name]:null;
}
static function myValidate( Array $rules ) {
$dummy = new MyValidator();
foreach($rules as $rule) {
if( isset($rule[0],$rule[1]) ) {
$validator = CValidator::createValidator(
$rule[1],
$dummy,
$rule[0],
array_slice($rule,2)
);
$validator->validate($dummy);
}
else { /* throw error; */ }
}
print_r( $dummy->getErrors() );
return !$dummy->hasErrors();
}
}
and use this myValidate static method anywhere just like below...
$rules = array(
array('name, email', 'required'),
array('email', 'email'),
);
if( MyValidator::myValidate($rules) ) {
....
}

How to encode Doctrine entities to JSON in Symfony 2.0 AJAX application?

I'm developing game app and using Symfony 2.0. I have many AJAX requests to the backend. And more responses is converting entity to JSON. For example:
class DefaultController extends Controller
{
public function launchAction()
{
$user = $this->getDoctrine()
->getRepository('UserBundle:User')
->find($id);
// encode user to json format
$userDataAsJson = $this->encodeUserDataToJson($user);
return array(
'userDataAsJson' => $userDataAsJson
);
}
private function encodeUserDataToJson(User $user)
{
$userData = array(
'id' => $user->getId(),
'profile' => array(
'nickname' => $user->getProfile()->getNickname()
)
);
$jsonEncoder = new JsonEncoder();
return $jsonEncoder->encode($userData, $format = 'json');
}
}
And all my controllers do the same thing: get an entity and encode some of its fields to JSON. I know that I can use normalizers and encode all entitities. But what if an entity has cycled links to other entity? Or the entities graph is very big? Do you have any suggestions?
I think about some encoding schema for entities... or using NormalizableInterface to avoid cycling..,
With php5.4 now you can do :
use JsonSerializable;
/**
* #Entity(repositoryClass="App\Entity\User")
* #Table(name="user")
*/
class MyUserEntity implements JsonSerializable
{
/** #Column(length=50) */
private $name;
/** #Column(length=50) */
private $login;
public function jsonSerialize()
{
return array(
'name' => $this->name,
'login'=> $this->login,
);
}
}
And then call
json_encode(MyUserEntity);
Another option is to use the JMSSerializerBundle. In your controller you then do
$serializer = $this->container->get('serializer');
$reports = $serializer->serialize($doctrineobject, 'json');
return new Response($reports); // should be $reports as $doctrineobject is not serialized
You can configure how the serialization is done by using annotations in the entity class. See the documentation in the link above. For example, here's how you would exclude linked entities:
/**
* Iddp\RorBundle\Entity\Report
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Iddp\RorBundle\Entity\ReportRepository")
* #ExclusionPolicy("None")
*/
....
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="reports")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
* #Exclude
*/
protected $client;
You can automatically encode into Json, your complex entity with:
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
$serializer = new Serializer(array(new GetSetMethodNormalizer()), array('json' => new
JsonEncoder()));
$json = $serializer->serialize($entity, 'json');
To complete the answer: Symfony2 comes with a wrapper around json_encode:
Symfony/Component/HttpFoundation/JsonResponse
Typical usage in your Controllers:
...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}
I found the solution to the problem of serializing entities was as follows:
#config/config.yml
services:
serializer.method:
class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
serializer.encoder.json:
class: Symfony\Component\Serializer\Encoder\JsonEncoder
serializer:
class: Symfony\Component\Serializer\Serializer
arguments:
- [#serializer.method]
- {json: #serializer.encoder.json }
in my controller:
$serializer = $this->get('serializer');
$entity = $this->get('doctrine')
->getRepository('myBundle:Entity')
->findOneBy($params);
$collection = $this->get('doctrine')
->getRepository('myBundle:Entity')
->findBy($params);
$toEncode = array(
'response' => array(
'entity' => $serializer->normalize($entity),
'entities' => $serializer->normalize($collection)
),
);
return new Response(json_encode($toEncode));
other example:
$serializer = $this->get('serializer');
$collection = $this->get('doctrine')
->getRepository('myBundle:Entity')
->findBy($params);
$json = $serializer->serialize($collection, 'json');
return new Response($json);
you can even configure it to deserialize arrays in http://api.symfony.com/2.0
I just had to solve the same problem: json-encoding an entity ("User") having a One-To-Many Bidirectional Association to another Entity ("Location").
I tried several things and I think now I found the best acceptable solution. The idea was to use the same code as written by David, but somehow intercept the infinite recursion by telling the Normalizer to stop at some point.
I did not want to implement a custom normalizer, as this GetSetMethodNormalizer is a nice approach in my opinion (based on reflection etc.). So I've decided to subclass it, which is not trivial at first sight, because the method to say if to include a property (isGetMethod) is private.
But, one could override the normalize method, so I intercepted at this point, by simply unsetting the property that references "Location" - so the inifinite loop is interrupted.
In code it looks like this:
class GetSetMethodNormalizer extends \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer {
public function normalize($object, $format = null)
{
// if the object is a User, unset location for normalization, without touching the original object
if($object instanceof \Leonex\MoveBundle\Entity\User) {
$object = clone $object;
$object->setLocations(new \Doctrine\Common\Collections\ArrayCollection());
}
return parent::normalize($object, $format);
}
}
I had the same problem and I chosed to create my own encoder, which will cope by themself with recursion.
I created classes which implements Symfony\Component\Serializer\Normalizer\NormalizerInterface, and a service which holds every NormalizerInterface.
#This is the NormalizerService
class NormalizerService
{
//normalizer are stored in private properties
private $entityOneNormalizer;
private $entityTwoNormalizer;
public function getEntityOneNormalizer()
{
//Normalizer are created only if needed
if ($this->entityOneNormalizer == null)
$this->entityOneNormalizer = new EntityOneNormalizer($this); //every normalizer keep a reference to this service
return $this->entityOneNormalizer;
}
//create a function for each normalizer
//the serializer service will also serialize the entities
//(i found it easier, but you don't really need it)
public function serialize($objects, $format)
{
$serializer = new Serializer(
array(
$this->getEntityOneNormalizer(),
$this->getEntityTwoNormalizer()
),
array($format => $encoder) );
return $serializer->serialize($response, $format);
}
An example of a Normalizer :
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class PlaceNormalizer implements NormalizerInterface {
private $normalizerService;
public function __construct($normalizerService)
{
$this->service = normalizerService;
}
public function normalize($object, $format = null) {
$entityTwo = $object->getEntityTwo();
$entityTwoNormalizer = $this->service->getEntityTwoNormalizer();
return array(
'param' => object->getParam(),
//repeat for every parameter
//!!!! this is where the entityOneNormalizer dealt with recursivity
'entityTwo' => $entityTwoNormalizer->normalize($entityTwo, $format.'_without_any_entity_one') //the 'format' parameter is adapted for ignoring entity one - this may be done with different ways (a specific method, etc.)
);
}
}
In a controller :
$normalizerService = $this->get('normalizer.service'); //you will have to configure services.yml
$json = $normalizerService->serialize($myobject, 'json');
return new Response($json);
The complete code is here : https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer
in Symfony 2.3
/app/config/config.yml
framework:
# сервис конвертирования объектов в массивы, json, xml и обратно
serializer:
enabled: true
services:
object_normalizer:
class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
tags:
# помечаем к чему относится этот сервис, это оч. важно, т.к. иначе работать не будет
- { name: serializer.normalizer }
and example for your controller:
/**
* Поиск сущности по ИД объекта и ИД языка
* #Route("/search/", name="orgunitSearch")
*/
public function orgunitSearchAction()
{
$array = $this->get('request')->query->all();
$entity = $this->getDoctrine()
->getRepository('IntranetOrgunitBundle:Orgunit')
->findOneBy($array);
$serializer = $this->get('serializer');
//$json = $serializer->serialize($entity, 'json');
$array = $serializer->normalize($entity);
return new JsonResponse( $array );
}
but the problems with the field type \DateTime will remain.
This is more an update (for Symfony v:2.7+ and JmsSerializer v:0.13.*#dev), so to avoid that Jms tries to load and serialise the whole object graph ( or in case of cyclic relation ..)
Model:
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Exclude;
use JMS\Serializer\Annotation\MaxDepth; /* <=== Required */
/**
* User
*
* #ORM\Table(name="user_table")
///////////////// OTHER Doctrine proprieties //////////////
*/
public class User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="FooBundle\Entity\Game")
* #ORM\JoinColumn(nullable=false)
* #MaxDepth(1)
*/
protected $game;
/*
Other proprieties ....and Getters ans setters
......................
......................
*/
Inside an Action:
use JMS\Serializer\SerializationContext;
/* Necessary include to enbale max depth */
$users = $this
->getDoctrine()
->getManager()
->getRepository("FooBundle:User")
->findAll();
$serializer = $this->container->get('jms_serializer');
$jsonContent = $serializer
->serialize(
$users,
'json',
SerializationContext::create()
->enableMaxDepthChecks()
);
return new Response($jsonContent);
If you are using Symfony 2.7 or above, and don't want to include any additional bundle for serializing, maybe you can follow this way to seialize doctrine entities to json -
In my (common, parent) controller, I have a function that prepares the serializer
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
// -----------------------------
/**
* #return Serializer
*/
protected function _getSerializer()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new ObjectNormalizer($classMetadataFactory);
return new Serializer([$normalizer], [new JsonEncoder()]);
}
Then use it to serialize Entities to JSON
$this->_getSerializer()->normalize($anEntity, 'json');
$this->_getSerializer()->normalize($arrayOfEntities, 'json');
Done!
But you may need some fine tuning. For example -
If your entities have circular reference, check how to handle it.
If you want to ignore some properties, can do it
Even better, you can serialize only selective attributes.
When you need to create a lot of REST API endpoints on Symfony,
the best way is to use the following stack of bundles:
JMSSerializerBundle for the serialization of Doctrine entities
FOSRestBundle bundle for response view listener. Also, it can generate definitions of routes based on controller/action name.
NelmioApiDocBundle to auto-generate online documentation and Sandbox(which allows testing endpoint without any external tool).
When you configure everything properly, you entity code will look like this:
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* #ORM\Table(name="company")
*/
class Company
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*
* #JMS\Expose()
* #JMS\SerializedName("name")
* #JMS\Groups({"company_overview"})
*/
private $name;
/**
* #var Campaign[]
*
* #ORM\OneToMany(targetEntity="Campaign", mappedBy="company")
*
* #JMS\Expose()
* #JMS\SerializedName("campaigns")
* #JMS\Groups({"campaign_overview"})
*/
private $campaigns;
}
Then, code in controller:
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\View;
class CompanyController extends Controller
{
/**
* Retrieve all companies
*
* #View(serializerGroups={"company_overview"})
* #ApiDoc()
*
* #return Company[]
*/
public function cgetAction()
{
return $this->getDoctrine()->getRepository(Company::class)->findAll();
}
}
The benefits of such a set up are:
#JMS\Expose() annotations in the entity can be added to simple fields, and to any type of relations. Also, there is the possibility to expose the result of some method execution (use annotation #JMS\VirtualProperty() for that)
With serialization groups, we can control exposed fields in different situations.
Controllers are very simple. The action method can directly return an entity or array of entities, and they will be automatically serialized.
And #ApiDoc() allows testing the endpoint directly from the browser, without any REST client or JavaScript code
Now you can also use Doctrine ORM Transformations to convert entities to nested arrays of scalars and back
The accepted answer is correct but if You'll need to serialize a filtered subset of an Entity , json_encode is enough:
Consider this example:
class FileTypeRepository extends ServiceEntityRepository
{
const ALIAS = 'ft';
const SHORT_LIST = 'ft.name name';
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, FileType::class);
}
public function getAllJsonFileTypes()
{
return json_encode($this->getAllFileTypes());
}
/**
* #return array
*/
public function getAllFileTypes()
{
$query = $this->createQueryBuilder(self::ALIAS);
$query->select(self::SHORT_LIST);
return $query->getQuery()->getResult();
}
}
/** THIS IS ENOUGH TO SERIALIZE AN ARRAY OF ENTITIES SINCE the doctrine SELECT will remove complex data structures from the entities itself **/
json_encode($this->getAllFileTypes());
Short note: Tested at least on Symfony 5.1

Resources