Duplicated value in my JSON response in Laravel - php

I created method show() in controller to return name and timestamps + all permissions this role has. Spatie/Laravel-Permission is responsible for associating users with permissions and roles in my API.
public function show($name)
{
$role = Role::findByName($name);
return response()->json([
$role,
$role->permissions
]);
}
Output:
[
{
"name": "root",
"created_at": "2019-04-08 19:41:49",
"updated_at": "2019-04-08 19:41:49",
"permissions": [
{
"name": "users.store"
},
{
"name": "users.destroy"
},
{
"name": "users.show.id"
},
{
"name": "users.update.id"
},
{
"name": "users.show.name"
},
{
"name": "users.update.name"
}
]
},
[
// This is duplicate
{
"name": "users.store"
},
{
"name": "users.destroy"
},
{
"name": "users.show.id"
},
{
"name": "users.update.id"
},
{
"name": "users.show.name"
},
{
"name": "users.update.name"
}
]
]
As you can see there is useless duplicate of permissions.
But if I will remove $role from my response
return response()->json([
// $role,
$role->permissions
]);
Everything is fine
[
[
{
"name": "users.store"
},
{
"name": "users.destroy"
},
{
"name": "users.show.id"
},
{
"name": "users.update.id"
},
{
"name": "users.show.name"
},
{
"name": "users.update.name"
}
]
]
And when I want to return only $role without permissions like this:
return response()->json([
$role,
// $role->permissions
]);
My output is:
[
{
"name": "root",
"created_at": "2019-04-08 19:41:49",
"updated_at": "2019-04-08 19:41:49"
}
]

When you call $role->permissions in your response, permissions are loaded into the $role object, and then serialized to json. So basically you're sending $role object with loaded permissions, and you're also sending permissions themselves.
Try:
public function show($name)
{
$role = Role::findByName($name)->load('permissions');
return response()->json([
$role
]);
}

you can use Eloquent: API Resources here
public function show($name)
{
return new RoleResource(Role::findByName($name));
}
and RoleResource like this:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class RoleResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'name' => $this->name,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'permissions' => $this-> permissions,
];
}
}

Related

Why is this ResourceCollection not working?

ResourceCollection is not providing a ResourceCollection
I'm trying to provide a collection of my tables to display for a Bulk (index) or all my tables in the database...
There appears to be a bug causing the ResourceCollection to not work, What should I check?
Resource\Order.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class Order extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
];
}
}
ResourceCollection is returning this incorrect Json response?
// 20191017103310
// http://domain.test/api/middleware/orders/bulkindex
[
{
"data": {
"connection": {
},
"grammar": {
},
"processor": {
},
"bindings": {
"select": [
],
"from": [
],
"join": [
],
"where": [
],
"having": [
],
"order": [
],
"union": [
],
"unionOrder": [
]
},
"aggregate": null,
"columns": null,
"distinct": false,
"from": "orders",
"joins": null,
"wheres": [
],
"groups": null,
"havings": null,
"orders": null,
"limit": null,
"offset": null,
"unions": null,
"unionLimit": null,
"unionOffset": null,
"unionOrders": null,
"lock": null,
"operators": [
"=",
"<",
">",
"<=",
">=",
"<>",
"!=",
"<=>",
"like",
"like binary",
"not like",
"ilike",
"&",
"|",
"^",
"<<",
">>",
"rlike",
"not rlike",
"regexp",
"not regexp",
"~",
"~*",
"!~",
"!~*",
"similar to",
"not similar to",
"not ilike",
"~~*",
"!~~*"
],
"useWritePdo": false
}
},
{
"data": {
"connection": {
},
"grammar": {
},
"processor": {
},
"bindings": {
"select": [
],
"from": [
],
"join": [
],
...
ETC...
Please note I can change it to a JsonResource and it works as expected...
EDIT: api.php (route)
Route::get('/orders/bulkindex/', function () {
return OrderResource::collection(Order::all());
});
EDIT:
use App\Http\Resources\Cart as CartResource;
return [
'data' => $this->collection,
'carts' => CartResource::collection($this->carts), // CODE DOES NOT WORK
];
I get this error message:
Property [carts] does not exist on this collection instance.
Resources\Cart.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class Cart extends ResourceCollection
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}
Pass an Eloquent Model collection to the constructor of the resource directly
Route::get('/orders/bulkindex/', function () {
return OrderResource(Order::all());
});
From the docs
After defining your resource collection, it may be returned from a route or controller:
use App\Http\Resources\UserCollection;
use App\User;
Route::get('/users', function () {
return new UserCollection(User::all());
});
Do not call the static method ::collection()
Hope this helps

How to read all product of sub categories

I need to return products of a particular category along with the following products in the subcategory of the category and I use laravel polymorphic for this work, can you help me how to return products?
I use from a recursive function for this work and this work and return to me but Not optimal
Product.php
/**
* Get all of the owning product_able models.
*/
public function product_able()
{
return $this->morphTo();
}
Category.php
/**
* Get the category's product.
*/
public function product()
{
return $this->morphOne(Product::class, 'product_able');
}
/**
* Get all of the categories for the children.
*/
public function children()
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* Get the category that owns the parent.
*/
public function parent()
{
return $this->belongsTo(self::class, 'parent_id');
}
CategoryController.php
public function detail()
{
$products = new Collection();
$category = Category::ofUuid(request('category_uuid'))->ofType(config('constants.category.type.main'))->active()->first();
$products->push($category->product);
$subCategory = CategoryDetailResource::collection($category->children);
$products->push(self::check($category));
$productsFlatten = $products->flatten();
$productsFilterNull = Utility::filterNullValue($productsFlatten);
$productsPagination = Utility::paginate_collection($productsFilterNull, 15);
$productsResource = ProductResource::collection($productsPagination);
return response()->json([
'sub_categories' => $subCategory,
'products' => $productsResource
]);
}
public function check($categories)
{
$data = [];
foreach ($categories->children as $category) {
if ($category->product) {
if ($category->product->status == config('constants.product.status.active'))
$data[] = [
'product' => $category->product,
'children' => self::check($category),
];
$data[] = [
'children' => self::check($category),
];
}
}
return $data;
}
CategoryResource.php
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'uuid' => $this->uuid ?? '',
'title' => $this->describe->title ?? '',
'path' => $this->file->path ?? config('constants.default.category.image'),
'children' => CategoryDetailResource::collection($this->children()->ofType(0)->get() ?? ''),
];
}
my output:
{
"sub_categories": [
{
"uuid": "afcac5f8-95d4-42f5-af7e-b7db943c5d09",
"title": "other",
"path": "https://dkstatics-public.digikala.com/digikala-products/2326879.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": [
{
"uuid": "c7b5d0f8-25af-437e-ac7e-4103c8bc1960",
"title": "cover",
"path": "https://dkstatics-public.digikala.com/digikala-products/5157933.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": []
},
{
"uuid": "9cd0c171-e46d-4e1d-b3b4-5c71a79fdd39",
"title": "power",
"path": "https://dkstatics-public.digikala.com/digikala-products/834627.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": []
},
{
"uuid": "f6b728a7-674f-44b8-9a8c-70d891283af7",
"title": "headphone",
"path": "https://dkstatics-public.digikala.com/digikala-products/2737272.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": []
},
{
"uuid": "a8160991-9750-4473-9252-dc4b53eef62f",
"title": "holder",
"path": "https://dkstatics-public.digikala.com/digikala-products/2027918.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": [
{
"uuid": "3216cd11-ba0e-431f-96b1-6665b9b6b420",
"title": "car",
"path": "https://dkstatics-public.digikala.com/digikala-products/4635214.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": []
}
]
}
]
},
{
"uuid": "a017d012-313c-47d9-8146-965ba0fb4bb3",
"title": "mobile",
"path": "https://dkstatics-public.digikala.com/digikala-products/4560689.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": []
},
{
"uuid": "eff6c9ff-7f1a-401b-ba90-df2b3e3b3567",
"title": "camera",
"path": "https://dkstatics-public.digikala.com/digikala-products/266661.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": []
},
{
"uuid": "a935ebb0-5392-4d56-b30d-942080ae1957",
"title": "laptop",
"path": "https://dkstatics-public.digikala.com/digikala-products/5177558.jpg?x-oss-process=image/resize,m_lfit,h_500,w_500/quality,q_80",
"children": []
}
],
"products": [
null
]
}

How do I override the REST serializer in Yii2?

I'm building a REST API with Yii2. Normally the request response looks something like this:
{
"items": [
{
"id": 1,
...
},
{
"id": 2,
...
},
...
],
"_links": {
"self": {
"href": "http://localhost/users?page=1"
},
"next": {
"href": "http://localhost/users?page=2"
},
"last": {
"href": "http://localhost/users?page=50"
}
},
"_meta": {
"totalCount": 1000,
"pageCount": 50,
"currentPage": 1,
"perPage": 20
}
}
I want to override the serializer so that the fields contained in the "_meta" array are instead included in the root of the array, i.e. the same level as "items" and "_links". How and where do I do that?
Thank you.
According to the documentation you create a new Serializer class. So, basically, you extend yii\rest\Serializer and rewrite the serialize() method. Then you set your custom serializer for your controller.
class MySerializer extends Serializer
{
public function serialize($data)
{
$d = parent::serialize($data);
$m = $d['_meta'];
unset($d['_meta']);
return array_merge($d, $m);
}
}
class MyController extends ActiveController
{
public $serializer = [
'class' => 'yii\rest\MySerializer',
'collectionEnvelope' => 'items',
];
}

ElasticSearch match combination in array

I'm implementing ElasticSearch into my Laravel application using the php package from ElasticSearch.
My application is a small jobboard and currently my job document is looking like this:
{
"_index":"jobs",
"_type":"job",
"_id":"19",
"_score":1,
"_source":{
"0":"",
"name":"Programmer",
"description":"This is my first job! :)",
"text":"Programming is awesome",
"networks":[
{
"id":1,
"status":"PRODUCTION",
"start":"2015-02-26",
"end":"2015-02-26"
},
{
"id":2,
"status":"PAUSE",
"start":"2015-02-26",
"end":"2015-02-26"
}
]
}
}
As you can see a job can be attached to multiple networks. In my search query I would like to include WHERE network.id == 1 AND network.status == PRODUCTION.
My current query looks like this, however this returns documents where it has a network of id 1, if it has any network of status PRODUCTION. Is there anyway i can enforce both to be true within one network?
$query = [
'index' => $this->index,
'type' => $this->type,
'body' => [
'query' => [
'bool' => [
'must' => [
['networks.id' => 1]],
['networks.status' => 'PRODUCTION']]
],
'should' => [
['match' => ['name' => $query]],
['match' => ['text' => $query]],
['match' => ['description' => $query]],
],
],
],
],
];
You need to specify that the objects in the networks array should be stored as individual objects in the index, this will allow you to perform a search on individual network objects. You can do so using the nested type in Elasticsearch.
Also, if you doing exact matches it is better to use a filter rather than a query as the filters are cached and always give you better performance than a query.
Create your index with a new mapping. Use the nested type for the networks array.
POST /test
{
"mappings": {
"job": {
"properties": {
"networks": {
"type": "nested",
"properties": {
"status": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
}
}
}
}
Add a document:
POST /test/job/1
{
"0": "",
"name": "Programmer",
"description": "This is my first job! :)",
"text": "Programming is awesome",
"networks": [
{
"id": 1,
"status": "PRODUCTION",
"start": "2015-02-26",
"end": "2015-02-26"
},
{
"id": 2,
"status": "PAUSE",
"start": "2015-02-26",
"end": "2015-02-26"
}
]
}
As you have a nested type you will need to use a nested filter.
POST /test/job/_search
{
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"nested": {
"path": "networks",
"filter": {
"bool": {
"must": [
{
"term": {
"networks.id": "1"
}
},
{
"term": {
"networks.status.raw": "PRODUCTION"
}
}
]
}
}
}
}
}
}
}

How to build nested responses in an Apigility driven application with a ZfcBase-DbMapper based model?

I'm developing a RESTful web application -- Apigility driven and based on the Zend Framework 2. For the model layer I'm using the ZfcBase DbMapper. The model essentially consists of two entities: Project and Image (1:n) and is currently implemented like this:
ProjectCollection extends Paginator
ProjectEntity
ProjectMapper extends AbstractDbMapper
ProjectService implements ServiceManagerAwareInterface
ProjectServiceFactory implements FactoryInterface
The same structure for Image.
When the resource (/projects[/:id]) is requested, the responsed project entity/entities should contain a list of its/their Image entities.
So, how can/should this 1:n structure be implemented?
Subquestions:
Does [DbMapper] provide some "magic" for retrieving such tree structures "automatically" without to write JOINs (or use an ORM)?
Does [Apigility] provide some "magic" for building nested responses?
{
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects?page=1"
},
"first": {
"href": "http://myproject-api.misc.loc/projects"
},
"last": {
"href": "http://myproject-api.misc.loc/projects?page=1"
}
},
"_embedded": {
"projects": [
{
"id": "1",
"title": "project_1",
"images": [
{
"id": "1",
"title": "image_1"
},
{
"id": "2",
"title": "image_2"
}
],
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
},
{
"id": "2",
"title": "project_2",
"images": [
{
"id": "3",
"title": "image_3"
},
{
"id": "4",
"title": "image_4"
}
],
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
}
]
},
"page_count": 1,
"page_size": 25,
"total_items": 1
}
EDIT
The output I'm currentliy getting is:
/projects/:id
{
"id": "1",
"title": "...",
...
"_embedded": {
"images": [
{
"id": "1",
"project_id": "1",
"title": "...",
...
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/images/1"
}
}
},
{
"id": "2",
"project_id": "1",
"title": "...",
...
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/images/2"
}
}
},
{
"id": "3",
"project_id": "1",
"title": "...",
...
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/images/3"
}
}
}
]
},
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
}
So it works for one single object. But not for collections, where single items include futher collections:
/projects
{
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects?page=1"
},
"first": {
"href": "http://myproject-api.misc.loc/projects"
},
"last": {
"href": "http://myproject-api.misc.loc/projects?page=24"
},
"next": {
"href": "http://myproject-api.misc.loc/projects?page=2"
}
},
"_embedded": {
"projects": [
{
"id": "1",
"title": "...",
... <-- HERE I WANT TO GET ["images": {...}, {...}, {...}]
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
},
{
"id": "2",
"title": "...",
... <-- HERE I WANT TO GET ["images": {...}, {...}, {...}]
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/2"
}
}
},
{
"id": "3",
"title": "...",
... <-- HERE I WANT TO GET ["images": {...}, {...}, {...}]
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/3"
}
}
}
]
},
"page_count": 24,
"page_size": 3,
"total_items": 72
}
EDIT
I edited my code and made a step to the goal.
It could not work, since my ProjectService#getProjects() was just returning the projects' data from the database, not enriched with the images:
public function getProjects() {
return $this->getMapper()->findAll();
}
edited to:
public function getProjects() {
$projects = $this->getMapper()->findAll();
foreach ($projects as $key => $project) {
$images = $this->getImageService()->getImagesForProject($project['id']);
$projects[$key]['images'] = $images;
}
return $projects;
}
and the ProjectMapper#findAll()
public function findAll() {
$select = $this->getSelect();
$adapter = $this->getDbAdapter();
$paginatorAdapter = new DbSelect($select, $adapter);
$collection = new ProjectCollection($paginatorAdapter);
return $collection;
}
edited to:
public function findAll() {
$select = $this->getSelect();
$adapter = $this->getDbAdapter();
$paginatorAdapter = new DbSelect($select, $adapter);
// #todo Replace the constants with data from the config and request.
$projects = $paginatorAdapter->getItems(0, 2);
$projects = $projects->toArray();
return $projects;
}
Now I get the wished output:
{
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects"
}
},
"_embedded": {
"projects": [
{
"id": "1",
"title": "...",
...
"_embedded": {
"images": [
{
"id": "1",
"project_id": "1",
"title": "...",
...
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/images/1"
}
}
},
{
...
},
{
...
}
]
},
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/1"
}
}
},
{
"id": "2",
"title": "...",
...
"_embedded": {
"images": [
...
]
},
...
}
]
},
"total_items": 2
}
But it's a little bit crappy solution, isn't it? What I'm actually doing, is: I'm just replacing a part of the Apigility data retrieving functionality... Anyway, I don't like this solution and want to find a better one (an "Apigility conform solution").
I have finally found a solution. (Thanks once again # poisa for his solution suggestion on GitHub.) In short, the idea is to enrich the (projects) list items with nested (image) items lists on the hydration step. I actually don't really like this way, since it's too much model logic on the hydration level for me. But it works. Here we go:
/module/Portfolio/config/module.config.php
return array(
...
'zf-hal' => array(
'metadata_map' => array(
...
'Portfolio\\V2\\Rest\\Project\\ProjectEntity' => array(
'entity_identifier_name' => 'id',
'route_name' => 'portfolio.rest.project',
'route_identifier_name' => 'id',
'hydrator' => 'Portfolio\\V2\\Rest\\Project\\ProjectHydrator',
),
'Portfolio\\V2\\Rest\\Project\\ProjectCollection' => array(
'entity_identifier_name' => 'id',
'route_name' => 'portfolio.rest.project',
'route_identifier_name' => 'id',
'is_collection' => true,
),
...
),
),
);
Portfolio\Module
class Module implements ApigilityProviderInterface {
...
public function getHydratorConfig() {
return array(
'factories' => array(
// V2
'Portfolio\\V2\\Rest\\Project\\ProjectHydrator' => function(ServiceManager $serviceManager) {
$projectHydrator = new ProjectHydrator();
$projectHydrator->setImageService($serviceManager->getServiceLocator()->get('Portfolio\V2\Rest\ImageService'));
return $projectHydrator;
}
),
);
}
...
}
Portfolio\V2\Rest\Project\ProjectHydrator
namespace Portfolio\V2\Rest\Project;
use Zend\Stdlib\Hydrator\ClassMethods;
use Portfolio\V2\Rest\Image\ImageService;
class ProjectHydrator extends ClassMethods {
/**
* #var ImageService
*/
protected $imageService;
/**
* #return ImageService the $imageService
*/
public function getImageService() {
return $this->imageService;
}
/**
* #param ImageService $imageService
*/
public function setImageService(ImageService $imageService) {
$this->imageService = $imageService;
return $this;
}
/*
* Doesn't need to be implemented:
* the ClassMethods#hydrate(...) handle the $data already as wished.
*/
/*
public function hydrate(array $data, $object) {
$object = parent::hydrate($data, $object);
if ($object->getId() !== null) {
$images = $this->imageService->getImagesForProject($object->getId());
$object->setImages($images);
}
return $object;
}
*/
/**
* #see \Zend\Stdlib\Hydrator\ClassMethods::extract()
*/
public function extract($object) {
$array = parent::extract($object);
if ($array['id'] !== null) {
$images = $this->imageService->getImagesForProject($array['id']);
$array['images'] = $images;
}
return $array;
}
}
Portfolio\V2\Rest\Project\ProjectMapperFactory
namespace Portfolio\V2\Rest\Project;
use Zend\ServiceManager\ServiceLocatorInterface;
class ProjectMapperFactory {
public function __invoke(ServiceLocatorInterface $serviceManager) {
$mapper = new ProjectMapper();
$mapper->setDbAdapter($serviceManager->get('PortfolioDbAdapter_V2'));
$mapper->setEntityPrototype($serviceManager->get('Portfolio\V2\Rest\Project\ProjectEntity'));
$projectHydrator = $serviceManager->get('HydratorManager')->get('Portfolio\\V2\\Rest\\Project\\ProjectHydrator');
$mapper->setHydrator($projectHydrator);
return $mapper;
}
}
Portfolio\V2\Rest\Project\ProjectMapper
namespace Portfolio\V2\Rest\Project;
use ZfcBase\Mapper\AbstractDbMapper;
use Zend\Paginator\Adapter\DbSelect;
use Zend\Db\ResultSet\HydratingResultSet;
class ProjectMapper extends AbstractDbMapper {
...
/**
* Provides a collection of all the available projects.
*
* #return \Portfolio\V2\Rest\Project\ProjectCollection
*/
public function findAll() {
$resultSetPrototype = new HydratingResultSet(
$this->getHydrator(),
$this->getEntityPrototype()
);
$select = $this->getSelect();
$adapter = $this->getDbAdapter();
$paginatorAdapter = new DbSelect($select, $adapter, $resultSetPrototype);
$collection = new ProjectCollection($paginatorAdapter);
return $collection;
}
/**
* Provides a project by ID.
*
* #param int $id
* #return \Portfolio\V2\Rest\Project\ProjectEntity
*/
public function findById($id) {
$select = $this->getSelect();
$select->where(array(
'id' => $id,
));
$entity = $this->select($select)->current();
return $entity;
}
...
}
As I already said in my post on GitHub, it would be great to get a feedback from someone from the Apigility core team, wheter this solution is "Apigility conform" and, if not, what is a better/"correct" solution.
I have no experience with db-mapper, but I think can answer question 2 for you.
If your extracted project resource (an array) has a key images that holds an object of type Hal\Collection it will automatically extract this collection and render it as you show in your Hal example.
This "magic" happens because extractEmbeddedCollection is called in the renderEntity method in Hal.php on line 563.
EDIT
You write that you want:
["images": {...}, {...}, {...}]
But what you should actually aim for is this:
{
"id": "2",
"title": "...",
"_links": {
"self": {
"href": "http://myproject-api.misc.loc/projects/2"
}
},
"_embedded": {
"images": [
{...},
{...},
{...}
]
}
}
How do you extract your objects? Did you register a hydrator in your metadata map?
You should try to return something like this:
use ZF\Hal\Collection
...
$images = new Collection($arrayOfImages);
$project['images'] = $images;
then it should work (I don't know how else to explain it).

Categories