Not getting expected data for Laravel JSON:API - laravel

I am trying to create an API based on the following two Models:
Product.php
<?php
namespace App\Models\Commerce;
use ...
class Product extends Model {
use HasFactory;
use SoftDeletes;
public mixed $id;
protected $fillable = ['user_id', 'title', 'default_variation', 'status'];
/**
+ * #return HasMany
+ */
public function productVariations() : HasMany {
return $this->hasMany('App\Models\Commerce\ProductVariation');
}
public function defaultVariation() : HasOne {
return $this->hasOne('App\Models\Commerce\ProductVariation', 'default_variation');
}
}
ProductVariation.php
<?php
namespace App\Models\Commerce;
use ...
class ProductVariation extends Model {
use HasFactory;
protected $fillable = ['user_id', 'product_id', 'sku', 'title', 'price', 'status'];
public function product() : BelongsTo {
return $this->belongsTo(Product::class);
}
}
I have configured the JSON:API according to the documents, as best I can tell:
routes/api.php
<?php
use ...
JsonApiRoute::server('v2')->prefix('v2')->resources(function ($server) {
$server->resource('products', JsonApiController::class)->readOnly();
$server->resource('product-variations', JsonApiController::class)->readOnly();
});
ProductSchema.php
<?php
// For authentication see ProductPolicy
namespace App\JsonApi\V2\Products;
use ...
class ProductSchema extends Schema
{
public static string $model = Product::class;
public function fields(): array
{
return [
ID::make(),
DateTime::make('createdAt')->sortable()->readOnly(),
DateTime::make('updatedAt')->sortable()->readOnly(),
Str::make('title'),
Number::make('user_id'),
Number::make('defaultVariation'),
HasOne::make('defaultVariation', 'default_variation'),
Boolean::make('status'),
HasMany::make('productVariations')
];
}
public function filters(): array
{
return [
WhereIdIn::make($this),
];
}
public function pagination(): ?Paginator
{
return PagePagination::make();
}
}
ProductVariationSchema.php
<?php
namespace App\JsonApi\V2\ProductVariations;
use ...
class ProductVariationSchema extends Schema
{
public static string $model = ProductVariation::class;
public function fields(): array
{
return [
ID::make(),
DateTime::make('createdAt')->sortable()->readOnly(),
DateTime::make('updatedAt')->sortable()->readOnly(),
Number::make('userId'),
Number::make('productId'),
Str::make('sku'),
Str::make('title'),
Number::make('price'),
Boolean::make('status'),
BelongsTo::make('product')
];
}
public function filters(): array
{
return [
WhereIdIn::make($this),
];
}
public function pagination(): ?Paginator
{
return PagePagination::make();
}
}
With some dummy data in the database, I can access the endpoint /api/v2/products and receive the following:
{
"jsonapi": {
"version": "1.0"
},
"data": [
{
"type": "products",
"id": "3",
"attributes": {
"createdAt": null,
"updatedAt": null,
"title": "foo-product",
"user_id": 1,
"status": 1
},
"relationships": {
"defaultVariation": {
"links": {
"related": "domain/api/v2/products/3/default-variation",
"self": "domain/api/v2/products/3/relationships/default-variation"
}
},
"productVariations": {
"links": {
"related": "domain/api/v2/products/3/product-variations",
"self": "domain/api/v2/products/3/relationships/product-variations"
}
}
},
"links": {
"self": "domain/api/v2/products/3"
}
}
]
}
/api/v2/product-variations
{
"jsonapi": {
"version": "1.0"
},
"data": [
{
"type": "product-variations",
"id": "3",
"attributes": {
"createdAt": null,
"updatedAt": null,
"userId": 1,
"productId": 3,
"sku": "foo-bar",
"title": "foo bar ",
"price": "11.11",
"status": 1
},
"relationships": {
"product": {
"links": {
"related": "domain/api/v2/product-variations/3/product",
"self": "domain/api/v2/product-variations/3/relationships/product"
}
}
},
"links": {
"self": "domain/api/v2/product-variations/3"
}
}
]
}
However, if I try api/v2/products/3/product-variations or /api/v2/products/3/relationships/product-variations I get a 404 error:
{
"jsonapi": {
"version": "1.0"
},
"errors": [
{
"status": "404",
"title": "Not Found"
}
]
}
Additionally, if I run a test I also get a 404 but I do see that the variations are correctly related:
I think that somewhere I have misconfigured something, but this is my first time doing this in Laravel so I might be completely missing a configuration also.

You only set a JsonApiRoute (starting with api) and prefix('v2') meaning api/v2 group. In this group you add 2 more product-variations meaning /api/v2/product-variations and products /api/v2/products. So anything else don't exists and laravel returns 404.
You need to add
$server->resource('products/{id}/product-variations', JsonApiController::class)->readOnly();
for api/v2/products/3/product-variations
and
$server->resource('products/{id}/relationships/product-variations', JsonApiController::class)->readOnly();
for /api/v2/products/3/relationships/product-variations
Also you can add (int $id) in your controller methods to read this id you pass.

Related

How to make attributes in Laravel API resource dynamically?

I want make the values in one collection to be attributes in my resource.
{
"data": {
"import_type": "Material",
"id": "1178d470-85b6-4594-9bb4-e9e17b9d9104",
"tabs": [
{
...
}
],
"graphics": {
"render": "",
"shading_colour": "",
}
"identity": {
"render": "",
"shading_colour": "",
}
}
}
Unfortunately I get that json code:
{
"data": {
"import_type": "Material",
"id": "1178d470-85b6-4594-9bb4-e9e17b9d9104",
"tabs": [
{
...
}
],
"0": [
{
"graphics": {
"render": "",
"shading_colour": "",
}
},
{
"identity": {
"render": "",
"shading_colour": "",
}
},
]
}
}
Because I have more attributes like "graphics", "identity" I want to display them dynamically, but I dont know how to achieve that.
Here is my PHP code:
class MaterialResource extends JsonResource
{
public function toArray($request)
{
return [
'import_type' => 'Material',
'id' => $this->uuid,
'tabs' => MaterialTabsResource::collection($this->tabs),
MaterialExtensionResource::collection($this->extensions),
];
}
}

How to filter collection in laravel?

I return the ExportTemplate model :
$exportTemplates = ExportTemplate::where('user_id', Auth::id())->get();
the result is something like:
[
{
"id": 45,
"name": "hallo",
"user_id": 1,
"deleted_at": null,
"created_at": "2020-02-27 14:12:50",
"updated_at": "2020-02-27 14:12:50",
"ExportTemplateColumns": [
{
"id": 398,
"export_template_id": 45,
"searchable_column_id": 7,
"created_at": "2020-02-27 14:12:50",
"updated_at": "2020-02-27 14:12:50",
"SearchableColumn": {
"id": 7,
"name": "bic",
"type": "string",
"searchable_table_id": 1,*** I need only results with searchable_table_id ==1
"created_at": "2020-02-06 09:11:24",
"updated_at": "2020-02-06 09:11:30",
"label": "BIC"
}
}
]
},
{
"id": 46,
"name": "fol",
(...)
I want to filter this result through Collection Filter to return only :
ExportTemplateColumns->SearchableColumn->searchable_table_id == 1
Models :
class ExportTemplate extends Model
{
protected $appends = ["ExportTemplateColumns"];
public function getExportTemplateColumnsAttribute()
{
return ExportTemplatesColumns::where('export_template_id',$this->id)->get();
}
public function user()
{
return $this->belongsTo(User::class,'id');
}
public function exportTemplatesColumns()
{
return $this->hasMany(ExportTemplatesColumns::class,'export_template_id');
}
}
class ExportTemplatesColumns extends Model
{
protected $appends = ["SearchableColumn"];
public function getSearchableColumnAttribute()
{
return SearchableColumn::find($this->searchable_column_id);
}
public function exportTemplate()
{
return $this->belongsTo(ExportTemplate::class,'id');
}
public function searchableColumn()
{
return $this->belongsTo(SearchableColumn::class,'id');
}
}
try this.
$exportTemplates = ExportTemplate::where('user_id', Auth::id())->get();
$filtered = $exportTemplates->filter(function($d){
return collect($d->ExportTemplateColumns)->filter(function($columns){
return $columns->SearchableColumn->searchable_table_id === 1;
})->count();
});
$filtered->all();

remove data from laravel fractal

I'm using laravel-fractal to transform my data and here is a response
how can I delete data;
I made a search and I realized I need to use a Serializer;
But I just want to remove data for all includes(relations)
{
"data": [
{
"id": 1,
"name": "test name",
"status": null,
"tags": [
"first",
"second"
],
"created_at": "1396/9/3",
"contacts": {
"data": [
{
"value": "test#test.com",
"type": "email",
"icon": "fa fa-email"
}
]
}
},
{
"id": 2,
"name": "name test 2",
"status": null,
"tags": [],
"created_at": "1396/9/3",
"contact": {
"data": []
}
}
]
I found my answer at GitHub. The following answer is copied from thephpleague/fractal on GitHub:
You can write your own serializer for that.
class YourDataSerializer extends ArraySerializer
{
public function collection($resourceKey, array $data)
{
if ($resourceKey) {
return [$resourceKey => $data];
}
return $data;
}
public function item($resourceKey, array $data)
{
if ($resourceKey) {
return [$resourceKey => $data];
}
return $data;
}
}
Register your serializer with manager
$manager = new Manager();
$manager->setSerializer(new YourDataSerializer());
and when you want to have data or anything else you can pass in resourceKey to your Item or Collection as a third param.
$resource = new Collection($folders, new AccountFolderTransformer(), 'data');
use array serializer:
Fractal::create()
->item($item, new MyTransformer())
->serializeWith(new ArraySerializer())

Eloquent (kind of) triple relationship

I'm having a hard time finding a simple solution, using Eloquent relationships, I have 3 models:
User
Item
Category
Now a User has many Categories, a Category only has one user. A user has many items, an item as many users. And finally an item has many categories and many categories has many items. The tricky part is that although an item can have many categories, it can only has one category from each user, so the reverse would be the user can only associate one category that he owns to the item.
Also the pivot table between item and user(item_user) has two attributes, boolean is_owner and tinyInt permissions
During my application I always have the user ID
I would like to retrieve the expenses that belong to the user, achieving something like this:
[
{
"id": "1",
"value": "0.40",
"description": "Expense 0",
"category": {
"id": "1",
"name": "Health",
"created_at": {
"date": "2015-04-15 01:33:05",
"timezone_type": 3,
"timezone": "UTC"
},
"update_at": {
"date": "2015-04-15 01:33:05",
"timezone_type": 3,
"timezone": "UTC"
}
},
"users": [
{
"id": "1",
"name": "Fabio",
"email": "fabioantuness#gmail.com",
"permissions": {
"expense_id": "1",
"user_id": "1",
"is_owner": "1",
"permissions": "6"
}
}
]
},
{
"id": "2",
"value": "12.40",
"description": "Expense 1",
"category": {
"id": "1",
"name": "Health",
"created_at": {
"date": "2015-04-15 01:33:05",
"timezone_type": 3,
"timezone": "UTC"
},
"update_at": {
"date": "2015-04-15 01:33:05",
"timezone_type": 3,
"timezone": "UTC"
}
},
"users": [
{
"id": "1",
"name": "Fabio",
"email": "fabioantuness#gmail.com",
"permissions": {
"expense_id": "2",
"user_id": "1",
"is_owner": "1",
"permissions": "6"
}
}
]
}
]
Item model:
class Item extends Model {
protected $fillable = ['category_id', 'value', 'description'];
public function users()
{
return $this->belongsToMany('App\User')->withPivot('is_owner', 'permissions');
}
public function categories()
{
return $this->belongsToMany('App\Category');
}
}
User model:
class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
use Authenticatable, CanResetPassword;
protected $table = 'users';
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
public function categories()
{
return $this->hasMany('App\Category');
}
public function items()
{
return $this->belongsToMany('App\Item')->withPivot('is_owner', 'permissions');
}
public function tokens()
{
return $this->hasMany('App\Token');
}
}
Category model:
class Category extends Model {
protected $fillable = ['name'];
public function user(){
return $this->belongsTo('App\User');
}
public function items()
{
return $this->belongsToMany('App\Item');
}
}
Schematizing the question here on stackoverflow helped me get to the solution, here's the query:
User::expenses()->with(
[
'users',
'categories' => function ($query) use ($userId)
{
$query->where('user_id', $userId);
}
]
)->get()->all();
If anyone has a better solution feel free to share it

Laravel's Eloquent pivot object

I have defined relation in my model:
public function positions() {
return $this->belongsToMany('Position', 'users_positions')->withPivot('season');
}
Is it possible to display objects with pivot in cleaner way?
For Example:
"positions": [
{
"id": 1,
"name": "A",
"pivot": {
"user_id": 1,
"position_id": 1,
"season": 2014
}
}
],
I would like to get:
"positions": [
{
"id": 1,
"name": "A",
"season": 2014
}
],
Use accessor:
public function getSeasonAttribute()
{
return ($this->pivot) ? $this->pivot->season : null;
}
Then you can access it just like other properties:
$position = $someModel->positions->first();
$position->season;
if you also need it in the toArray/toJson output, then use on you model:
protected $appends = ['season'];

Resources