How to load relations with { json:api } Client? - laravel

I am using the {json:api} Client to parse json into Eloquent like models.
I have two models, Congress and Speaker. A Congress has many Speaker.
This is what I was able to do:
$repository = app(App\Repositories\CongressRepository::class);
$document = $repository->all();
$document is a CollectionDocument with the following attributes:
I would like to get the speakers of the first Congress. This is what I tried
$congresses = $document->getData();
$congress = $congresses->first();
$congress->speakers // <- this is null!
Why is $congress->speakers null? I also tried to us $repository->all(['includes' => 'speakers']);
but this makes no differences.

It seems that $congress->speakers was null because the relation was null:
I use this package to create the json output. Inside the Schema.php I had to add self::DATA to make the data visible, as explained in the docs.
public function getRelationships($resource, $isPrimary, array $includeRelationships)
{
return [
'speakers' => [
self::SHOW_SELF => true,
self::SHOW_RELATED => true,
self::DATA => function () use ($resource) {
return $resource->speakers;
},
],
];
}
I still wonder if its possible to load the relation, if only the link is given in the API.

Related

Error "cannot use object of type App\DataTransferObjects\TestDTO as array" in Laravel when working with collection of DTOs

I'm trying to create a custom collection of a specific type of Data Transfer Objects. I'm using the "spatie/data-transfer-object" package.
Here is a test class I'm using to debug this issue that popped up within the last few days.
namespace App\DataTransferObjects;
​
use Illuminate\Support\Collection;
​
class TestDTOCollection extends Collection
{
public function __construct($items)
{
$DTOs = [];
​
foreach($items as $item) {
array_push($DTOs, new TestDTO(
id: $item['id'],
));
}
​
parent::__construct($DTOs);
}
}
I'm using it like this elsewhere:
$collection = TestDTOCollection::make($items);
dd($collection);
The output is the contents of the collection containing TestDTO objects, as expected.
App\DataTransferObjects\TestDTOCollection^ {#942
#items: array:2 [
0 => App\DataTransferObjects\TestDTO^ {#962
+id: "0123456789"
#exceptKeys: []
#onlyKeys: []
}
1 => App\DataTransferObjects\TestDTO^ {#986
+id: "000000000"
#exceptKeys: []
#onlyKeys: []
}
]
}
But if I use the filter method too:
TestDTOCollection::make($items)
->filter(fn ($item) => $item->id === $someId));
it'll throw this error:
Cannot use object of type App\DataTransferObjects\TestDTO as array
at app/DataTransferObjects/TestDTOCollection.php:15
11▕ $DTOs = [];
12▕
13▕ foreach($items as $item) {
14▕ array_push($DTOs, new TestDTO(
➜ 15▕ id: $item['id'],
16▕ ));
17▕ }
18▕
19▕ parent::__construct($DTOs);
What's made this difficult to debug is that I'm definitely not trying to use TestDTO objects as arrays in the constructor or anywhere else, and yet this error still pops up—and even weirder still, it points to the TestDTOCollection constructor after the collection is instantiated without any problems.
For reference, here is the TestDTO class:
namespace App\DataTransferObjects;
use Spatie\DataTransferObject\DataTransferObject;
class TestDTO extends DataTransferObject
{
public string $id;
}
I'm using Laravel 8 with PHP 8. I was instantiating my DTOs using PHP 8's named arguments syntax and that much does seem to work, so I'm very stumped on how to fix this or what's even wrong in the first place.
Small update
It seems when I'm not using a custom collection class, it just works as expected. For example:
collect($items)
->transform(fn ($item) => new TestDTO(
id: $item['id'],
))
->filter(fn ($item) => isset($item->id));
Still, if anyone is able to help me figure out why this is happening, I'd greatly appreciate it. I'll move along for now, but I hate not at least knowing why what I was doing wasn't working, especially if the issue is so hazy.

Conditionally loaded data in API resource: How to "pass" a condition?

I got a little issue to solve. In my app I am handling with a lot of Models and each model does have something like:
ModelResource
ModelResourceCollection
ModelResourceOverview
ModelResourceOverviewCollection
The reason is: Sometimes I don't need all the information that are visible if I am using the ModelResource - in this case I am calling the ModelResourceOverview.
Example
PostResource
- title
- tags
- content
- author
Example
PostOverviewResource
- title
- author
As I have a lot of Models I have a huge number of ApiResource-Classes and I want to get rid of that.
I thought about using $this->when in the Resource and pass something like "full" or "overview" to the request in the Controller.
Example
$data
= new PostResource($this->post);
So my question is: Is it the best way to add this to the request or is there a handier/nicer way to handle this?
Laravel has a way to hide fields on the fly: Hiding attributes from json
e.g.
return $post->makeHidden('content')->toArray();
If your logic about "visible or not" is bound to the current request, then you should use when or mergeWhen as you mentioned (everything here https://laravel.com/docs/7.x/eloquent-resources#conditional-attributes ), therefore you'll only have 2 resources instead of 4
public function toArray($request)
{
return [
'title' => $this->title,
'author' => $this->author,
$this->mergeWhen($this->needsFullData($request), [
'tags' => $this->tags,
'content' => $this->content,
]),
];
}
protected function needsFullData($request)
{
//Your logic
}

Laravel Model Get Method Decoding JSON

Hi there below is what I'm storing in my db but when I use my get method in my model I have to use json_decode twice when formating my data why is this happening and can I have it just use it once somehow.
json exactly in db:
"[{\"id\":\"1\",\"country\":\"New Zealand\",\"shipping_rate\":\"1\"},{\"id\":\"2\",\"country\":\"Australia\",\"shipping_rate\":\"2\"}]"
Model Get Method:
public function getshippingAttribute()
{
return $this->attributes['shipping'] ? json_decode(json_decode($this->attributes['shipping'])) : [];
}
The problem is not clear enough from your question but the Laravel offers a builtin mechanism for attribute casting (Since v-5.1). In this case, in your model, just declare a $casts property for example:
protected $casts = [
'shipping' => 'array',
// more ...
];
Because of the $casts property given above, whenever you'll write (create/update) a model, you don't need to explicitly use json_encode to convert the array to json string, Laravel will do it for you and also, when you'll retrieve the model (single/collection), the shipping attribute will be automatically converted back to an array so you don't need to use json_decode for working with the attribute.
Regarding the response, that will be also handled by laravel if you don't convert it to json manually (when returning a model/collection). This will possibly solve your problem.
public function getshippingAttribute()
{
return $this->attributes['shipping'] ? json_decode($this->attributes['shipping']) : [];
}
Try return json response
public function getshippingAttribute()
{
return response()->json($this->attributes['shipping'])
}

Remove key data in dingo - fractal

I already looked into several example on how to remove the key "data" in response, but i cannot fix it.
I tried to use the callback provided by dingo
return $this->collection($users, new UserTransformer, function ($resource, $fractal) {
$fractal->setSerializer(new ArraySerializer);
});
In change the "$fractal->setSerializer(new CustomSerializer);" to "$fractal->setSerializer(new ArraySerializer);" since I dont have CustomSerializer(And how to make this custom serializer?) based on the fractal documentation array serializer but the out put has the key "data".
I also tested the fractal library in different project and implementing the ArraySerializer as the Serializer, and it works.
What am I missing in the setup of dingo-fractal?
UPDATE*
I included the setup in the config
$this->app->bind('Dingo\Api\Transformer\Adapter\Fractal', function($app) {
$fractal = $app->make('\League\Fractal\Manager');
$serializer = new \League\Fractal\Serializer\ArraySerializer();
$fractal->setSerializer($serializer);
return new \Dingo\Api\Transformer\Adapter\Fractal($fractal);
});
And in my controller
$obj = \App\EloquentModel\User::find(1);
return $this->response->item($obj, new UserTransformer);
And in my UserTransformer
public function transform(User $trans)
{
return [
'id' => (int) $trans->id,
'name' => $trans->name,
'description' => $trans->description
];
}
Applying those things removed the key "data" for single item.
{
"id": 1,
"name": "Juan",
"description": "The BOss"
}
But when I try to make it an Array. User::all(), the response has the "data" key.
Thanks.
I was missing the CustomSerializer, a class that is extend by ArraySerializer. Thanks to this link enter link description here

PHPSpec how to ask if a method returns either this or that

I have a method which queries the database and either returns an array with the results or false if there are no results.
All I need PHPSpec to do in this case is test whether it returns an array or false, but I can't work out how to do that.
Or do I need to mock the database query and separate it out of my method?
You're not showing any code so we can work with, but if current matchers doesn't work for you, you can create new ones:
function it_should_return_array_or_false()
{
$this->getOptions()->shouldBeArrayOrFalse();
}
public function getMatchers()
{
return [
'beArrayOrFalse' => function($subject, $value) {
return is_array($value) || $value === false;
},
];
}

Resources