ApiPlatform API docs: how to create areas like in NelmioApiDocs? - api-platform.com

I'd like to create "area"s in docs generated by ApiPlatform.
Something like NelmioApiDocs allow to do (https://symfony.com/bundles/NelmioApiDocBundle/current/areas.html)
Is it possible in ApiPlatform? How
If not, is there any other concept that can be used to make the documentation more readable when there are many endpoints?

As far as I know, there is no mechanism in the API Platform that can handle multiple documentation. Although installing independently NelmioApiDocBundle can be a workaround for you. If configured properly you can create areas that are separate from API Platform autogenerated documentation.
There is only one problem with this solution. If you are using API Platform for configured areas documentation is duplicated. Endpoints that are supposed to be only in doc bundle are also in swagger generated by API Platform. To fix that problem you can decorate API platform swagger normalizer:
final class HideCoreDocsNormalizer implements NormalizerInterface {
private const DOCS_PATHS = [
'/api/foo/docs',
'/api/foo/docs.json',
];
public function __construct(
private RequestStack $requestStack,
) {
}
/**
* #param Documentation $object
* #param array<string, mixed> $context
*
* #return mixed[]|string|int|float|bool|null
*/
public function normalize($object, $format = null, array $context = []): mixed
{
return null;
}
public function supportsNormalization($data, $format = null): bool
{
if ($this->requestStack->getCurrentRequest() === null) {
return false;
}
return in_array($this->requestStack->getCurrentRequest()->getPathInfo(), self::DOCS_PATHS);
}
}
And in configuration file:
Foo\Bar\Serializer\HideDocsNormalizer:
decorates: 'api_platform.swagger.normalizer.api_gateway'
arguments: [ '#request_stack' ]
autoconfigure: false

Related

Cannot declare class Spatie\MediaLibrary\UrlGenerator\GcsUrlGenerator because the name is already in use

I'm having a problem with Spatie Media Library. I created my class to use a different filesystem (specifically a Google bucket). Everything works smooth and I can integrate the filesystem correctly, save and view through the custom url. I created my class and gave what "Spatie" describes in its documentation as a namespace namespace Spatie\MediaLibrary\UrlGenerator;
. However, when I run the "artisan config: cache" command I get the error mentioned above.
Here my Custom Class Code extending BaseUrlGenerator:
namespace Spatie\MediaLibrary\UrlGenerator;
use DateTimeInterface;
use Illuminate\Contracts\Config\Repository as Config;
use Illuminate\Filesystem\FilesystemManager;
class GcsUrlGenerator extends BaseUrlGenerator
{
/** #var \Illuminate\Filesystem\FilesystemManager */
protected $filesystemManager;
public function __construct(Config $config, FilesystemManager $filesystemManager)
{
$this->filesystemManager = $filesystemManager;
parent::__construct($config);
}
/**
* Get the url for a media item.
*
* #return string
*/
public function getUrl(): string
{
$url = $this->getPathRelativeToRoot();
if ($root = config('filesystems.disks.'.$this->media->disk.'.root')) {
$url = $root.'/'.$url;
}
$url = $this->rawUrlEncodeFilename($url);
$url = $this->versionUrl($url);
return config('medialibrary.gcs.domain').'/'.$url;
}
/**
* Get the temporary url for a media item.
*
* #param \DateTimeInterface $expiration
* #param array $options
*
* #return string
*/
public function getTemporaryUrl(DateTimeInterface $expiration, array $options = []): string
{
return $this
->filesystemManager
->disk($this->media->disk)
->temporaryUrl($this->getPath(), $expiration, $options);
}
/**
* Get the url for the profile of a media item.
*
* #return string
*/
public function getPath(): string
{
return $this->getPathRelativeToRoot();
}
/**
* Get the url to the directory containing responsive images.
*
* #return string
*/
public function getResponsiveImagesDirectoryUrl(): string
{
$url = $this->pathGenerator->getPathForResponsiveImages($this->media);
if ($root = config('filesystems.disks.'.$this->media->disk.'.root')) {
$url = $root.'/'.$url;
}
return config('medialibrary.gcs.domain').'/'.$url;
}
}
Here what I included in the published vendor of medialibrary
'custom_url_generator_class' => \Spatie\MediaLibrary\UrlGenerator\GcsUrlGenerator::class,
What I'm missing here?
Thanks for helping me
According to the documentation you should implement the Spatie\MediaLibrary\UrlGenerator interface, not the namespace. Alternatively you can extend Spatie\MediaLibrary\UrlGenerator\BaseUrlGenerator which implements that interface itself. So the namespace of your custom class should still adhere to default naming, meaning it should have namespacing according to the folder structure and classname so it gets autoloaded properly.

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 swap Laravel implementation with Behat and Mockery

I have a Behat FeatureContext for which I want to swap a Laravel implementation of a given class with a mocked one.
so I have this method, with a #beforeSuite annotation
/**
* #static
* #beforeSuite
*/
public static function mockData()
{
$unitTesting = true;
$testEnvironment = 'acceptance';
$app = require_once __DIR__.'/../../../bootstrap/start.php';
$app->boot();
$fakeDataRetriever = m::mock('My\Data\Api\Retriever');
$fakeData = [
'fake_name' => 'fake_value'
];
$fakeDataRetriever->shouldReceive('getData')->andReturn($fakeData);
$app->instance('My\Data\Api\Retriever', $fakeDataRetriever);
}
So I see the Laravel app and the fake data being swapped, but when I run Behat, it is being ignored, meaning Laravel is using the actual implementation instead of the fake one.
I'm using Laravel 4.2
Does someone know a way to swap Laravel implementations when running Behat?
The reason I need this is because the data is coming from remote API and I want the test to run without hitting the API.
I'm not too familiar with Behat besides what I just read in a quick tutorial to see if I can help found here... http://code.tutsplus.com/tutorials/laravel-bdd-and-you-lets-get-started--cms-22155
It looks like you are creating a new instance of Laravel, setting an instance implementation inside of it, then you are not doing anything with the Laravel instance. What's likely happening next is the testing environment is then going ahead and using its own instance of Laravel to run the tests on.
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use PHPUnit_Framework_Assert as PHPUnit;
use Symfony\Component\DomCrawler\Crawler;
use Illuminate\Foundation\Testing\ApplicationTrait;
/**
* Behat context class.
*/
class LaravelFeatureContext implements SnippetAcceptingContext
{
/**
* Responsible for providing a Laravel app instance.
*/
use ApplicationTrait;
/**
* Initializes context.
*
* Every scenario gets its own context object.
* You can also pass arbitrary arguments to the context constructor through behat.yml.
*/
public function __construct()
{
}
/**
* #BeforeScenario
*/
public function setUp()
{
if ( ! $this->app)
{
$this->refreshApplication();
}
}
/**
* Creates the application.
*
* #return \Symfony\Component\HttpKernel\HttpKernelInterface
*/
public function createApplication()
{
$unitTesting = true;
$testEnvironment = 'testing';
return require __DIR__.'/../../bootstrap/start.php';
}
/**
* #static
* #beforeSuite
*/
public function mockData()
{
$fakeDataRetriever = m::mock('My\Data\Api\Retriever');
$fakeData = [
'fake_name' => 'fake_value'
];
$fakeDataRetriever->shouldReceive('getData')->andReturn($fakeData);
$this->app->instance('My\Data\Api\Retriever', $fakeDataRetriever);
}
}

How to extend the Symfony2 Debug Toolbar with custom data?

I want to extend the Symfony2 Debug Toolbar with my own custom data.
I have a service where I want to log specific method calls and then display them in the web debug toolbar.
I read the cookbook article, but it's not very helpful.
I created my own DataCollector class:
class PermissionDataCollector extends DataCollector
{
private $permissionCalls = array();
private $permissionExtension;
public function __construct(PermissionExtension $permissionExtension)
{
$this->permissionExtension = $permissionExtension;
}
/**
* Collects data for the given Request and Response.
*
* #param Request $request A Request instance
* #param Response $response A Response instance
* #param \Exception $exception An Exception instance
*
* #api
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->permissionCalls = $this->permissionExtension->getPermissionCalls();
$this->data = array(
'calls' => $this->permissionCalls
);
}
public function getPermissionCallsCount()
{
return count($this->permissionCalls);
}
public function getFailedPermissionCallsCount()
{
return count(array_filter($this->permissionCalls, array(&$this, "filterForFailedPermissionCalls")));
}
private function filterForFailedPermissionCalls($var)
{
return $var['success'];
}
/**
* Returns the name of the collector.
*
* #return string The collector name
*
* #api
*/
public function getName()
{
return 'permission';
}
}
The PermissionExtension logs all calls and then I want to retrieve this array of calls in
PermissionDataCollector.
And a template just outputting {{ collector.permissionCallsCount }}.
The section gets displayed in the in the toolbar but it just shows a 0 which is wrong.
I'm not sure if I'm even doing this right, because the documentation lacks this section. I'm using Symfony 2.1
Has anybody extended the toolbar with custom data?
ah great! It works. I basically need to refer to $this->data all the time.
The reason for this that ->data is used by the Symfony\Component\HttpKernel\DataCollector\DataCollector and serialized (see DataCollector::serialize).
This is later stored (somehow, I don't know where, but it is later unserialized). If you use own properties the DataCollector::unserialize just prunes your data.
See https://symfony.com/doc/current/profiler/data_collector.html#creating-a-custom-data-collector
As the profiler serializes data collector instances, you should not store objects that cannot be serialized (like PDO objects) or you need to provide your own serialize() method.
Just use $this->data all the time, or implement your own \Serializable serializing.

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