How to use conditional relationship in API Resource? - php

I have created an API Resource:
class OrderResource extends JsonResource
{
public function toArray($request)
{
return [
"id" => $this->Id,
"photo" => ''
];
}
}
In controller I get data from model OrderModel the put data into resource OrderResource:
public function show($id)
{
$order = OrderModel::with('OrderPhoto')->findOrFail(1);
return new OrderResource($order);
}
So, I tried to use relation OrderPhoto in OrderResource like this:
public function toArray($request)
{
return [
"id" => $this->Id,
"photo" => OrderPhotoResource::collection($this->whenLoaded('OrderPhoto')),
];
}
But it does not work and gives this error:
Undefined property: Illuminate\Database\Query\Builder::$map
I did dd($this) in resource and what I got:
Class OrderPhoto:
class OrderPhoto extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

TL;DR
Try this in your OrderResource:
use OrderPhoto as OrderPhotoResource;
//
public function toArray($request)
{
return [
"id" => $this->Id,
"photo" => new OrderPhotoResource($this->whenLoaded('OrderPhoto')),
];
}
Explanation
As you can see, you are already defining the OrderPhoto as a Resource Collection:
class OrderPhoto extends ResourceCollection // <-- note the extended class
So in this case, you'll need to use this class instanciating it and pass in it the collection, instead of using the static method collection.
When you define a API Resource for a single object, like this:
php artisan make:resource PostResource
you use it like below:
$post = Post::find(1);
return new PostResource($post);
And if you want to use an API Resource to format a collection of resources instead of a single one, you need to do this:
$posts = Post::all();
return PostResource::collection($posts); // <-- note the ::collection part
Controlling the metadata
If you want to have a total control of the returned metadata in the response, define a custom API Resource Collection class instead.
Generate the class as a collection (adding the 'Collection' at the end or using the flag --collection):
php artisan make:resource PostResourceCollection
then, after customize it:
$posts = Post::all();
return new PostResourceCollection($posts); // <-- instantiating the class

Related

filter entity fields on symfony controller

How can I choose(filter) on my controller which fields I want (or don't want) to pass to my frontend?
my Controller:
/**
* #Route("/", name="dashboard")
*/
public function index()
{
$aniversariantes = $this->getDoctrine()->getRepository(Usuario::class)->aniversariantes();
return $this->render('dashboard/index.html.twig', [
'controller_name' => 'DashboardController',
'aniversariantes' => $aniversariantes
]);
}
My repository:
/**
* #return []
*/
public function aniversariantes(): array
{
$qb = $this->createQueryBuilder('u')
->andWhere('u.ativo = 1')
->andwhere('extract(month from u.dtNascimento) = :hoje')
->setParameter('hoje', date('m'))
->getQuery();
return $qb->execute();
}
Dump from entity:
What can I do if I don't want to pass the "password" field for example?
If you are just trying to prevent certain fields from being dumped, it is useful to know
Internally, Twig uses the PHP var_dump function.
https://twig.symfony.com/doc/2.x/functions/dump.html
This means you can can define the PHP magic method __debugInfo in your entity
This method is called by var_dump() when dumping an object to get the properties that should be shown. If the method isn't defined on an object, then all public, protected and private properties will be shown.
https://www.php.net/manual/en/language.oop5.magic.php#object.debuginfo
So in your entity do something like this:
class Usuario {
...
public function __debugInfo() {
return [
// add index for every field you want to be dumped
// assign/manipulate values the way you want it dumped
'id' => $this->id,
'nome' => $this->nome,
'dtCadastro' => $this->dtCadastro->format('Y-m-d H:i:s'),
];
}
...
}

Laravel accessor returning values of nested relationships which are not required

Currently I'm stuck in making an accessor. I'm trying to access some values from the nested relationship after when I got that I'm returning the value and appending it to the model, but the problem is inside my response I'm getting values of the relationship which I try to access in my accessor.
public function getTranslatorEmailAttribute()
{
if (in_array(AddOnConfirmation::EMAIL, $this->customer->department->company->add_on_confirmation)) {
return $this->assignedTranslator()->first()->email;
} else {
return null;
}
}
Here is the customer relation which I'm trying to use
public function customer()
{
return $this->belongsTo(User::class)->with('customerData.customerType', 'customerData.department.company');
}
How can I fix this?
Here is a screenshot of response I'm getting with using accessor
Your GET route that handles api/bookings/{id} should return the resource in the end, something like:
return BookingResource::make($booking);
Then create BookingResource and likely put it in namespace App\Http\Resources\Api;
The file itself can look like:
namespace App\Http\Resources\Api;
use Illuminate\Http\Resources\Json\Resource;
/** #mixin \App\Models\Booking */
class BookingResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
$append = $request->get('append');
return [
// Here you'd put everything you want to show
'id' => $this->id,
'translator_email' => $append == 'translator_email' ? $this->translator_email : null,
// ...
];
}
}

How to generalize a resource function to be used in all controllers for different models?

In laravel API Resources:
I need a dynamic way to generalize a code for all resources to be used in all controllers instead of using resources in all methods for each controller .. for more clarification, I have a trait that includes generalized functions which return json responses with data and status code, lets take a "sample function" suppose it is showAll(Collection $collection) which is used for returning a collection of data of the specified model for example it is used for returning all users data ..
so I need to build a function that call what ever resource of the specified model, knowing that I have many models...
a) trait that include showAll method:
namespace App\Traits;
use Illuminate\Support\Collection;
trait ApiResponser
{
private function successResponse($data, $code) {
return response()->json($data, $code);
}
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$collection, 'code' => $code], $code);
}
protected function resourceData(Collection $collection) {
return $collection;
}
}
b) usercontroller as a sample
namespace App\Http\Controllers\User;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\ApiController;
class UserController extends ApiController
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$users = User::all();
// Here the showAll(Collection $collection) is used
return $this->showAll($users);
}
}
c) UserResource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'identity' => $this->id,
'name' => $this->name,
'email' => $this->email,
'isVerified' => $this->verified,
'isAdmin' => $this->admin,
'createDate' => $this->created_at,
'updateDate' => $this->updated_at,
'deleteDate' => $this->deleted_at,
];
}
}
generalize: means used everywhere without code redundancy
What about providers, you may load data there and make that data reachable at places where user data can be reachable ?
laravel docs
I found a simple solution.. by adding the following method
protected function resourceData($collection) {
$collection = get_class($collection[0]);
$resource = 'App\Http\Resources\\' . str_replace('App\\', '', $collection) .
'Resource';
return $resource;
}
The $collection[0] in the first line of this method will get the
model you are currently using.
get_class will get the model name ex: App\User
'App\Http\Resources\\' . str_replace('App\\', '', $collection):
This will get the path of the resource by adding 'App\Http\Resources\' before the
model
str_replace('App\\', '', $collection): will remove App\ path from the collection
name so App\User should be User
then 'Resource' would be concatenated with the previous results and the whole
string should be like that: App\Http\Resources\UserResource
So at the end you should return the whole string App\Http\Resources\UserResource
,finally you should call the resourceData() in
the showAll() method:
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
//Calling resourceData() method
$resource = $this->resourceData($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$resource::collection($collection), 'code' => $code], $code);
}

Laravel dingo/api custom transformer

I am trying to implement a custom transformer using dingo api (https://github.com/dingo/api/wiki/Transformers#custom-transformation-layer) for my Post model and I am getting this exception:
Missing argument 2 for PostTransformer::transform(), called in /home/.../vendor/league/fractal/src/Scope.php on line 298 and defined
My controller:
$post = Post::findOrFail(2);
return $this->item($post, new PostTransformer);
My PostTransformer class:
<?php
use Illuminate\Http\Request;
use Dingo\Api\Transformer\Binding;
use Dingo\Api\Transformer\TransformerInterface;
class PostTransformer implements TransformerInterface
{
public function transform($response, $transformer, Binding $binding, Request $request)
{
// Make a call to your transformation layer to transformer the given response.
return [
'kkk' => 'val'
];
}
}
What is wrong?
Your PostTransformer isn't a Transformer. What you specified there is an TransformerLayer (https://github.com/dingo/api/wiki/Transformers#custom-transformation-layer).
However a Transformer in Dingo looks like this:
<?php
use League\Fractal\TransformerAbstract;
class PostTransformer extends TransformerAbstract
{
public function transform(Post $post) {
return [
'id' => $post->id
// ...
];
}
}

Get array of Eloquent model's relations

I'm trying to get an array of all of my model's associations. I have the following model:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public function author()
{
return $this->belongsTo('Author');
}
public function category()
{
return $this->belongsTo('Category');
}
}
From this model, I'm trying to get the following array of its relations:
array(
'author',
'category'
)
I'm looking for a way to pull this array out from the model automatically.
I've found this definition of a relationsToArray method on an Eloquent model, which appears to return an array of the model's relations. It seems to use the $this->relations attribute of the Eloquent model. However, this method returns an empty array, and the relations attribute is an empty array, despite having my relations set up correctly.
What is $this->relations used for if not to store model relations? Is there any way that I can get an array of my model's relations automatically?
It's not possible because relationships are loaded only when requested either by using with (for eager loading) or using relationship public method defined in the model, for example, if a Author model is created with following relationship
public function articles() {
return $this->hasMany('Article');
}
When you call this method like:
$author = Author::find(1);
$author->articles; // <-- this will load related article models as a collection
Also, as I said with, when you use something like this:
$article = Article::with('author')->get(1);
In this case, the first article (with id 1) will be loaded with it's related model Author and you can use
$article->author->name; // to access the name field from related/loaded author model
So, it's not possible to get the relations magically without using appropriate method for loading of relationships but once you load the relationship (related models) then you may use something like this to get the relations:
$article = Article::with(['category', 'author'])->first();
$article->getRelations(); // get all the related models
$article->getRelation('author'); // to get only related author model
To convert them to an array you may use toArray() method like:
dd($article->getRelations()->toArray()); // dump and die as array
The relationsToArray() method works on a model which is loaded with it's related models. This method converts related models to array form where toArray() method converts all the data of a model (with relationship) to array, here is the source code:
public function toArray()
{
$attributes = $this->attributesToArray();
return array_merge($attributes, $this->relationsToArray());
}
It merges model attributes and it's related model's attributes after converting to array then returns it.
use this:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public $relationships = array('Author', 'Category');
public function author() {
return $this->belongsTo('Author');
}
public function category() {
return $this->belongsTo('Category');
}
}
So outside the class you can do something like this:
public function articleWithAllRelationships()
{
$article = new Article;
$relationships = $article->relationships;
$article = $article->with($relationships)->first();
}
GruBhub, thank you very much for your comments. I have corrected the typo that you mentioned.
You are right, it is dangerous to run unknown methods, hence I added a rollback after such execution.
Many thanks also to phildawson from laracasts, https://laracasts.com/discuss/channels/eloquent/get-all-model-relationships
You can use the following trait:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Relations\Relation;
trait EloquentRelationshipTrait
{
/**
* Get eloquent relationships
*
* #return array
*/
public static function getRelationships()
{
$instance = new static;
// Get public methods declared without parameters and non inherited
$class = get_class($instance);
$allMethods = (new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC);
$methods = array_filter(
$allMethods,
function ($method) use ($class) {
return $method->class === $class
&& !$method->getParameters() // relationships have no parameters
&& $method->getName() !== 'getRelationships'; // prevent infinite recursion
}
);
\DB::beginTransaction();
$relations = [];
foreach ($methods as $method) {
try {
$methodName = $method->getName();
$methodReturn = $instance->$methodName();
if (!$methodReturn instanceof Relation) {
continue;
}
} catch (\Throwable $th) {
continue;
}
$type = (new \ReflectionClass($methodReturn))->getShortName();
$model = get_class($methodReturn->getRelated());
$relations[$methodName] = [$type, $model];
}
\DB::rollBack();
return $relations;
}
}
Then you can implement it in any model.
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use App\Traits\EloquentRelationshipTrait;
class User extends Authenticatable
{
use Notifiable, HasApiTokens, EloquentRelationshipTrait;
Finally with (new User)->getRelationships() or User::getRelationships() you will get:
[
"notifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"readNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"unreadNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"clients" => [
"HasMany",
"Laravel\Passport\Client",
],
"tokens" => [
"HasMany",
"Laravel\Passport\Token",
],
]
I have published a package in order to get all eloquent relationships from a model. Such package contains the helper "rel" to do so.
Just run (Composer 2.x is required!):
require pablo-merener/eloquent-relationships
If you are on laravel 9, you are able to run artisan command model:show

Categories