Laravel format resource collection response error Property [product_id] does not exist on this collection instance - json

In laravel we can format our json response from resource class as seen below
class ProductsResource extends JsonResource
{
public function toArray($request)
{
return [
'id'=> $this->product_id ,
'code'=> $this->product_code,
'shortdescription'=> $this->product_short_description,
'image'=> $this->product_image,
];
}
}
But when returning resources collection i can't format my collection error Property [product_id] does not exist on this collection instance
class ProductsResource extends ResourceCollection
{
public function toArray($request)
{
return [
'id'=> $this->product_id ,
'code'=> $this->product_code,
'shortdescription'=> $this->product_short_description,
'image'=> $this->product_image,
];
}
}
thanks.

It's because ResourceCollection expects a collection of items instead of a single item. The collection resource expects you to iterate through the collection and cannot perform single enitity routines directly from $this (as it's a collection).
See resource collections in documentation
What you are probably looking for is to cast a custom mutation which examples can be found here:
See custom casts in documentation
Look/search for Value Object Casting. It is explained thoroughly how to mutate attributes on get and set, which is probably better than a resource collection (if this is the only thing you wish to do with it). This will modify the collection immediately and saves you from having to manually instantiate the resource collection every time you need it (as you are modifying at model level).
Coming from the docs:
Value Object Casting
You are not limited to casting values to primitive types. You may also cast values to objects. Defining custom casts that cast values to objects is very similar to casting to primitive types; however, the set method should return an array of key / value pairs that will be used to set raw, storable values on the model.
But to get back on topic...
If you dump and die: dd($this); you will see there is an attribute called +collection
In case you wish to transform keys or values, you have to iterate through $this->collection in order to transform the collection values or keys to your requirements.
As you can see in the parent class Illuminate\Http\Resources\Json\ResourceCollection the method toArray() is already a mapped collection.
In which you can see it's pointing to $this->collection
/**
* Transform the resource into a JSON array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return $this->collection->map->toArray($request)->all();
}
You could use something like the following. And update the item/key values within this collection map.
return $this->collection->map(function($item, $key){})->toArray();
if you wish to transform the values before returning it to an array.
Or a simple foreach like this (have not tested it and there are far better ways of doing this) But for the sake of sharing a simple-to-grasp example:
$result = [];
// Map the associations to be modified
$resultMap = [
'product_id' => 'id',
'product_code' => 'code',
'product_short_description' => 'shortdescription',
'product_image' => 'image'
];
// Iterate through the collection
foreach ($this->collection as $index => $item)
foreach ($item as $key => $value)
$result[$index][$resultMap[$key]] = $value;
return $result;

Related

Yii2 how can an model attribute is modified after load method? (like the afterFind method)

I have an attribute of the model which should be modified after it's loaded from the database.
I could extend the afterFind method, which could the convert the varchar value to a php array. So it works find.
But when the model is loaded I have no idea how to convert that varchar to the php array.
I have tried with rules but does not works:
[['languages'], 'each', 'rule' => ['string']],
or this one
[['languages'], 'safe'],
So this one works afterFind:
public function afterFind()
{
$this->languages = $this->convertToPHPArray($this->languages);
parent::afterFind();
}
By the way I have tried to extend the init or the __constructor method with this conversation, but no success, after load method the languages attribute is still a string instead of a php array.
If I understood your question, I think that you could use a property in the model:
public class Model {
public function getLanguagesArray()
{
return $this->convertToPHPArray($this->languages);
}
}
Then, use it:
$arr = $model->languagesArray;

Reindexing ArrayCollection elements in RESTAPI

Imagine, I've an array collection $items, and implemented a method removeItem in my SomeEntity entity class,
public function removeItem(Item $item)
{
$this->items->removeElement($item);
return $this;
}
and a controller action which receives a PATCH request:
public function patchAction(Request $request, SomeEntity $entity) {
$form = ...;
$form->handleRequest($request);
if ($form->isValid()) {
...
$this->get('doctrine.orm.entity_manager')->flush();
return $entity;
}
return $form;
}
Imagine that the instance of the SomeEntity class holds items as [0 => {ITEM}, 1 => {ITEM}, 2 => {ITEM}, 3 => {ITEM}] (indexed array of objects). If you send a request to getAction and receive the SomeEntity object in your front project in JSON format, the type of those items in an object will be an indexed Array of objects (you can iterate over them with array methods), but if you remove one or more of them from an object with PATCH method and receive a JSON response, you get an Object type with objects in it, because some keys have been removed after the PATCH request and the object of SomeEntity class in the response no longer holds an indexed array, instead there will be an object of objects. It is because of, when you convert an array into json object you can get two different results
array of objects(arrays) if keys are indexed (e.g: 0,1,2,3,...)
object of objects(arrays) if keys are not indexed (e.g: 0,2,3,6,...)
At this moment I'm solving this problem with modifying (manually recreating elements) the existing removeItem method in entity class, like this:
public function removeItem(Item $item)
{
$this->items->removeElement($item);
if (!$this->items->isEmpty()) {
$items = new ArrayCollection();
foreach ($this->items as $item) {
$items->add($item);
}
$this->items = $items;
}
return $this;
}
May be there is a better way to solve this? How do you solve this problem?
I'm using FOSRestBundle and JmsSerializerBundle.
This appears to be a common problem - see https://github.com/schmittjoh/JMSSerializerBundle/issues/373 for others having this issue. There is definitely a shorter way to solve it in your functions where you remove items than looping through each element again:
public function removeItem(Item $item)
{
$this->items->removeElement($item);
$this->items= new ArrayCollection($this->items->getValues());
return $this;
}
There are other workarounds listed in that ticket that may or may not suit your needs - if you want a global solution you might be able to override the JsonSerializationVisitor class which casts to an array.

Laravel: decode JSON within Eloquent

I need to JSON decode a certain column in my Eloquent query. Is there a way to do this without breaking all apart?
So far I have this.
public function index()
{
return Offer::all();
}
Use an accessor on the model:
public function getColumnNameAttribute($value) {
return json_decode($value);
}
or use attribute casting to tell Laravel to do it automatically:
protected $casts = [
'column_name' => 'array',
];
The array cast type is particularly useful when working with columns that are stored as serialized JSON.
Note that you may have to stop json_encodeing your data if you use casts, as Laravel will now do that step automatically as well.

Symfony2 doctrine and response

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.

Propel toArray Mechanics in Doctrine?

I was using Propel for a long time and now I want to try Doctrine.
In My Propel days I used PropelObjectCollection::toArray (for a collection) or PropelObject::toArray() for a single record to convert the PropelObject via array to json.
In my company we override the toArray method to store virtual columns in the array and then the json string.
For example:
public function toArray() {
$arr = parent::toArray();
$arr['full_name'] = $this->getFullName(); // full_name isnt part of the table, it's just a getter
return $arr;
}
When I turn this into json i have my full_name property in my json and then in my Extjs store Object (we use extjs).
Now I wanna try doctrine, but doctrine doesn't seem to allow this.
Can i override a function or property in my doctrine class, or can I do this by annotations, is it possible to generate a json with propertys ('first_name', 'last_name', 'full_name') if my Doctrine class only has the properties $first_name, $last_name and no $full_name
or is there a work around to achieve the same?
Thanks for your help
Edit:
I found something in JMSSerializerBundle if you use Annotations:
#VirtualProperty
use JMS\Serializer\Annotation\VirtualProperty;
at the top of my Doctrine Entity File and an example method
/**
*
* #VirtualProperty
* #return string
*/
public function getFullName() {
return $this->getName(). " mylastname";
}
my json then contains the virtual property full_name