I am trying to create a simple calendar using Symfony. For this I want to request the events of the selected month via AJAX. Each event can have multiple users connected to it.
Now retrieving the event is quite simple
$events = $this->em->getRepository('AppBundle:Event')
->findByYearMonth($this->getUser(), $year, $month);
$encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceHandler(function ($object) {
return $object->getId();
});
$serializer = new Serializer([$normalizer], [$encoder]);
$serialized = $serializer->serialize($events, 'json');
$response = new Response($serialized);
$response->headers->set('Content-Type', 'application/json');
I needed to setCircularReferenceHandler because obviously users have the events connected to them as well.
In my test I currently have only 2 events, yet I get a response of ~50kb, simply because it gets bloated by the user objects. All I really want is user ID and name, but it retrieves all other fields (and other connected entities).
The circular reference handler itself only gets active once the user is already returned (and catches the event within the user that originally retrieved the user).
I have already searched about this but did not find a single result that went in the way I wanted to (most were about forms, general serialization, etc).
The only idea I had was adding a method to the Event entity, eg getSimple where I manually set some parameters and leave outers out (but have to do so for everything connected as well). This seemed quite unclean, hence I am asking here.
TL;DR
I want something like
[
{"id":1, "name":"foo", "users":[
{"id":1, "name":hans"},
{"id":2, "name":"jack"},...
]},...
]
but I get something like
[
{"id":1, "name":"foo", "users":[
{"id":1, "name":hans", "events":[1,{"id":3, "name":"this hasnt already been shown"}], "userfield":"var", "anotheruserfield":"bar", "someOtherRelation":{"bla"},
{"id":2, "name":"jack", "events":[1,{"id":3, "name":"this hasnt already been shown"}], "userfield":"var", "anotheruserfield":"bar", "someOtherRelation":{"bla"}},...
]},...
]
If I correctly understand, you want to exclude the events property (and maybe others) from your objects serialization.
The JMSSerializer would fits your needs as it allows to exclude/expose properties and/or limit their depth.
The bundle is mentioned at the end of the Serialization chapter of the Symfony documentation.
See exclusion strategies and its #Exclude, #Expose, #Groups and #MaxDepth annotations.
Yes, JMSSerializer provides Exclude and other methods which might solve this, but I decided for something different: Writing a custom EventNormalizer which does exactly what I want and doesn't change the other entities per se.
$encoder = new JsonEncoder();
$normalizer = new \AppBundle\Serializer\Normalizer\EventNormalizer();
$serializer = new Serializer([$normalizer], [$encoder]);
$serialized = $serializer->serialize($events, 'json');
And the normalizer
namespace AppBundle\Serializer\Normalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Event normalizer
*/
class EventNormalizer implements NormalizerInterface
{
/**
* {#inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
$result = /* using fields from $object */
return $result;
}
/**
* {#inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \AppBundle\Entity\Event;
}
}
Related
We use the Symfony Serializer to convert some Doctrine objects to JSON, goal is to provide them as results of API calls.
Out data model has about thirty classes, all are somehow linked with each other and the Doctrine model reflects this. So navigation from one instance to other linked instances is easy.
Now, we are pretty happy that no changes are necessary when we add a new attribute to a class. However, the situation is different is a new link is added, this often adds way too much information because the linked classes also have links and they have links and arrays of objects...
To restrict this, we typically add ignored attributes:
protected function serialize($e, $ignored = ['user'])
{
if ($this->container->has('serializer')) {
$this->serializer = $this->container->get('serializer');
} else {
throw new ServiceNotFoundException('Serializer not found');
}
return $this->serializer->serialize($e, 'json', [
'ignored_attributes' => $ignored,
ObjectNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) {
if (method_exists($object, 'getUuid')) {
return $object->getUuid();
} elseif (method_exists($object, 'getId')) {
return $object->getId();
} elseif (method_exists($object, '__toString')) {
return $object->__toString();
} else {
return '[Reference to object of type ' . get_class($object) . ']';
}
},
]);
}
And then:
return new Response($this->serialize(
[
'presentation' => $presentation,
],
[
'zoomUser',
'userExcelDownloads',
'presentationUserTopics',
'addedBy',
'user',
'presentations',
'sponsorScopes',
'sponsorPresentations',
'showroomScope',
'presentationsForTopic',
'presentationsForTopics',
'presentationHistories',
'showroomTopics',
'presentation',
'presentationHistory',
]
));
This works but maintenance is horrible - when ever the database model is changed, there is the risk that an API call emits a few more MB because a new attribute would have to be ignored. There is no way of finding this.
So how do you handle this?
One solution could be to restrict the serialization depth similar to the CIRCULAR_REFERENCE_HANDLER, i.e., for objects on level three just add the IDs and not the full objects. How could I build this?
Symfony serializer has built-in Ignore strategy (https://symfony.com/doc/current/components/serializer.html#ignoring-attributes)
you can ignore the attribute directly from the model.
use Symfony\Component\Serializer\Annotation\Ignore;
class Presentation
{
/**
* #Ignore()
*/
public $zoomUser;
//...
}
or by using context.
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
$normalizer = new ObjectNormalizer();
$encoder = new JsonEncoder();
$serializer = new Serializer([$normalizer], [$encoder]);
$serializer->serialize($presentation, 'json', [AbstractNormalizer::IGNORED_ATTRIBUTES => ['zoomUser']]);
We switched to JMS Serializer Bundle where setting the max. depth is very simple and helps us a lot.
https://jmsyst.com/bundles/JMSSerializerBundle
For Symfony serializer, the only way is to use serialization groups.
First off, I am building using Symfony components. I am using 3.4. I was following the form tutorial https://symfony.com/doc/3.4/components/form.html which lead me to this page
https://symfony.com/doc/current/forms.html#usage
I noticed that Symfony added a Form directory to my application.
This was great! I thought I was on my way. So, in my controller, I added this line.
$form = Forms::createFormFactory();
When I tried loading the page, everything went well with no error messages until I added the next two lines.
->addExtension(new HttpFoundationExtension())
->getForm();
I removed the ->addExtension(new HttpFoundationExtension()) line and left the ->getForm() thinking it would process without the add method call. It did not. So, I backed up to see if the IDE would type hint for me.
In the IDE PHPStorm, these are the methods that I have access to but not getForm per the tutorial
Every tutorial I have tried ends with not being able to find some method that does not exist. What do I need to install in order to have access to the ->getForm() method?
UPDATE:
I have made a couple of steps forward.
$form = Forms::createFormFactory()
->createBuilder(TaskType::class);
The code above loads with no errors. (Why is still fuzzy). But next stop is the createView(). None existant also. I only get hinted with create().
Reading between the lines in this video help with the last two steps. https://symfonycasts.com/screencast/symfony3-forms/render-form-bootstrap#play
UPDATE 2:
This is what I have now.
$session = new Session();
$csrfManager = new CsrfTokenManager();
$help = new \Twig_ExtensionInterface();
$formFactory = Forms::createFormFactoryBuilder()
->getFormFactory();
$form = $formFactory->createBuilder(TaskType::class)
->getForm();
//$form->handleRequest();
$loader = new FilesystemLoader('../../templates/billing');
$twig = new Environment($loader, [
'debug' => true,
]);
$twig->addExtension(new HeaderExtension());
$twig->addExtension(new DebugExtension());
$twig->addExtension($help, FormRendererEngineInterface::class);
return $twig->render('requeueCharge.html.twig', [
'payments' => 'Charge',
'reportForm' => $form->createView()
]);
Does anyone know of an update standalone for example? The one that everyone keeps pointing two is 6 years old. There have been many things deprecated in that time period. So, it is not an example to follow.
Your Form class and method createFormFactory must return object that implement FormBuilderInterface then getForm method will be available. You need create formBuilder object.
But this can't be called from static method because formBuilder object need dependency from DI container. Look at controller.
If you want you need register your own class in DI and create formBuilder object with dependencies and return that instance of object.
EDIT
You don't need to use abstract controller. You can create your own class which is registered in DI for geting dependencies. In that class you create method which create new FormBuilder(..dependencies from DI from your class ...) and return instance of that FormBuilder. Then you can inject your class in controller via DI.
Example (not tested)
// class registered in DI
class CustomFormFactory
{
private $_factory;
private $_dispatcher;
public CustomFormFactory(EventDispatcherInterface $dispatcher, FormFactoryInterface $factory)
{
$_dispatcher = $dispatcher;
$_factory = $factory;
}
public function createForm(?string $name, ?string $dataClass, array $options = []): FormBuilderInterface
{
// create instance in combination with DI dependencies (factory..) and your parameters
return new FormBuilder($name, $dataClass, $_dispatcher, $_factory, $options);
}
}
Usage
$factory = $this->container->get('CustomFormFactory');
$fb = $factory->createForm();
$form = $fb->getForm();
I am making a REST API that will return different JSON responses depending on which type of User is making the call.
There is a single endpoint: example.com/api/v1/collect that uses Laravel's API authentication to get the User model with $user = auth()->guard('api')->user();.
Each User will belong to a Type.
If User 1 (type_id 1) makes the call, the response will look like:
{
"example": 1,
"success": true,
"data" : [
...
]
}
If User 2 (type_id 2) makes the call, the response can be different, depending on the user's type. It could look like:
{
"example": 2,
"response" : [
...
],
"custom": "string",
"success": 200
}
The ... is the data that we are sending back (for example a list of Post titles) and it will always be the same, but the "envelope" (or wrapper) around it would be specific to each user (or type of user).
So far, I've found two solutions to wrap that ... in an abstracted way:
Solution 1: Using Laravel Blade
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different blade filename
// There could be around a 100 types which will result in a 100 blade files
// The filename is stored in the database
$filename = $user->type->filename; // returns 'customBladeTemplate'
// Return a JSON response after passing the $data to the view
return response()->json([
view($filename, compact('data'))->render(),
]);
Using a blade file for each type of user allows me to wrap the data like this:
// resources/views/customBladeTemplate.blade.php
// This filename has to match the one in the database column
{
"example": 1,
"success": true,
"data" : [
{!! $data !!}
]
}
That will output a JSON response for the User 1 (example 1)
Solution 2: Using Laravel response macros
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different macro name
// There could be around a 100 types which will result in a 100 different macros
// The macro name is stored in the database
$macroName = $user->type->macro_name; // returns 'customMacroName'
return response()->{macroName}($data);
Creating a Macro for each type of user, using the macro name from the DB:
// App\Providers\AppServiceProvider.php
use Illuminate\Http\Response;
public function boot()
{
Response::macro('customMacroName', function ($data) {
return Response::json([
'example' => 2,
'response' => $data,
'custom' => 'string',
'success' => 200,
]);
});
}
That macro will output a JSON response for the User 2 (example 2)
Both options work fine but I am still wondering:
Is there another (possibly better) way to do it?
Are those two solutions valid or can they be enhanced?
Which of those two solutions seem to be better and why?
Edit: The $data is not actually coming from an eloquent model, it is rather from a serialized JSON column (JSON casting) - which means I can't use the Laravel API resources
If you are looking for the response formatting you should go with the Laravel API Resources
Based on your requirement(data formate different for two type of users), you can create two different Api Resource classes.
AdminResource & UserResource.
Here you have more flixibility on controlling fields or orgnizing data.
Here is how you can define the resource class:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UserResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
And you can use this as:
use App\User;
use App\Http\Resources\UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
If you would like to include with condition check in with user type, you can create a common function called renderJson($userType, $data) and place this in your parent class or can wrap with traits, all depends on your application architecture.
Here you can find laravel documentation for API Resource: https://laravel.com/docs/5.8/eloquent-resources
Edited:
With Laravel API Resource, you not only parse the modal object, you can parse any arrayble object.
Essentially they are just simple objects with one very important job
to do — transform your objects (interesting I said objects and not
models). To do this out of the box, all you have to do is instantiate
the Resource (collection or individual) with an Arrayable object. If
you did nothing else but generate a standard Resource and pass in an
Arrayable object the Resource would transform that object
automatically, and because Models are Arrayable this is where I got
caught out because if you create a resource collection and instantiate
it with a collection of models then the models get toArray'd and not
their corresponding resource.
Src: https://medium.com/#dinotedesco/laravel-api-resources-what-if-you-want-to-manipulate-your-models-before-transformation-8982846ad22c
So in your case if you can just collect() the json data and pass to api resource.
You could use middlewares to change what the response looks like.
With middleware you could change the response after the execution of your regular code, without having to take this into account in the controller itself. Using the below code you modify the response AFTER it has been executed.
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
// Calls the controller and processes the request.
$response = $next($request);
// Here you can retrieve the user and modify the response however you want.
// Some example code:
$user = Auth::user();
if ($user->type == 1) {
... //Change response for user type 1
}
if ($user->type == 2) {
... //Change response for user type 2
}
// Etc...
return $response;
}
}
Reference: https://laravel.com/docs/5.8/middleware
Depends on how different the responses are from each other. I'm inclined to take inventory of each type's common features and build a response array as appropriate. This could be done in the controller or a helper function and then returned using Laravel's JSON response type.
$response = [];
// results common to all types
$response['example'] = $example;
$response['success'] = $success;
// customized results for specific types
if (in_array($type, [1, 3, 4, 5, ...])) {
$response['data'] = $dotdotdot;
}
if (in_array($type, [2, 6, 7, 8, ...])) {
$response['response'] = $dotdotdot;
$response['custom'] = $custom;
}
return response()->json($response);
I don't know if this is what you are looking for. I had something similiar a few months ago and fixed it with json files. As json is amazingly fast and you can create thousands of types.
Sorry for my bad english i will fix it after the weekend :-)
Let's get started.
First the user logs in using laravel passport or api routes.
Second the api calls a controller.(class). I will create a class based on your info.
let's say the api calls the ApiController and the method handle
use Illuminate\Http\Request;
class ApiController
{
public function __construct()
{
}
/**
* Handle the incoming request
*
* #param Request $request
*/
public function handle(Request $request)
{
//first let's find the correct format
$type = $requets->user()->type; //lets say type_1
$config = $this->getUserType($type);
//i don't know where you data comes from but let's say $data is your data.
$data = json_encode(['some' => "data", 'to' => "return"]);
//lets fill the data
$response = $this->fillDataInConfig($data, $config);
return response()->json($response);
}
/**
* Find the user type config by type name
*
* #param string $type
* #return object
*/
private function getUserType(string $type) : string
{
//let store them in the local storage
$config = \Storage::disk('local')->get("api_types/$type.json");
return json_decode($config);
}
/**
* Fill the data
*
* #param mixed $data
* #param object $config
* #return object
*/
private function fillDataInConfig($data, object $config) : object
{
//as for your example. The reusl//
// {
// "example": 2,
// "response" : *responseData*, <===== where the response should be
// "custom": "string",
// "success": 200
// }
foreach($config as $index => $value){
if($value === '*responseData*'){
$config->{$idnex} = $data;
}
}
//the data is filled in the response index
return $config;
}
}
I started using zend 3 a few months ago for a project and now I'm stuck.
I have a customized authentication (not using zend authentication module) which is working fine but I need to validate every time i access a redirected page.
Because on every page's url goes a token that is used to check in database, and I'm trying to do inside the function onBootStrap().
I learned to use factories, models, mappers and I'm currently using them in some controllers, but i can't find a way to achieve, at least if i could get the dbAdapter from the bootstrap event to use, it will be enough.
Any thoughts?
You can use lazy event for this job. here's you are a simple example.
public function onBootstrap(EventInterface $e)
{
/** #var \Interop\Container\ContainerInterface $container */
$container = $e->getApplication()->getserviceManager();
$events = $e->getApplication()->getEventManager();
$events->attach(MvcEvent::EVENT_ROUTE, new LazyListener([
'listener' => Listener::class,
'method' => 'onRoute'
], $container));
}
So you can check you auth on Listener class' "onRoute" method. If MvcEvent::ROUTE event is too early for you, you can use other MvcEvents too.
Hope this can solve your problem.
To get the adapter and use it in tablegateway
public function onBootstrap(EventInterface $event){
$container = $event->getApplication()->getServiceManager();
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new TableObjectClass());
$tableGateway = new TableGateway("Table name", $dbAdapter, null, $resultSetPrototype);
$mapper = new TableObjectClassMapper($tableGateway);
//and use $mapper to get data from the table and store it as TableObjectClass
$data = $mapper->fetch()->current();
}
I am trying to add some validation to my entity, using the Symfony validation component, i have added some constraints to my User Entity.
/**
* #param ClassMetadata $metadata
*/
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('username', new Assert\NotBlank);
$metadata->addPropertyConstraint('password', new Assert\NotBlank);
$metadata->addPropertyConstraint('first_name', new Assert\NotBlank);
$metadata->addPropertyConstraint('last_name', new Assert\NotBlank);
}
Now i want to test if i get some errors, when violating the constraints, this is done like this.
$user = new User();
$user->username = '';
$user->password = '';
$validator = Validation::createValidator();
if (0 < count($validator->validate($user))) {
throw new \RuntimeException('The given user is invalid');
}
But the count is zero, which is odd, as all the constraints is clearly violated? Am i missing something here? Well i must be :D.
Might be worth to notice, that my application is not a Symfony application; it's a ordinary php application, i am just using the component.
You need to specify which methods act as mapping methods:
$validator = Validation::createValidatorBuilder()
->addMethodMapping('loadValidatorMetadata')
->getValidator()