Laravel Relation on Json Array - json

i have relation b/w product categories and model where in model i have product_categories_id where i will save ["product_categories_id1","product_categories_id2"]
so
i have seen this https://github.com/staudenmeir/eloquent-json-relations
and used like this in Models.php
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
public function category()
{
return $this->belongsToJson(ProductCategory::class, 'product_category_id');
}
but did'nt get the expected result

Define the field type in model and try it. Also please make sure the array should not have the key.
protected $casts = [
'product_category_id' => 'json',
];

Related

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

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;

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.

Yii2: Can I use scenarios to specify different set of model fields for different actions?

I can see in the Yii2 model page (http://www.yiiframework.com/doc-2.0/yii-base-model.html), in the "fields" section that you can set "different lists of fields based on some context information. For example, depending on $scenario or the privilege of the current application user, you may return different sets of visible fields or filter out some fields."
But, scenarios documentation (http://www.yiiframework.com/doc-2.0/guide-structure-models.html#scenarios) says scenarios is for creating different context for model attributes validation.
I'm using Yii2 Restful API, where I have to use default actions (actionIndex, actionView, ...) to get data from model and show as API results. I know I can override those methods (http://www.yiiframework.com/doc-2.0/guide-rest-controllers.html#extending-active-controller), but how can I say in those methods to use different set of fields (depending on different scenarios) ?
What I need is to output field1, field2, field3 for actionIndex (items list), but I want to output field1, field2, field3, field4 for actionView (item list).
You do it in your model.
Default REST implementation in Yii2 will only include attributes that are returned by the fields() method. By default, that method returns all attributes. Therefore, you define it like so:
class MyModel extends ActiveRecord
{
//...
public function fields()
{
switch ($this->scenario) {
case 'my_scenario_1':
return ['field1', 'field2'];
case 'my_scenario_2':
return ['field3', 'field4'];
}
}
}
Also, you have the scenarios() method at your disposal, which returns all active attributes for a given scenario.
Don't forget to set the models' scenario in your controller.
According to this guide: http://www.yiiframework.com/doc-2.0/guide-structure-models.html#scenarios
first set your scenarios in your model. example:
const SCENARIO_LESS = 'index';
const SCENARIO_MORE = 'view';
public function scenarios()
{
return [
self::SCENARIO_LESS => ['field1', 'field2'],
self::SCENARIO_MORE => ['field1', 'field2', 'field3'],
];
}
now in your action(s) just set the scenario like this:
// scenario is set through configuration
public function actionIndex()
{
$model = new User(['scenario' => User::SCENARIO_LESS]);
...
}
...
public function actionView()
{
$model = new User(['scenario' => User::SCENARIO_MORE]);
...
}
that should do it.
this is my solution for this problems, using Yii::$app()
public function fields()
{
if(Yii::$app->controller->action->uniqueId == 'controller/action'){
return ['field_1','field_2','field_3','field_4'];
}else{
return ['field_1','field_3'];
}
}
hope works for you

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.

serialize an entity that extends FOS user model

I have a user entity that extends the entity model of FOsUserBundle (FOS\UserBundle\Entity\User), as they recommend it.
Then I'd like to get all the users I have and pass them to twig as a json:
$em = $this->getDoctrine()->getManager();
$user_array = em->getRepository('MyBundle:user')->findByCustomer($customerID);
So I have an array which contains objects.
if I do:
json_encode($user_array);
or
json_encode($user_array[0]);
I get an empty string {}. I was at least expecting to get the array defined in the FOS User class
public function serialize()
{
return serialize(array(
$this->password,
$this->salt,
$this->usernameCanonical,
$this->username,
$this->expired,
$this->locked,
$this->credentialsExpired,
$this->enabled,
$this->id,
));
}
but it seems actually FOS doesn't implements Jsonserialize so it doesn't work.
When I change the FOS user class to implement Jsonserialize, it stops working (I can't connect anymore for example...).
Is there any way to get this work with FOS ?
All fields in the FriendsOfSymfony User entity are protected.
Which means you can simply reference them in the User class that is extending it just as you would a normal field.
This also means you can add another method in your own User class that will return an json encoded array containing all the values of the User.
Example of this would be:
public function json_encode()
{
return json_encode(array(
$this->password,
$this->salt,
$this->usernameCanonical,
$this->username,
$this->expired,
$this->locked,
$this->credentialsExpired,
$this->enabled,
$this->id,
));
}
You can't just simply json encode the whole array so the way to do it is:
$jsonEncodedUserArray = array();
foreach($user_array as $user) {
$jsonEncodedUser = $user->json_encode();
array_push($jsonEncodedUserArray, $jsonEncodedUser);
}