I am using PHP league's Fractal as the transformer for my API. However, I think I must be doing something wrong as the item transformer wraps everything in an array like it would a collection which is against the JSON API standard I believe.
So for a user with ID of one I get something like this:
{
"users":[
{
"id":1,
"firstName":"Jacob",
"surname":"Windsor",
}
]
}
When surely it should be this?
{
"users":
{
"id":1,
"firstName":"Jacob",
"surname":"Windsor",
}
}
I am using ember.js and this is causing problems with naming conventions.
I am using Laravel and in my userController I have something like this:
public function show($id)
{
$user = User::find($id);
return $this->respondItem($user);
}
Then in the apiController that everything extends from:
public function respond($response, $status = 200){
return Response::make($response, $status);
}
public function respondTransform($resource){
$fractal = new Fractal\Manager();
$fractal->setSerializer(new JsonApiSerializer());
return $this->respond($fractal->createData($resource)->toJson());
}
public function respondItem($data, $transformer = null, $namespace = null){
! isset($transformer) ? $transformer = $this->transformer : $transformer = $transformer;
! isset($namespace) ? $namespace = $this->namespace : $namespace = $namespace;
$resource = new Item($data, $transformer, $namespace);
return $this->respondTransform($resource);
}
I must be doing something wrong. The fractal docs have no examples specifically for items only collections so I am unsure what I have done.
So it seems that Fractal doesn't quite obey ember-data's conventions which is an annoying problem but very easily overcome using custom serialziers.
I have a psr-4 autoloaded file named CustomJsonSerializer which I have included in my ApiController class. If you follow the article on php league's site (posted above) its fairly easy to do. I have these two methods.
public function collection($resourceKey, array $data)
{
return array($resourceKey ?: 'data' => $data);
}
/**
* Serialize an item resource
*
* #param string $resourceKey
* #param array $data
*
* #return array
*/
public function item($resourceKey, array $data)
{
return [$resourceKey => $data];
}
You can see that the collection is responding as it normally would, i.e I haven't changed it. But the item method just responds without the extra array. Simple! You have to include all the other methods as well and I haven't got round to sorting out pagination but it should be fairly simple.
I hope this helps anyone wanting to use ember-data with Fractal. I highly recommend it, fractal has made my life so much easier. You could build transformers yourself but it makes it so much easier and more easily modified in the future.
Edit:
Please make sure you keep the $resourceKey in both the methods. You need to be using it and setting it when calling the transformer. |Ember-data requires a resource key.
Assuming your userController extends ApiController, you could simply do:
public function show($id)
{
$user = User::findOrFail($id);
return $this->setStatusCode(200)->withItem($user, new UserTransformer);
}
You do need to implement the UserTransformer class. If you need help with that, let me know in the comments.
I actually found that a much simpler adjustment of JsonApiSerializer did what I needed for Ember:
(I just took out the count($data) check)
<?php
namespace Acme\Serializer;
use RuntimeException;
use League\Fractal\Serializer\JsonApiSerializer;
class EmberSerializer extends JsonApiSerializer
{
/**
* Serialize the top level data.
*
* #param string $resourceKey
* #param array $data
*
* #return array
*/
public function serializeData($resourceKey, array $data)
{
if (! $resourceKey) {
throw new RuntimeException('The $resourceKey parameter must be provided when using '.__CLASS__);
}
return array($resourceKey => $data);
}
}
Related
The attributes of laravel modal are named using underscore (_), for example :
first_name
but attributes of javascript objects are named with camelCase:
{ firstName: "..." }
And this presents a conflict, is there a solution to resolve it ?
Try to use Laravel eloquent resource pattern will do that for You.
Check this helpful documentation.
https://laravel.com/docs/8.x/eloquent-resources
Like Zrelli Mjdi mentioned it's done with Resource Collections.
I did not find a way to let this resources transform the result recursively for nested JSON-Objects, so I created a middleware (see the github-gist) for this, which should take a rather heavy toll on performance. So use it sparsely.
I'd use this middleware only temporary if your frontend demands camel-case properties. In the long run I'd modify my migrations to use camel-case fieldnames. This should, according to this reddit-thread, be possible and won't affect performance like my middleware.
Edit: The code in the gist had a bug which is now fixed.
This is about how it's done with Resource-Collections and non-nested JSON-Results:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class MyResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'userId' => $this->user_id,
'createdAt' => $this->created_at,
];
}
}
in the controller:
public function myControllerMethod(Request $request)
{
// ...
return MyResource::collection($logs)
}
I want to control the same user access on some methods in my controller.
Currently, I use this :
$this->denyAccessUnlessGranted('ACCESS', $this->Player($toolRepository));
However I am forced to use this line and inject the ToolRepository into each method ...
What would be the easiest way to do it?
I saw that there was the IsGranted annotation but my subject needs to be a doctrine object to control access with my Vote.
/**
* #Route("/player")
*/
class PlayerController extends AbstractController
{
/**
* #Route("/", name="player")
* #throws Exception
*/
public function Player(ToolRepository $toolRepository): \App\Entity\Tool
{
$playerTool = 'TestTool2';
$tool = $toolRepository->findOneBy(array('libelle' => $playerTool));
if (!$tool) {
throw new Exception('Tool : ' . $playerTool . 'not found!');
}
return $tool;
}
/**
* #Route("/main", name="player")
* #IsGranted ("ACCESS", subject="tool")
* #throws Exception
*/
public function mainPlayer(PlayerRepository $playerRepository, ToolRepository $toolRepository): Response
{
$this->denyAccessUnlessGranted('ACCESS', $this->Player($toolRepository));
$players = $playerRepository->findAll();
return $this->render('player/player_mainpage.html.twig', ['players'=>$players]);
}
}
I think this ressource should answer you: Symfony voters.
You'll put your security logic in your custom voter which will be called in every function of your controller (or every methods where you want to control access) isGranted() function.
Calling your Player() function is a easier way to do this for beginner, you can keep like that but you shouldn't put it in Controller and use a Service instead .
Edit:
You may store your ToolRepository as Controller private property and inject it in a __construct() method so you don't have to inject ToolRepository in each method
So basically, I want to create implement a factory class that will use a couple different models. I don't have a clue as to how I should go about doing this within yii2. Any help would be nice.
Here is a general idea of what I am trying to do.
use app\models\Event;
use app\models\EventParticipant;
use app\models\Match;
/**
* #property Event $Event
* #property EventParticipant $EventParticipant
* #property Match $Match
*/
abstract class Tournament
{
protected $_id;
protected $_event;
protected $_type;
final public function __construct($event) {
$this->Event = new Event();
$this->EventParticipant = new EventParticipant();
$this->Match = new Match();
if(!$event) {
throw new \yii\web\HttpException(400, 'Invalid Event', 405);
}
$this->_id = $event['Event']['id'];
}
}
}
I would avoid throwing Http exceptions in models, use them in Controllers. You can throw InvalidConfigurationException for example, not necessary as you need to have $event.
There are many implementations of Factory design pattern, here is the simplest
class TournamentFactory
{
public static function create(Event $event, EventParticipant $eventParticipant, Match $match) {
return new Tournament($event, $eventParticipant, $match);
}
}
but I don't see it's use in this example. I mostly use it to switch between object types, something like this, in your example:
$grandSlam = TournamentFacory::create('grandSlam');
$grandSlam->setEvent($event);
$grandSlam->setParticipants($participants);
...
$masters = TournamentFacory::create('masters');
...
these objects might have same properties in common, but different implementations. For example masters are played to two winning sets, grand slams on 3...etc...
I have been some time without programing in Synfony and I have some doubts.
Is posible that and Action Controller return a variable (for example and integer) instead of a Response Object or Json Object.
What I need is call a function inside another function in a different Controller. If the 2 functions live in the same Controller it has no problem (like this):
class AController{
public function AAction(){
$var = $this->BAction(); //Do whatever I want with $var
return Response ("Hello");
}
public function BAction(){
return 34; //return an integer instead of a Response
}
}
THE PROBLEM IS when the BAction is in another Controller. If I use a forward, Symfony expect that BAction return a Response object or a Json array, but I only want to return a simple variale.
Is this posible?? Return a simple integer...
Thanks a lot!!
No a Action must return a Response Object. But if you have two controllers (that will say two different classes) then you could create a service.
app/config/config.yml
services:
app.my_ownservice:
class: AppBundle\Services\OwnService
arguments:
entityManager: "#doctrine.orm.entity_manager"
app/Services/OwnService.php
namespace AppBundle\Services;
use Doctrine\ORM\EntityManager;
class OwnService {
/**
*
* #var EntityManager
*/
private $em;
public function __constructor(EntityManager $entityManager)
{
$this->em = $entityManager;
}
public function doSomething(){
// you could use the entitymanager here
return 'Okay i will do something.';
}
}
And from each controller (or whatever) you can do:
$myOwnService = $this->get('app.my_ownservice');
$text = $myOwnService->doSomething();
// echo $text;
A controller should never use another controllers action. Thats not the problem that Controllers solve. Symfony business logic structure is SOA based. (https://en.wikipedia.org/wiki/Service-oriented_architecture) Therefore for custom business logic you should always use either:
Services: http://symfony.com/doc/current/book/service_container.html
Events: http://symfony.com/doc/current/components/event_dispatcher/introduction.html
I have a little problem, I'm trying to send a json response, but all I get is a empty object all the time.
So here is my code:
//Get the data from DB
$template = $this->getDoctrine()
->getRepository('EVRYgroBundle:Template')
->findOneBy(
array('active' => 1)
);
if (!$template) {
throw $this->createNotFoundException(
'No product found for id '
);
}
//Send the response
$response = new Response();
$response->setContent(json_encode($template));
return $response;
And when I'm viewing it all it shows is {}
And I have also tried with the jsonResponse and with this code:
$response = new JsonResponse();
$response->setData($template);
And I have no idea what i'm doing wrong!
json_encode expects an array as first parameter to be given in. When you call it with an object the public properties will may be displayed. To keep the properties protected (as they should be) you can add a expose function to your entity:
/**
* delivers all properties and values of the entity easily
*
* #return array
*/
public function expose()
{
return get_object_vars($this);
}
and then call
$response->setData(json_encode($template->expose()));
This way you keep your entity clean with only access via getter and setter methods and you can access all properties via json still.
Well I found the problem, the problem was that some variables that holds the information from the db was set to protected and not public.