Why or when to use Laravel resources/collections - laravel

I do not understand why or when to use the resources in Laravel https://laravel.com/docs/7.x/eloquent-resources .
See this controller :
public function show(School $school)
{
// return response()->json($school, 200);
return new SchoolResource($school);
}
The both return solutions returned this kind of response :
{
"data": {
"id": "4f390a7b-3c3f-4c23-9e6a-dd4429cf835d",
"name": "school name",
.......,
The data are the results of a query automatically injected (here : $school).
And same question for a collection of resources. Imagine this controller :
public function index(Request $request)
{
try {
$schools = $this->schoolRepository->all($request->all());
} catch (\Exception $e) {
return response()->json(['message' => 'Bad request'], 400);
}
return SchoolResource::collection($schools);
// return response()->json($schools, 200);
}
If I need to add some fields , I can do that either in the model or in the repository.
I often read that this resource notion is important to understand and to use. But for the moment I do not see when or why I should use it. I certainly must not understand something!

There are a couple of primary reasons to use resources to manage your return values even if your resources don't do anything than pass data through today, you may want it to do something different in the future.
A small list of reasons why resources are really useful
1. Data manipulation, specific to clients (i.e. js applications consuming your api)
If you start manipulating data in your models using mutators (getters / setters). Your internal application now has to work with these constraints. Many times its easier to work with the raw data internally and then just allow your resources to manipulate the data for the client.
2. Conforming to an API specification, such as JSON API v1.0
Whilst you will likely need logic in your application to handle schemas like this, your models and controllers should not. The resource has a critical role here to organise the data for your consumer applications in a compliant fashion.
3. The age old mantra, separation of concerns
This goes hand in hand with point 1, it is not a model or a controllers responsibility to map data to what you expect your consumer applications to receive.
Building on your example
You currently have the following in the show route of your resource controller.
public function show(School $school)
{
// return response()->json($school, 200);
return new SchoolResource($school);
}
This need not change, even if your API specification does (reason 1 and 3).
Adding fields, yes, you'll need to add them to your model but lets use this to actually do something meaningful with your resource.
We've created a migration for a new JSON field ratings. Ratings has a data structure like this:
[
{
name: string,
value: float,
}
]
For reasons such as media scrutiny, we never want to expose all the rating data to our publically available front end consumer apps. Instead we want to provide an average score of all ratings.
Now we could do this in the model, but is it the models responsibility to do this? Not really, the model should be handling and dealing in raw / discreetly modified data only. So is it the controllers responsibility? No, the controller coordinates what should be done and is not interested in specific details or the data.
So where do we do this? Enter your resource that was handily already set up.
class School extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'rating' => $this->getRating(),
];
}
/**
* Mean rating rounded to one decimal place
* #return float
*/
protected function getRating()
{
return (round(array_reduce($this->ratings, function($acc, $curr) {
$acc += $curr['value'];
return $acc;
}, 0) / count($this->ratings), 1);
}
}
The resource is where we have manipulated data specifically for our responses and left our internal data modelling un-touched and clean without pollution from specific nuances of our consuming applications.
More importantly, if you just returned return response()->json($school, 200); from your controller, your data structures will not match and you would be exposing some sensitive data to your front end applications.
Additional (24/12/21)
It's worth noting that if, for example, the data that you are manipulating is required by many different views / resources then we have a number of options.
Traits, create a trait that adds the getRating method. Downside, every class that needs this must import and declare the trait. Upside, your code is dry.
Model scopes, add a scope to your model that does the data processing via SQL (or your DB QL of choice). Downside, slight pollution of the model. Upside, super quick.
Append the data to the model (https://laravel.com/docs/8.x/eloquent-serialization#appending-values-to-json) using an accessor that runs the getRating code to set the data on the model. Upside, clean and usable throughout the application. Downsides pollutes the model a little and data only available in the JSON representation of the model.
Decorate the resource. This allows you to intercept and modify/add to the result of the toArray method in the decorated resource. Upsides not many. Downsides, obfuscated and confusing implementation detail. I wouldn't recommend this approach.
Helper function. Rather than have the method on the resource, create a helper that takes the ratings array and returns the result. Upside, simple implementation. Downsides, none that I can thing of.
So after thinking about this alot I think that I would likely do what I originally wrote in this answer. If I need to re-use I would likely create a helper. If I was concerned about performance I would use a model scope to do the calculations in SQL (if possible, remember it's a JSON field). Taking a step further, if many models require this logic, a trait for those models would be my next step (this only applies if you go down the SQL calculation route).

Related

API Platform - Which approach should I use for creating custom operation without entity

I'm new to API Platform. I think it's great but I cannot find any example how to create custom endpoint that isn't based on any entity. There are a lot of examples based on an entity and usually they are all about CRUD. But what about custom operations?
I need to create custom search through database with some custom parameters which aren't related to any entity.
E.g. I want to receive POST request something like this:
{
"from": "Paris",
"to": "Berlin"
}
This data isn't saved to db and I haven't entity for it.
After I receive this data, there should be a lot of business logic including db queries through a lot of db tables and also getting data from external sources.
Then, after the business logic is finished, I want to return back result which is also custom and isn't related to any entity.
E.g.
{
"flights": [/* a lot of json data*/],
"airports": [/* a lot of json data*/],
"cities": [/* a lot of json data*/],
.......
}
So, I think I'm not the only on who does something similar. But I really cannot find a solution or best practices how to do this.
In the documentation I've found at least three approaches and I cannot implement none of them.
The best one, I guess the most suitable for me it is using Custom Operations and Controllers. But documentation says this one is not recommended. Also I think I should use DTOs for request and response, but for this approach I'm not sure I can use them.
The second one I found it's using Data Transfer Objects, but this approach requires an entity. According to the documentation, I should use DTOs and DataTransformers to convert DTO to an Entity. But I don't need entity, I don't need save it to db. I want just handle received DTO on my own.
The third one I guess it is using Data Providers, but I'm not sure it is suitable for my requirements.
So, the main question is which approach or best practice should I use to implement custom operation which isn't related to any entity. And it will be great use DTOs for request and response.
You are not forced to use entities. Classes that are marked with #ApiResource annotation may not be entities. Actually, if your application is smarter than basic CRUD you should avoid marking entities as ApiResource.
Since you want to use POST HTTP method (which is for creating resource items) you can do something like this.
1) Define class describing search fields and which will be your #ApiResource
<?php
// src/ApiResource/Search.php
namespace App\ApiResource;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Action\NotFoundAction;
use ApiPlatform\Core\Annotation\ApiProperty;
use App\Dto\SearchResult;
/**
* #ApiResource(
* itemOperations={
* "get"={
* "controller"=NotFoundAction::class,
* "read"=true,
* "output"=false,
* },
* },
* output=SearchResult::class
* )
*/
class Search
{
/**
* #var string
* #ApiProperty(identifier=true)
*/
public $from;
/** #var string */
public $to;
}
2) Define DTO that will represent the output
<?php
// src/Dto/SearchResult.php
namespace App\Dto;
class SearchResult
{
public $flights;
public $airports;
public $cities;
}
3) Create class that will inplement DataPersisterInterface for handling business logic.
It will be called by framework because you make POST request.
<?php
// src/DataPersister/SearchService.php
declare(strict_types=1);
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Dto\SearchResult;
use App\ApiResource\Search;
final class SearchService implements DataPersisterInterface
{
public function supports($data): bool
{
return $data instanceof Search;
}
public function persist($data)
{
// here you have access to your request via $data
$output = new SearchResult();
$output->flights = ['a lot of json data'];
$output->airports = ['a lot of json data'];
$output->cities = ['inputData' => $data];
return $output;
}
public function remove($data)
{
// this method just need to be presented
}
}
That way you will recieve results based on request.

Passing computed parameters to middleware

I would like to pass a computed value into my middleware through my web.php routes file. I have this model configuration:
Planet hasMany Countries
Country hasMany Cities
City hasMany Buildings
In all of my routes I need to build a menu that helps navigate between planets. So, no matter what I'm doing I need to be able to access the planet->id. Right now, in my middleware I have a long series of if/elseif checking to see which parameters exist in the URL:
if (isset($parameters['planet']))
$planetId = $parameters['planet']->id;
else if (isset($parameters['country']))
$planetId = $parameters['country']->planet->id;
else if (isset($parameters['building']))
$planetId = $parameters['building']->country->planet->id;
Obviously this gets very unwieldy very quickly and I feel like there should be a better way to pass the planetId into the middleware but I cannot find any way to gracefully do that...
Thank you very much for your help!
You're better off just adding "planet_id" to all of your models that use their planet reference frequently.
Writing logic with a bunch of nested relationship referencing can really take a toll on the speed of your application. Writing the extra data which seems counter intuitive at first, will actually really help. You want to prioritize the speed of your SELECT queries over the size of your database where you can reasonably. Your SELECT queries will be the majority of your applications database usage. In our modern day having a few extra bytes isn't that big of a deal and it would serve a huge purpose here.
Add planet_id to your countries and buildings migration table.
Add relationship method "planet" to your country and building model class.
Then you can easily do something like:
function getPlanetFromRequest($request)
{
$parameters = $request->all();
$planetParameterNames = ['building', 'country', 'planet'];
foreach ($planetParameterNames as $planetParameterName) {
if (isset($parameters[$planetParameterName])) {
$model = $parameters[$planetParameterName];
// if the model is a planet, just return its self.
if ($model instanceof Planet) {
return $model;
}
// $model would be country or building.
return $model->planet;
}
}
return null;
}
If you only need the planets id, save yourself a query and just reference the models planet_id rather than referencing its planet and then grabbing the id.
$country->planet_id; // NOT $country->planet->id;

Laravel - Retrieve the inverse of a many-to-many polymorphic relation (with pagination)

after some digging I still could not find any solid way to retrieve the inverse of a many-to-many polymorphic relation that allows mixed models results.
Please consider the following:
I have several models that can be "tagged". While it is trivial to retrieve for example $item->tags, $article->tags and the inverse with $tag->articles and $tag->items I have no easy way to do something like $tag->taggables to return both articles and items in the same collection. Things get even bumpier as I need to use pagination/simple pagination to the query.
I have tried a few workarounds but the best I could put together still looks crappy and limited. Basically:
I queried the DB once per "taggable";
put all in a single big collection;
passed the collection to a phpleague/fractal transformer (my API uses it) that returns different json values depending on the parsed models.
The limits of this approach is that building a pagination is a nightmare and fractal "include" options can't be used out of the box.
Can anyone help me? I'm currently using Laravel 5.1.
There is not much magic in my current code. Faking and simplifying it to make it short:
From the api controller:
$tag = Tag::findOrDie($tid);
$articles = $tag->cms_articles()->get();
$categories = $tag->cms_categories()->get();
$items = $tag->items()->simplePaginate($itemsperpage);
$taggables = Collection::make($articles)->merge($categories);
// Push items one by one as pagination would dirt the collection struct.
foreach ($items as $item) {
$taggables->push($item);
}
return $this->respondWithCollection($taggables, new TaggableTransformer);
Note: using simplePaginate() is there only because I would like all articles and categories to be shown on first page load while the number of items are so many that need pagination.
From the Transformer class:
public function transform($taggable)
{
switch (get_class($taggable)) {
case 'App\Item':
$transformer = new ItemTransformer;
break;
case 'App\CmsArticle':
$transformer = new CmsArticleTransformer;
break;
case 'App\CmsCategory':
$transformer = new CmsCategoryTransformer;
break;
}
return $transformer->transform($taggable);
}
Please consider that the other transformers are simply returning arrays of data about the models they correlate with. If you use Fractal you would easily spot that nested "included" models would not be applied.
Nothing fancy for the Tag model:
class Tag extends Model
{
protected $morphClass = 'Tag';
protected $fillable = array('name', 'language_id');
public function cms_articles() {
return $this->morphedByMany('App\CmsArticle', 'taggable');
}
public function cms_categories() {
return $this->morphedByMany('App\CmsCategory', 'taggable');
}
public function items() {
return $this->morphedByMany('App\Item', 'taggable');
}
// Would love something like this to return inverse relation!! :'(
public function taggables() {
return $this->morphTo();
}
}
I am also considering the option to do 3 separate calls to the API to retrieve articles, categories and items in three steps. While in this particular scenario this might make sense after all, I would still need to deal with this particular inverse relation headache with another part of my project: notifications. In this particular case, notifications would have to relate to many different actions/models and I would have to retrieve them all in batches (paginated) and sorted by model creation date...
Hope this all makes sense. I wonder if a completely different approach to the whole inverse "polymorphic" matter would help.
Kind regards,
Federico
Ah yes. I was down your path not all that long ago. I had the same nightmare of dealing with resolving the inverse of the relationship of polymorphic relationships.
Unfortunately polymorphic relationships haven't been given much attention in the Laravel ecosystem. From afar they look like unicorns and rainbows but soon you're fighting things like this.
Can you post an example of a $thing->taggable for a better picture? Think it may be solvable with a dynamic trait + accessor magic.

Can I include a convenience query in a Doctrine 2 Entity Method?

I'd like to include some additional functions in my Doctrine 2 entities to contain code that I'm going to have to run quite frequently. For example:
User - has many Posts
Post - has a single user
I already have a function $user->getPosts(), but this returns all of my posts. I'm looking to write a $user->getActivePosts(), which would be like:
$user->getPosts()->where('active = true') //if this were possible
or:
$em->getRepository('Posts')->findBy(array('user'=>$user,'active'=>true)) //if this were more convenient
As far as I can tell, there's no way to get back to the entity manager though the Entity itself, so my only option would be
class User {
function getActivePosts() {
$all_posts = $this->getPosts();
$active_posts = new ArrayCollection();
foreach ($all_posts as $post) {
if ($post->getActive()) {
$active_posts->add($post);
}
}
return $active_posts;
}
However, this requires me to load ALL posts into my entity manager, when I really only want a small subset of them, and it requires me to do filtering in PHP, when it would be much more appropriate to do so in the SQL layer. Is there any way to accomplish what I'm looking to do inside the Entity, or do I have to create code outside of it?
I think you should implement the method on the PostRepository rather than on the entity model.
I try to keep all model related logic in the repositories behind "domain specific" methods. That way if you change the way you represent whether a post is active or not, you only have to change the implementation of a single method instead of having to find all the active = true statements scattered around in your application or making changes in an "unrelated" entity model.
Something like this
PostRepository extends EntityRepository {
public function findActiveByUser($user){
// whatever it takes to get the active posts
}
}

How can I "defang" a Doctrine2 Entity?

I'd like to create an "API-like" layer in my code that effectively cordons-off database access to higher level code. For example, I might have the following function:
class MyApi {
private $my_user_id;
function getContacts() {
$contacts = $em->getRepository('Contacts')->findByOwner($this->my_user_id);
$em->clear();
return $contacts;
}
function getGroups() {
$groups = $em->getRepository('Groups')->findByOwner($this->my_user_id);
//hydrate each group's contacts list
foreach ($groups as $group) {
$group->getContacts();
}
$em->clear();
return $groups;
}
}
I'm using $em->clear() to detach the Entities from the EntityManger before returning them, so my Views can't accidentally modify managed entities. However, I run into problems when I want to compare entities returned by two sequential API functions. Ideally, I'd like a view/controller to contain:
$my_contacts = $myapi->getContacts();
$my_groups = $myapi->getGroups();
foreach($my_groups as $group) {
foreach ($my_contacts as $contact) {
if ($group->getContacts()->contains($contact)) {
echo "{$group->getName()} contains {$contact->getName()}!<br/>";
} else {
echo "{$group->getName()} does not contain {$contact->getName()}!<br/>";
}
}
}
However, since I detached all of the Contacts from the EntityManager at the end of the getContacts API call, the objects returned by $group->getContacts() are different PHP objects than those returned by $api->getContacts(), so the contains() function doesn't work properly.
Do I have any options for "defanging" my entities, making them effectively read-only, without giving up the benefits that the EntityManager provides? (Such as all managed entities representing the same database entry being the same object, being able to further hydrate associated objects after they've been passed back from the API, etc.)
Why would you worry that your views are going to make changes that will be committed back to the database? If your views don't know about the EM (and they shouldn't), any changes they make to the entities will disappear at the end of the request.
The only other option I can think of is to hydrate your results as arrays when they're destined to be fed to the view script. But that gives up a lot of handy functionality.
Maybe this is a little late, but it can be useful for anyone who still needs answer on this issue...
I think there is missing a Domain Driven Design principle here: Command Query Separation.
On every object you can only have two kind of methods:
doSomething() {
//this kind of method can change the internal state
}
getSomething() {
//this kind of method NEVER changes internal state
}
Keeping proper MVC in mind, views should only need get-methods and they can never change a thing.
Detaching you entities is not necessary and no worries are needed, if keeping just CQS and MVC in mind.

Resources