Nested JSON with Eloquent - php

I'm new in Laravel and I want to create an API . I just want to know on how to build a JSON response like this
JSON RESPONSE:
[
{ //THIS IS MODEL FOR Industry
id: "1",
value: "Accounting / Finance
sectors: [ //THIS IS MODEL FOR Sectors
{
id: "1", //sector_id AS id
value: "Accounting"
},
{
id: "3",
value: "Finance"
}
],
id: "2",
value: "Audit / Tax"
sectors: [
{
id: "4",
value: "Audit"
},
{
id: "5",
value: "Tax"
}
]
}
]
The database in my industries contains
| id | value |
| 1 | Accounting/Finance |
| 2 | Audit/Tax |
while my database in industry_sectors contains
| sector_id | industry_id | name |
| 1 | 1 | Accounting |
| 3 | 1 | Finance |
| 4 | 2 | Audit |
| 5 | 2 | Tax |
Below are my partial code for models.
MODELS
Industries.php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Industries extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
public $primaryKey = 'id';
public $table = 'industries';
public $timestamps = false;
protected $guarded = [];
}
IndustrySectors.php
namespace App\Models;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class IndustrySectors extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
public $primaryKey = 'sector_id';
public $table = 'industry_sectors';
public $timestamps = false;
protected $guarded = [];
}
I want to use the API as a GET method and this is my partial code but it just print the whole database in json in Industry Sectors table.
public function getIndustry(){
$flight = IndustrySectors::all();
return response()->json($flight);
}

You need to declare the relationship between industries and industry_sectors first by declaring it
// in Industries.php
public function industrySectors() {
return $this->hasMany('App\IndustrySectors');
}
// in IndustrySectors.php
public function industries() {
return $this->belongsTo('App\Industries');
}
Also be sure to read Laravel Eloquent Relationship Documentation carefully

Related

Laravel global access with polymorphic relations

In my application I have two columns employable_id && employable_type in almost every table, which are used for storing the information about the user who has created the record.
Something like this:
subscribers: products:
------------------------------------ ------------------------------------
id | employable_id | employable_type id | employable_id | employable_type
------------------------------------ ------------------------------------
1 | 1 | App\Company 1 | 1 | App\Company
2 | 1 | App\Company 2 | 1 | App\Employee
3 | 1 | App\Employee 3 | 3 | App\Employee
4 | 1 | App\Employee 4 | 8 | App\Employee and more...
Subscriber.php
class Subscriber extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
}
I have created an accessor to combine polymorphic relationship's columns, which is working fine.
class Subscriber extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
/**
* Accessor
*/
public function getAddedByAttribute()
{
return $this->employable->name . ' [ ' . $this->employable->designation . ' ]';
}
}
class Product extends Model
{
/**
* Polymorphic relations
*/
public function employable()
{
return $this->morphTo();
}
/**
* Accessor
*/
public function getAddedByAttribute()
{
return $this->employable->name . ' [ ' . $this->employable->designation . ' ]';
}
}
I am trying to make the getAddedByAttribute method global instead of adding in every model class.
How can I do that..?
you can make a trait and use it in every model need this function

Product with Multiple Product Related using Laravel Model HasMany() & BelongsTo?

As of now I have 2 different Model
ProductModel.php Which is my list of products.
ProductRelated.php Which is the assigned related product per specific product.
The scenario is,
If I have id = 1 in my ProductModel. id (primary key of Product Model)
and in my ProductRelated.php I have this
prodID (Which is the equivalent of id (primary key of Product Model)
This will serve to identify which specific product id the related products are being assiged.
prodRelatedID this is the products related to that product.
So my ProductModel will have this data
id | prodTitle | prodDesc | More fields here...
1 | Product 1 | Description | More FIeld value here ..
2 | Product 2 | Description | More FIeld value here ..
3 | Product 3 | Description | More FIeld value here ..
In my ProductRelated will have this data
id | prodID | prodRelatedID | More fields here...
1 | 1 | 2 | More FIeld value here ..
2 | 1 | 3 | More FIeld value here ..
Which means, id = 1 from ProductModel has 2 different prodRelatedID
and those are the 2 and 3
My idea by doing this is to use hasMany()
Here's my ProductModel
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\User;
class ProductModel extends Model
{
public $timestamps = true;
protected $table = 'product';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'id',
'prodTitle',
'prodDesc',
'prodCategory',
'attachment',
'prodSize',
'prodPrice',
'created_by',
'is_best_seller',
'prod_included',
'prod_instruction',
];
}
My ProductRelated
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\User;
class ProductRelated extends Model
{
public $timestamps = true;
protected $table = 'product_related';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'id',
'prodID',
'prodRelatedID',
'created_by'
];
}
If you just want to get all related products per specific product, I do not think you need ProductRelated.
In your ProductModel
public function relatedProducts()
{
return $this->belongsToMany(ProductModel::class, 'product_related', 'prodID', 'prodRelatedID');
}
And then you should be able to get all records like this:
$relatedProducts = ProductModel::find(1)->relatedProducts()->get();

MySQL (Laravel Eloquent) - Get record when two columns have two or more values at the same time

I have this database that I got from this post that manages products and its variants:
+---------------+ +---------------+
| PRODUCTS |-----< PRODUCT_SKUS |
+---------------+ +---------------+
| #product_id | | #product_id |
| product_name | | #sku_id |
+---------------+ | sku |
| | price |
| +---------------+
| |
+-------^-------+ +------^------+
| OPTIONS |------< SKU_VALUES |
+---------------+ +-------------+
| #product_id | | #product_id |
| #option_id | | #sku_id |
| option_name | | #option_id |
+---------------+ | value_id |
| +------v------+
+-------^-------+ |
| OPTION_VALUES |-------------+
+---------------+
| #product_id |
| #option_id |
| #value_id |
| value_name |
+---------------+
The problem is, that I don't know how would I get the SKU at the moment that a user selects the options of the product he wants:
SKU_VALUES
==========
product_id sku_id option_id value_id
---------- ------ --------- --------
1 1 1 1 (W1SSCW; Size; Small)
1 1 2 1 (W1SSCW; Color; White)
1 2 1 1 (W1SSCB; Size; Small)
1 2 2 2 (W1SSCB; Color; Black)
Let's suppose that the user selects the product with ID 1 and the options size-small and color-black, how am I able to get the sku_id (in this case I would want value 2 from sku_id) in order to get the price that's inside the PRODUCT_SKUS table.
I cannot do something like this for obvious reasons:
SELECT sku_id FROM SKU_VALUES
WHERE (SKU_VALUES.option_id = 1 AND SKU_VALUES.value_id = 1)
AND (SKU_VALUES.option_id = 2 AND SKU_VALUES.value_id = 2)
NOTE that it seems that I would need to append the same number of conditions (or whatever I need) as the number of options that are available from a product, in this case there are just 2 rows because the product has 2 options (size and color), but the product may have "n" options.
I would appreciate if someone could guide me for this query and if it's possible doing it with Laravel Eloquent instead of using RAW query.
The models I have created are the following:
"Product" Model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Producto extends Model
{
protected $table = 'productos';
protected $fillable = [
'nombre',
'descripcion'
];
public function opciones(){
return $this->hasMany('App\Models\OpcionProducto', 'producto_id');
}
public function skus(){
return $this->hasMany('App\Models\ProductoSku', 'producto_id');
}
}
"Options" Model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\HasCompositePrimaryKey;
class OpcionProducto extends Model
{
use HasCompositePrimaryKey;
protected $table = 'productos_opciones';
protected $primaryKey = array('producto_id', 'opcion_id');
protected $fillable = [
'producto_id',
'opcion_id',
'nombre_opcion',
'valor'
];
public function producto(){
return $this->belongsTo('App\Models\Producto', 'producto_id');
}
public function valores(){
return $this->hasMany('App\Models\OpcionValorProducto', 'opcion_id', 'opcion_id');
}
public function skusValores(){
return $this->hasMany('App\Models\SkuValor', 'opcion_id', 'opcion_id');
}
}
"OptionValues" Model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\HasCompositePrimaryKey;
class OpcionValorProducto extends Model
{
use HasCompositePrimaryKey;
protected $primaryKey = array('producto_id', 'opcion_id', 'valor_id');
protected $table = 'productos_opciones_valores';
protected $fillable = [
'producto_id',
'opcion_id',
'valor_id',
'valor_variacion',
'valor'
];
public function producto(){
return $this->belongsTo('App\Models\Producto', 'producto_id');
}
public function opcion(){
return $this->belongsTo('App\Models\OpcionProducto', 'opcion_id', 'opcion_id');
}
}
"Product_SKUS" model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\HasCompositePrimaryKey;
class ProductoSku extends Model
{
use HasCompositePrimaryKey;
protected $primaryKey = array('producto_id', 'sku_id');
protected $table = 'productos_skus';
protected $fillable = [
'producto_id',
'sku_id',
'imagen_id',
'precio',
'stock',
'sku'
];
public function producto(){
return $this->belongsTo('App\Models\Producto', 'producto_id');
}
public function valoresSku(){
return $this->hasMany('App\Models\SkuValor', 'sku_id');
}
}
}
"SKU_VALUES" model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\HasCompositePrimaryKey;
class SkuValor extends Model
{
use HasCompositePrimaryKey;
protected $primaryKey = array('producto_id', 'sku_id', 'opcion_id');
protected $table = 'valores_skus';
protected $fillable = [
'producto_id',
'sku_id',
'opcion_id',
'valor_id',
];
public function producto(){
return $this->belongsTo('App\Models\Producto', 'producto_id');
}
public function opcion(){
return $this->belongsTo('App\Models\OpcionProducto', 'opcion_id', 'opcion_id');
}
public function sku(){
return $this->belongsTo('App\Models\ProductoSku', 'sku_id', 'sku_id');
}
}
After going through your question, this is the code I came up with. Of course this is un-tested. Please give this a shot.
$skuValor = SkuValor::with('producto', 'opcion.valores', 'sku')
->whereHas('producto', function ($q) use ($request) {
$q->where('id', $request->get('product_id')); // id: 1
})
->whereHas('opcion', function ($q) use ($request) {
$q->whereIn('id', $request->get('option_ids')) // id: [1, 2] where 1=size, 2=color
->whereHas('valores', function($q2) use ($request) {
$q2->whereIn('id', $request->get('value_ids')); // id: [1, 3] where 1=small, 3=pink
});
})
->get();
$skuValor->sku->id; // sky id
with() : This is called Eager Loading. Load some relationships when retrieving a model.
whereHas() : Querying Relationship Existence. This method allow you to add customized constraints to a relationship constraint.
use() : Passing data so that inner (that particular) query can use it. Notice we have used 1 for opcion and another one for valores.
whereIn() : This method verifies that a given column's value is contained within the given array.
Comment if you get stuck.
Here is a solution using pure SQL.
This is your attempt using a raw query:
select sku_id
from sku_values
where (option_id = 1 and value_id = 1) and (option_id = 2 and value_id = 2)
This doesn't work because you need to search across rows sharing the same sku_id rather than on each row. This suggest aggregation:
select sku_id
from sku_values
where (option_id, value_id) in ((1, 1), (2, 2)) -- either one combination or the other
group by sku_id
having count(*) = 2 -- both match
You can easily extend the query for more options by adding more combinations in the where clause predicate and incrementing the target count in the having clause accordingly. For example, this filters on 4 criterias:
select sku_id
from sku_values
where (option_id, value_id) in ((1, 1), (2, 2), (3, 10) (12, 17))
group by sku_id
having count(*) = 4
It is also possible to filter by option names and values by adding more joins in the subquery:
select sv.sku_id
from sku_values sv
inner join options o
on o.product_id = sv.product_id
and o.option_id = sv.option_id
inner join option_values ov
on ov.product_id = sv.product_id
and ov.option_id = sv.option_id
and ov.value_id = sv.value_id
where (o.option_name, ov.value_name) in (('Size', 'Small'), ('Color', 'Black'))
group by sv.sku_id
having count(*) = 2
Now, say you want to get the corresponding product name and price: you can join the above query with the relevant tables.
select p.product_name, ps.price
from products p
inner join product_skus ps
on ps.product_id = p.product_id
inner join (
select sv.sku_id
from sku_values sv
inner join options o
on o.product_id = sv.product_id
and o.option_id = sv.option_id
inner join option_values ov
on ov.product_id = sv.product_id
and ov.option_id = sv.option_id
and ov.value_id = sv.value_id
where (o.option_name, ov.value_name) in (('Size', 'Small'), ('Color', 'Black'))
group by sv.sku_id
having count(*) = 2
) x
on x.sku_id = ps.sku_id
This is possible using Laravel subqueries but I would suggests the use create a RAW query to get those values you need and then implement it with Laravel Query builder.
For queries that involves many tables Eloquent can result some kind of unefficient and it will take you to write unmaintainable code.
Extra suggestion, even when you your table names are in other languages it is recomendable create full english models, you can specify what field is related to what property.

How can I do a belongsTo() relation in Laravel?

Here is my model User.php
class User extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password', 'cell_phone', 'province_id', 'city_id', 'job'
];
protected $hidden = [
'password', 'remember_token',
];
public function city()
{
return $this->belongsTo('City');
}
}
And here is a part of my controller:
$user_info = User::find(Auth::user()->id);
dd($user_info->city);
And it throws this:
"Undefined class constant 'city'"
How can I fix the problem?
Tables structure:
// users
+----+--------+---------+----------
| id | name | city_id | ...
+----+--------+---------+----------
| 1 | Jack | 5 | ...
// city
+----+--------+
| id | name |
+----+--------+
| 1 | Tehran |
You need to pass full class name:
return $this->belongsTo('App\City');
Or:
return $this->belongsTo(City::class);
Also, you don't need to do that:
$user_info = User::find(Auth::user()->id);
Because Auth::user() already has user instance loaded, you can just get city instance with:
Auth::user()->city
Basically , you don't provide full path of your class name of city that's why you belongs To relation not working properly.
for example your code must be
return $this->belongs To('App/City');
Because your in app folder you have city class module and others.

Yii2 Relational databases confusion

So i am working with Yii2 and i'm trying to get what i think is relation databases in order. What im trying to do is have multiple tables return one set of data with the Yii2 rest api.
Ive been trying to find exactly what i need to do but im a bit confused. I believe i have to setup foreign keys and link them somehow but that's where i get lost. Also i think once the database portion is setup, does the gii tool pick up on the 'relationships' when i build a new model? I think ill have to manually build the new model/controller instead of using gii, that sends all templates to the 'frontend' directory and not my versioned 'module/v1' directory
----EDITED for changes----
table 'state':
- country_id is the index
+--------------------------------------+---------------------------------+
| Column | Internal Relations | Foreign Key constraint (INNODB) |
+--------------------------------------+---------------------------------+
| country_id | yii2.country.country_id | yii2.ountry.country_id |
+------------------------------------------------------------------------+
+-------------+------------------+----------------------+
| id (int, AI)| country_id (int) | state_name (varchar) |
+-------------+------------------+----------------------+
| 0 | 10 | Texas |
| 1 | 10 | New York |
| 2 | 20 | Glasgow |
+-------------+------------------+----------------------+
table 'country':
- country_id is the Primary Key
- column to display in relation-view is country_name
+------------------+--------------------------+
| country_id (int) | country_name (varchar) |
+------------------+--------------------------+
| 10 | United States of America |
| 20 | Germany |
+------------------+--------------------------+
So my goal is to have my api GET request of: localhost/yii2/api/web/vi/states/
And get a response of:
{
"success": true,
"data": [
{
"id": 1,
"country_id": 10,
"state_name": "Texas"
},
{
"id": 2,
"country_id": 10,
"state_name": "New York"
},
{
"id": 3,
"country_id": 20,
"state_name": "Glasgow"
}
]
}
}
model (State.php):
<?php
namespace api\modules\v1\models;
use Yii;
/**
* This is the model class for table "state".
*
* #property integer $country_id
* #property string $state_name
*
* #property Country $country
*/
class State extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'state';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['country_id', 'state_name'], 'required'],
[['country_id'], 'integer'],
[['state_name'], 'string', 'max' => 55]
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'country_id' => 'Country ID',
'state_name' => 'State Name',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getCountry()
{
return $this->hasOne(Country::className(), ['country_id' => 'country_id']);
}
}
controller (StateController.php):
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
/**
* Country Controller API
*
*/
class StateController extends ActiveController
{
public $modelClass = 'api\modules\v1\models\State';
}
Setup the foreign keys in the database and use gii to generate the models and it will link them automatically according to your relation and then you can use it like this ...
$states= State::find()->with('countries')->all();
this code will get you the states with their countries
I believe i have to setup foreign keys and link them somehow but
that's where i get lost. Also i think once the database portion is
setup, does the gii tool pick up on the 'relationships' when i build a
new model?
Yes
I think ill have to manually build the new model/controller instead of
using gii, that sends all templates to the 'frontend' directory and
not my versioned 'module/v1' directory
You can make Gii send the files wherever you want, you just have to put the namespace in the proper place.

Categories