I need to search a range for a field. How do I apply a between or greater than/less than statement to the search model.
Something similar to this. However those attributes aren't valid in the Search Model
$params['MlsSearch']['min_price'] = 10;
$params['MlsSearch']['max_price'] = 100;
$searchModel = new ModelSearch();
$dataProvider = $searchModel->search($params);
1) Define attributes in the model.
class ModelSearch extends Model
{
public $min_price;
public $max_price;
/*....*/
}
2) Make attributes safe
public function rules()
{
return [
/*... */
[['min_price', 'max_price', ], 'safe'],
]
}
3) Modify Search Function
public function search($params)
{
/*... */
$query->andFilterWhere(['>', 'price', $this->min_price]);
$query->andFilterWhere(['<', 'price', $this->max_price]);
/*... */
}
You can also use BETWEEN construction:
$query->andFilterWhere(['between', 'price', $this->min_price, $this->max_price]);
Related
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();
}
}
...
}
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;
Is it possible to append an attribute to my model whenever a model scope is called?
For example in my controller I want to call a scope to append those dynamic attribute like :
$Media_query = OutDoorMedia::query();
$Media_query->orderby('created_at', 'desc');
$Media_query->PreviouslyOrdered();
$Media = $Media_query->get();
And in my model I want to do something like :
class OutDoorMedia extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'id',
'user_id',
'address',
'location',
'media_type',
];
}
class scopePreviouslyOrdered extends OutDoorMedia
{
public $appends = ['previously_ordered'];
public function getPreviouslyOrderedAttribute()
{
if ($this->hasMany('App\Models\OutDoorMediaOrders', 'odm_id', 'id')->Where(function ($query) {
$query->where('status', MEDIA_ORDER_CHECKOUT_STATUS)
->orWhere('status', STATUS_TO_PAY);
})->exists()) {
return true;
} else {
return false;
}
}
}
But it's not working and I know it's wrong, How to achieve this?
I solved this problem with help of #apokryfos but with a bit tweak. hope this reduce wasting others time.
Instead of appending attributes on the model I have appended the said attribute to my model by the eloquent magic method :
$Media_query = OutDoorMedia::query();
$Media_query->orderby('created_at', 'desc');
$Media = $Media_query->get()->each(function ($items) {
$items->append('previously_ordered');//add this attribute to all records which has the condition
});
In Model As apokryfos said I have put these two methods:
public function PreviousOrders() {
return $this->hasMany('App\Models\OutDoorMediaOrders', 'odm_id', 'id');
}
public function getPreviouslyOrderedAttribute() {
return $this->PreviousOrders()->exists();
}
But I don't need this method and I had to remove it from the model because if it exist in model it will automatically append to model:
public $appends = [ 'previously_ordered' ];
I think there's a misunderstanding on how scopes should work. A scope is basically like a shortcut query for a model. You are using it to test existance of a relationship but there's a better way to do that using whereHas
Here's how you would achieve this using a relationship:
class OutDoorMedia extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'id',
'user_id',
'address',
'location',
'media_type',
];
public function previousOrders() {
return $this->hasMany('App\Models\OutDoorMediaOrders', 'odm_id', 'id');
}
public function getPreviouslyOrderedAttribute() {
return $this->previousOrders()->exists();
}
}
Then you simply do:
$Media_query = OutDoorMedia::whereHas('previousOrders')
->orderby('created_at', 'desc');
If you what the dynamic attribute appended on the model automatically you can just add the following to the model:
public $appends = [ 'previously_ordered' ];
I guess if you want the best from both worlds you can do:
class OutdoorMediaWithPreviouslyOrdered extends OutDoorMedia {
public $appends = [ 'previously_ordered' ];
}
Then when you need the appending model you can use :
$Media_query = OutdoorMediaWithPreviouslyOrdered ::orderby('created_at', 'desc');
thank you view my question.
I would like to retrieve information on the tag table relation with the store with many-to-many when searching for a category
I created Store-table, Category-table, Tag-table.
The store-table and the category-table are connected by a many-to-many relation. The tag-table is the same.
I was able to search for categories and get information on businesses that are relation- ed, but I do not know how to get information on tags that are relations with stores.
So, I try this idea. search categories → get storeID from relation data→ storeID search → return shop data that hit.
However, I do not know how to get storeID in the store data acquired by category search
How can I write the code?
please help me.
sorry, bat my English.
App\Store
use Illuminate\Database\Eloquent\Model;
class Store extends Model
{
protected $fillable = ['name','location', 'price', 'open_time',
'closed_day'];
protected $table = 'stores';
public function photos(){
return $this->hasMany(StorePhoto::class);
}
public function categories(){
return $this->belongsToMany(Category::class,'category_store','category_id','store_id');
}
public function tags(){
return $this->belongsToMany(Tag::class, 'store_tag', 'tag_id', 'store_id');
}
}
App\Category
protected $fillable = ['store_id', 'category_id'];
public function stores()
{
return $this->belongsToMany(Store::class,'category_store','store_id','category_id');
}
App\Tag
protected $fillable = ['store_id', 'tag_id'];
public function stores()
{
return $this->belongsToMany(Store::class, 'store_tag', 'store_id', 'tag_id');
}
Resource/Category
class Category extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'store' => $this->stores,
];
}
}
web.php
use App\Category;
use App\Http\Resources\Category as CategoryResource;
Route::get("/store/api/category", function (Request $request) {
$search_category = $request->get('category_id');
return new CategoryResource(Category::find($search_category));
});
You can use dot notation to eager load nested relations:
$category = Category::with('stores.tags')->find($request->get('category_id'));
The tags will then be accessible on each Store model related to the Category:
// create a single flattened array of all the tags
$tags = $category->stores->flatMap->tags;
I have some project in Yii. First it was a form with two text fields: Name and Subject (subject is list of subjects separated by comma). Now I need to replace subject text field with list of checkboxes, which will add the same string separated by comma.
<!--<?php echo $form->labelEx($model,'Subjects'); ?>
<?php echo $form->textField($model,'Subjects',array('size'=>60,'maxlength'=>255)); ?>
<?php echo $form->error($model,'Subjects'); ?>-->
<?php echo $form->labelEx($model,'Subjects'); ?>
<?php echo $form->checkBoxList($model,'Subjects',$this->listOfSubjects()); ?>
<?php echo $form->error($model,'Subjects'); ?>
List of subjects is a controller method that returns array required for checkboxlist in Yii, something like:
array(
'1'=>'Something',
'2'=>'Anotherthing'
);
Also here is code of my action create. It's rather standart:
public function actionCreate()
{
$model=new CrdTeachers;
if(isset($_POST['CrdTeachers']))
{
$model->attributes=$_POST['CrdTeachers'];
if($model->save())
$this->redirect(array('view','id'=>$model->Teacher_ID));
}
$this->render('create',array(
'model'=>$model,
));
}
This code worked fine when there was just two text fields. Now when I'm using create action it says that mistake:
mb_strlen() expects parameter 1 to be string, array given
I can't find where can I process it's data to make it string. Any experts of Yii here? What should I look for?
UPDATE:
Here is CRDTeachers model class
class CrdTeachers extends CActiveRecord
{
/**
* Returns the static model of the specified AR class.
* #param string $className active record class name.
* #return CrdTeachers the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'crd_teachers';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('Name, Subjects', 'required'),
array('Name, Subjects', 'length', 'max'=>255),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('Teacher_ID, Name, Subjects', 'safe', 'on'=>'search'),
);
}
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
//'rSubjects'=>array(self::HAS_MANY, 'CrdSubjects', 'Subject_ID'),
//'categories'=>array(self::MANY_MANY, 'CrdTeachers', '{{CrdSubjects}}(Subject_ID, Subject_Name)'),
);
}
public function behaviors()
{
return array(
'DMultiplyListBehavior'=>array(
'class'=>'DMultiplyListBehavior',
'attribute'=>'categoriesArray',
'relation'=>'categories',
'relationPk'=>'id',
),
);
}
protected function afterSave()
{
//$this->refreshCategories();
parent::afterSave();
}
protected function refreshCategories()
{
$categories = $this->categoriesArray;
CrdTeachers::model()->deleteAllByAttributes(array('Subject_ID'=>$this->id));
if (is_array($categories))
{
foreach ($categories as $id)
{
if (Category::model()->exists('id=:id', array(':id'=>$id)))
{
$postCat = new CrdTeachers();
$postCat->post_id = $this->id;
$postCat->category_id = $id;
$postCat->save();
}
}
}
}
/**
* #return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'Teacher_ID' => 'Teacher',
'Name' => 'Name',
'Subjects' => 'Subjects',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
* #return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
*/
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('Teacher_ID',$this->Teacher_ID);
$criteria->compare('Name',$this->Name,true);
$criteria->compare('Subjects',$this->Subjects,true);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
/*
* Возвращает список по ID
*/
public function getParentTypeById($id) {
$title = $this->model()->findByPk($id)->Name;
return $title;
}
}
You may use this method in your model.
public function beforeValidate()
{
if (is_array($this->Subjects)) {
$this->Subjects = implode(', ', $this->Subjects);
}
return parent::beforeValidate();
}
You can process the input before validation with the beforeValidate function in the model. Make sure that you run the parent method and return true if want validation to continue. Usually it is done like this:
public function beforeValidate()
{
// do stuff to transform the array into the string
return parent::beforeValidate();
}