How to link attribute in Yii 2 to another model's field? - php

I have Products model and ProductProperties via hasOne relation:
class Product extends ActiveRecord
{
...
public function getProductProperties()
{
return $this->hasOne(ProductProperties::class, ['product_id' => 'id']);
}
...
}
I had price attribute in Products and I want to remove it (including column in database) and to link it to price attribute of ProductProperties model.
Is it possible and how can I do that? First I tried to override attributes method like this:
public function fields()
{
return [
'price' => function () {
return ProductProperties::find(['product_id' => $this->id])->price;
}
]
...
but I'm not sure if I can assign values using arrow method. Besides, fields() method uses $this->price before it returns anything:
public function fields()
{
if ($this->price){*some manipulations with price*}
...
return [
'price',
..*other fields*
];
}
The question is How can I remove the price from model and use another model's price attribute without too much pain?

If you only want to show the price, you can do
class Product extends ActiveRecord
{
...
public function getProductProperties()
{
return $this->hasOne(ProductProperties::class, ['product_id' => 'id']);
}
public function getPrice() {
return $this->productProperties->price;
}
...
}
And use it
$product = Product::findOne(1);
echo $product->price; // this is a shortcut
echo $product->productProperties->price; // same as this which is the complete route
To save the data, you should first determine how to handle the user data collection, since you have different models and each one has its own validations.
However, if you want to save the price as a Product attribute (I don't recommend it), you could do the following
class Product extends ActiveRecord
{
public $price;
public function rules () {
return [
[['price'], 'integer'] // for massive assignment
];
}
public function afterFind()
{
parent::afterFind();
$this->price = $this->productProperties->price;
}
public function getProductProperties()
{
return $this->hasOne(ProductProperties::class, ['product_id' => 'id']);
}
public function afterSave($insert, $changedAttributes)
{
parent::afterSave($insert, $changedAttributes);
if (array_key_exists('price', $changedAttributes)) {
// You should make sure that $this->productProperties exists.
$this->productProperties->price = $this->price;
$this->productProperties->save();
}
}
...
}

Related

Create nested API

I'm trying to make an api that have lists and inside each list there is anther list inside of it called cards and the cards list is the cards of this list.
I tried to show it in index function and didn't work it was like this:
public function index()
{
// $list = List -> cards();
$list = List::cards();
return response( $list );
}
Card Model:
public function list()
{
return $this->belongsTo( List::class() );
}
Card Model:
public function cards()
{
return $this->hasMany( Card::class() );
}
What i want to output is json data like this:
"lists":[
'name':listname
'cards':[
'card one': card name,
]
]
If you use Laravel framework use Resource for response, in Resource of laravel you can load cards. For example in ListController :
public function index()
{
return ListResource::collection(List::all()->paginate());
}
And in ListResource :
public function toArray($request)
{
'cards' => CardResource::collection('cards');
}
belongsTo or hasMany accepts model name as a first argument. In your case you need to pass your model class name in your relations methods.
public function list()
{
return $this->belongsTo(List::class);
}
and
public function cards()
{
return $this->hasMany(Card::class);
}
So if you want to receive models including relations you can use with method.
return response(List::query()->with('cards'));
You can use resources.
Http\Resources\List:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class List extends JsonResource
{
public function toArray($request)
{
$cards = [];
foreach ($this->cards as $card) {
$cards[] = $card->name;
}
return [
'name' => $this->name,
'cards' => $cards,
];
}
}
Http\Controllers\ListController:
namespacce App\Http\Controllers;
use App\Http\Resources\List as ListResource;
use App\Components\List;
class ListController extends Controller
{
$lists = List::query()->get();
return ListResource::collection($lists)->response();
}

Laravel get value from my function in Model

My model Dispatch has a field invoice_id.
It is a foreign key so i can get the invoice details using the below code:
protected $fillable = [
'id',
'truck_no',
'driver_name',
'driver_phone',
'gps_details',
'invoice_id',
];
public function invoice(){
return $this->belongsTo('App\Invoice')->select('id','invoice_no','permit_id');
}
Now I want to get the value permit_id from invoice() so i can use it to get the details of the Permit.
permit_id = id of Permit model
So I use the below code to get the permit data.
public function permit(){
return $this->hasOne('App\Permit','id',$this->invoice()->permit_id);
}
Update:
My Invoice Model has :
class Invoice extends Model
{
protected $fillable = [
'id',
'invoice_no',
'invoice_date',
'permit_id',
];
public function permit(){
return $this->hasOne('App\Permit', 'id', 'permit_id');
}
}
My Permit Model has:
class Permit extends Model
{
protected $fillable = [
'permit_type',
'permit_no',
'application_no',
'supply_unit',
'supply_unit_id' ,
];
public function supplyunit(){
return $this->hasOne('App\SupplyUnit','id','supply_unit_id');
}
}
And as per suggestions i have added below code in my Dispatch Model:
class Dispatch extends Model
{
protected $fillable = [
'id',
'truck_no',
'driver_name',
'driver_phone',
'gps_details',
'invoice_id',
];
public function invoice(){
return $this->hasOne('App\Invoice','id','invoice_id');
}
public function permit(){
return $this->hasOne('App\Permit','id','permit_id');
}
}
But it doesn't work. What should i do to achieve the above? Is there any other solutions please suggest.
Assuming each invoice has one permit, your relationship definition should look like this:
public function permit(){
return $this->hasOne('App\Permit', 'id', 'permit_id');
}
Edit: If invoice belongs to permit, which is the inverse, your relationship would look like this instead:
public function permit(){
return $this->belongsTo('App\Permit', 'permit_id');
}
Edit: Based on your updated question, I think you got the relationship definitions a bit wrong. The following should work:
Since you have an invoice_id column in App\Dispatch, it means that each App\Dispatch belongs to an invoice.
In App\Dispatch, your relationship definition should be as follows:
public function invoice() {
return $this->belongsTo('App\Invoice');
}
// permit does not belong to `App\Dispatch` as a direct relationship
// it should be removed
In App\Invoice, your relationship definition should be as follows:
public function dispatch() {
return $this->hasOne('App\Dispatch');
}
public function permit() {
return $this->belongsTo('App\Permit');
}
In App\Permit, your relationship definition should be as follows:
public function invoice() {
return $this->hasOne('App\Invoice');
}
To then retrieve the permit id from an Invoice model, you would do
$invoice->permit->id;
Change this line
return $this->belongsTo('App\Invoice')->select('id','invoice_no','permit_id');
To
return $this->belongsTo('App\Invoice');
And add the following code on Invoice
public function permit(){
return $this->belongsTo('App\Permit');
}
And you can access as
Dispatch::find($id)->invoice->permit->id;
Or if you want all the information
Dispatch::find($id)->invoice->permit;

Get Parent from Child in Laravel without extra query

I am trying to make a small system with two models: Product, ProductPrice.
Here is the Product model:
class Product extends Model
{
protected $with = ['prices'];
public $tax_rate = 0.2;
public function prices ()
{
return $this->hasMany(ProductPrice::class);
}
}
I put the tax_rate constant here for more clarity, but in real world, it is handled by another relation.
The most important thing here is that the tax_rate is a property of the Product model
Here is the ProductPrice model:
class ProductPrice extends Model
{
protected $appends = ['tax_included_price'];
public function getTaxIncludedPriceAttribute()
{
return (1 + $this->product->tax_rate) * $this->price;
}
public function product ()
{
return $this->belongsTo(Product::class);
}
}
Now let's imagine that I need to use $product->toArray() on some model. With this example, I will get an Exception for infinite loop because my getTaxIncludedPriceAttribute() method makes a new request to find the product attribute.
So could I access the Product parent in the ProductPrice model if I access to it through the parent, and without making an extra query
So, I solved the problem with a handmade solution, not sure of implementation, but it works like I want it to work.
class Product extends Model
{
protected $with = ['pricesRelation'];
protected $appends = ['prices'];
public $tax_rate = 0.2;
public function pricesRelation ()
{
return $this->hasMany(ProductPrice::class);
}
public function getPricesAttribute ()
{
$collection = new Collection();
foreach($this->pricesRelation as $relation) {
$relation->loadProduct($this);
$collection->add($relation);
}
return $relation;
}
}
As you see, I run a $relation->loadProduct($this); to define parent on relation without re querying it...
class ProductPrice extends Model
{
protected $appends = ['tax_included_price'];
protected $loaded_product;
public function getTaxIncludedPriceAttribute()
{
$tax_rate = is_null($loaded_product) ? $this->product->tax_rate : $this->loaded_product->tax_rate;
return (1 + $tax_rate) * $this->price;
}
public function loadProduct (Product $product)
{
$this->loaded_product = $product;
}
public function product ()
{
return $this->belongsTo(Product::class);
}
}

Laravel 5.2 - Using SetIdAttribute() Mutator To Set Other Value

I am currently creating a blog where each Post row in my database will have a unique hash attribute that is based of the post's id (incrementing, always unique).
This my Post model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Hashids;
class Post extends Model
{
public function setTitleAttribute($value)
{
$this->attributes['title'] = $value;
if (! $this->exists) {
$this->attributes['slug'] = str_slug($value);
}
}
public function setIdAttribute($value) {
$this->attributes['id'] = $value;
$this->attributes['hash'] = Hashids::encode($value);
}
}
When I run this factory
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'title' => $faker->sentence(mt_rand(3, 10)),
'content' => join("\n\n", $faker->paragraphs(mt_rand(3, 6))),
'author' => $faker->name,
'category' => rand(1, 20),
];
});
The setIdAttribute($value) function is getting called, but my hash attribute is not being set. I am not sure if it is getting overwritten or what.
If I move the line
$this->attributes['hash'] = Hashids::encode($value);
to the function
public function setTitleAttribute($value)
and encode the title attribute it works fine, but I want to encode the 'id' attribute. Any idea how I would do this?
You can add the following to your model:
/**
* Events
*/
public static function boot()
{
parent::boot();
static::created(function($model)
{
$model->hash = Hashids::encode($model->id);
$model->slug = str_slug($model->title);
}
}
It's likely setIdAttribute($value) isn't being called until after the insert runs because it doesn't know the ID until then.
The real issue is you can't set a hash of the id in the same query because the id isn't going to be known (assuming it's auto_incrementing) until after the insert.
Because of this, the best you can probably do here is fire some code on the model's saved event.
In that model, you can probably do something like...
public static function boot()
{
parent::boot();
static::flushEventListeners(); // Without this I think we have an infinite loop
static::saved(function($post) {
$post->hash = Hashids:encode($post->id);
$post->save();
});
}

yii CFormModel dynamic properties

i got such form
class CC extends CFormModel
{
public $static_field;
public $fields;
public function rules()
{
return array(
array('static_field, testF', 'required')
);
}
public function getForm()
{
return new CForm(array(
'showErrorSummary'=>true,
'elements'=>array(
'static_field'=>array(),
'testF'=>array(),
),
'buttons'=>array(
'submit'=>array(
'type'=>'submit',
'label'=>'Next'
)
)
), $this);
}
public function attributeLabels()
{
return array(
'static_field' => 'static_field'
);
}
public function __get($name)
{
if (isset($this->fields[$name]))
return $this->fields[$name];
else
return '';
}
public function __set($name, $value)
{
$this->fields[$name] = $value;
}
}
i want to add dynamical field testF
i try to use __get\__set and array for values, but nothing work. any ideas?
If by dynamic you mean not required, you can add it as a property just as you have done with static_field. All attributes, or fields, are encapsulated member data of your FormModel class. So, if you wanted to add your dynamic_field attribute, you could add it in this manner:
class CC extends CFormModel
{
public $static_field;
public $dynamic_field;
public function rules()
{
return array(
array('static_field','required'),
array('dynamic_field','safe'),
);
}
}
Also, you're not exactly following the dominant usage pattern for this type of class. If I were you, I would suggest creating some CRUD through gii and examining the usage patterns for models and forms.

Categories