API Platform return deep association data - php

I would like to request data from API Platform such as I can get a list of book categories when I request a list of shops.
So I have 3 entities with ManyToMany associations: Shop <--> Book <--> Category
When I call the GET /shops endpoint, I'd like to get a list of shops and for each shop, a list of categories (depending on the books available in the shop). Something like:
{
"#context": "/contexts/Shop",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/shops/1",
"#type": "Shop",
"name": "Shop 1",
"categories": [
{
"id": "/categories/12"
"#type": "Category"
"name": "Novel"
},
{
"id": "/categories/15"
"#type": "Category"
"name": "SF"
}
]
}
]
}
For now, I did it with a calculated field on the Shop entity:
#[Groups(['shop:collection:get'])]
public function getCategories(): array
{
$categories = [];
foreach ($this->getBooks() as $book) {
foreach ($book->getCategories() as $category) {
$categories[$category->getId()] = $category;
}
}
return array_values($categories);
}
I also created a Doctrine Extension to add joins to the SQL query when I request a collection of shops to improve performance but I am not sure if that's a good practice...
Is there any other way to get the same JSON result?
I also tried to use addSelect() with the QueryBuilder inside the Doctrine Extension but then the shop data are stored as a sub object:
{
"#context": "/contexts/Shop",
"#type": "hydra:Collection",
"hydra:member": [
{
"0": {
"#id": "/shops/1",
"#type": "Shop",
"name": "Shop 1",
}
}
]
}

Related

Problem on GraphQL on API Platform with an entity having a relation with itself

In my diagram, I have objects that are attached to categories. These categories can also have a parent category.
When I use GraphQL to retrieve my categories that don't have parents and their subcategories (I only have one level of depth), I can't retrieve them, or the list stays empty, or I get errors.
I don't know if I missed something or if I have to do something different. I've searched everywhere on the internet, but I must not be doing it with the right terms, thanks to my bad English 😅
Example of my categories tree:
Category 1
Category 1.1
Objects...
Category 1.2
Objects...
Category 2
Objects...
First, I tried to simply display the categories with their subcategories, but the subcategories do not load. But when I load the parent category, there is no problem
My entity
#[ORM\Entity(repositoryClass: CategoryRepository::class)]
#[ApiResource]
class Category
{
...
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'categories')]
private ?self $category = null;
#[ORM\OneToMany(mappedBy: 'category', targetEntity: self::class)]
private Collection $categories;
...
}
My query
{
categories {
edges {
node {
id
label
categories {
edges {
node {
id
label
}
}
}
}
}
}
}
The result
{
"data": {
"categories": {
"edges": [
{
"node": {
"id": "/api/categories/1",
"label": "Category 1",
"categories": {
"edges": []
}
}
},
{
"node": {
"id": "/api/categories/2",
"label": "Category 2",
"categories": {
"edges": []
}
}
},
{
"node": {
"id": "/api/categories/3",
"label": "Category 1.1",
"categories": {
"edges": []
}
}
},
{
"node": {
"id": "/api/categories/4",
"label": "Category 1.2",
"categories": {
"edges": []
}
}
}
]
}
}
}
Then I tried to have less generic names for my parent category and my subcategories. And with the same request, I got this time a semantical error
My entity
#[ORM\Entity(repositoryClass: CategoryRepository::class)]
#[ApiResource]
class Category
{
...
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'childCategories')]
private ?self $parentCategory = null;
#[ORM\OneToMany(mappedBy: 'parentCategory', targetEntity: self::class)]
private Collection $childCategories;
...
}
My error
[Semantical Error] line 0, col 354 near 'category IN (SELECT': Error: Class App\\Entity\\Category has no field or association named category

Filter relationship, based on array of integers

My model has many options, multiple fields of a single option store values as an array on integers.
I want to filter based on an integer is included.
in php similare to in_array(), but the array is stored in db, and the number is key word.
$productsWithOptions = [
{
"id":2,
"name":"mac 2",
"options":[
{
"id":4,
"price":99.9,
"product_id":2,
"colors":"[4,3,2]",
"sizes":"[5,3,2]"
},
{
"id":5,
"price":99.9,
"product_id":2,
"colors":"[4,4,5]",
"sizes":"[1,2,2]"
},
{
"id":7,
"name":"mac 7",
"options":[
{
"id":19,
"price":99.9,
"product_id":7,
"colors":"[6,3,4]",
"sizes":"[5,2,5]"
},
{
"id":20,
"price":1999.9,
"product_id":7,
"colors":"[1,6,6]",
"sizes":"[5,6,2]"
},
{
"id":21,
"price":1999.9,
"product_id":7,
"colors":"[4,6,5]",
"sizes":"[5,2,2]"
}
]
}
]
$products = Product::whereHas('options', function (Builder $query) use ($arr) {
$query->where('colors','like', "%2%" );
})->get();
This is how options table looks like in db.

Laravel Eloquent - how to join a table

I working on an API for my app.
I'm trying to pull items from the database and return them in JSON object,
my items table looks like this:
Items
-id
-name
-description
-price
-currency_id
-company_id
this is how I'm getting the items:
$rows = Company::where('guid',$guid)
->first()
->items()
->orderBy('name', $sort_order);
I want to replace the currency_id with a currency object that contains all the columns of currency table
so my result will be like this:
[
{
'id':'1',
'name':'name',
'description': 'example',
'price':'100',
'currency':{
'id':'1',
'name':'usd',
'symbol': '$'
}
}
]
update:
This is my currencies table:
id
name
symbol
code
Edit 2: The user's problem was more complex than this since there was pagination and search integration with the query. Helped with https://pastebin.com/ppRH3eyx
Edit : I've tested the code. So here.
In Company model
public function items()
{
return $this->hasMany(Item::class);
}
In Item model
public function currency()
{
return $this->belongsTo(Currency::class);
}
Controller logic
$items = Company::with(['items' => function($query) use ($sort_order) {
$query->with('currency')->orderBy('name', $sort_order);
}])
->where('guid', $guid)
->first()
->items;
Result with test data
[
{
"id": 2,
"name": "Toy",
"description": "Random text 2",
"price": 150,
"company_id": 1,
"currency_id": 1,
"currency": {
"id": 1,
"name": "usd",
"symbol": "$",
"code": "USD"
}
},
{
"id": 1,
"name": "Phone",
"description": "Random text",
"price": 100,
"company_id": 1,
"currency_id": 1,
"currency": {
"id": 1,
"name": "usd",
"symbol": "$",
"code": "USD"
}
}
]
Try this.
$rows = Company::with('items.currency')
->where('guid', $guid)
->first()
->items()
->orderBy('name', $sort_order);
Try below
Make one relationship in Item Model
public function currencies() {
return $this->hasMany('App\Currency');
}
then do below in your controller
$row=Items::All()->with('currencies');

how to use json_encode to fetch data from database

I Have 2 table that I want to join them and fetch some data like this
I have one student with multiple grade
{
"student": {
"studentID": "2",
"Name": "s1",
"age": " 12",
"grade": [
{
"GradeID": "2"
},{
"GradeID": "3"
}
]
}
I fetch this data from two query in a function in my model and then use json_encode in my controller for my output
but I have this
{
"student": {
"studentID": "2",
"Name": "s1",
"age": " 12"
},
"grade": [
{
"GradeID": "2"
},{
"GradeID": "3"
}
]
}
and now I don't know how to use json_encode for the first format.
thanks
my model(student):
function get_student_id($id)
{
$student['student']=
$this->db->select('tbl_student.*')
->from('tbl_student')
->where('tbl_student.SID',$id)
->get()->row();
$student['grade']=
$this->db->select('tbl_grade.GradeID')
->from('tbl_grade')
->join('tbl_sudent','tbl_grade.StudentID=tbl_sudent.SID')
->where('tbl_student.SID',$id)
->get()->result();
return $student;
}
my controller:
public function get_student_id()
{
$id = $input['one'];
$this->load->model('student');
$temp=$this->student->get_student_id($id);
$output= json_encode($temp);
die($output);
}
You just have to structure the array you're returning from the model correctly. You're putting everything inside two subarrays called student and grade, which are inside the outer array student. Try this:
my model(student):
function get_student_id($id)
{
$student=
$this->db->select('tbl_student.*')
->from('tbl_student')
->where('tbl_student.SID',$id)
->get()->row();
$student['grade']=
$this->db->select('tbl_grade.GradeID')
->from('tbl_grade')
->join('tbl_sudent','tbl_grade.StudentID=tbl_sudent.SID')
->where('tbl_student.SID',$id)
->get()->result();
return $student;
}
I'm not totally certain you want to call get->row() on the first query, if that doesn't work try get->row_array()

Force JMS Serialiser to output an object keyed by a specific field

I have an entity Product with a one-to-many relationship to an entity Property. When I serialise a product instance using the JMS Serialiser I get the following JSON output:
{
"id": 123,
"name": "Mankini Thong",
"properties": [{
"label": "Minimal size",
"name": "min_size",
"value": "S"
}, {
"label": "Maximum size",
"name": "max_size",
"value": "XXXL"
}, {
"label": "colour",
"name": "Colour",
"value": "Office Green"
}]
}
I try to get the serialiser to serialise the properties collection as an object in which a certain field is used as key. For instance, the name field. The desired output is:
{
"id": 123,
"name": "Mankini Thong",
"properties": {
"min_size": {
"label": "Minimal size",
"value": "S"
},
"max_size": {
"label": "Maximum size",
"value": "XXXL"
},
"colour": {
"label": "Colour",
"value": "Office Green"
}
}
}
What would be the best approach to achieve this?
Ok, I figured it out:
First add a virtual property to the serialisation mapping and exclude the original properties field. My configuration is in yaml but using annotations shouldn't be that different:
properties:
properties:
exclude: true
virtual_properties:
getKeyedProperties:
serialized_name: properties
type: array<Foo\BarBundle\Document\Property>
Then I've added the getKeyedProperties method to the document class in Foo\BarBundle\Document\Article:
/**
* Get properties keyed by name
*
* Use the following annotations in case you defined your mapping using
* annotations instead of a Yaml or Xml file:
*
* #Serializer\VirtualProperty
* #Serializer\SerializedName("properties")
*
* #return array
*/
public function getKeyedProperties()
{
$results = [];
foreach ($this->getProperties() as $property) {
$results[$property->getName()] = $property;
}
return $results;
}
Now, the serialised output contains an object properties which are serialised article properties keyed by name.

Categories