And Where is not working before where condition? - yii2

Here I am overriding the find() method
class ActiveRecord extends BaseActiveRecord{
public static function find() {
return parent::find()
->where(['=',static::tableName().'.company_id',Yii::$app->user->identity->company_id])
->andWhere(['=',static::tableName().'.branch_id',Yii::$app->user->identity->branch_id]);
}
}
Now if I use the condition like this
\common\models\Order::find()->where(['stastus'=>'3'])->count();
the Active Record global condition is executed before the condition I am
using in Order model and after that the Order Model where overriding
the active record global condition.
And if I use the Active Record condition like this
class ActiveRecord extends BaseActiveRecord{
public static function find() {
return parent::find()
->andWhere(['=',static::tableName().'.company_id',Yii::$app->user->identity->company_id])
->andWhere(['=',static::tableName().'.branch_id',Yii::$app->user->identity->branch_id]);
}
}
There were in my local model overriding the global condition. Difficult for
me to override each where with and where.

You should change the where and andWhere to onCondition in your BaseActiveRecord which I suppose is an alias for \yii\db\ActiveRecord as the parent::find() return object of ActiveQuery if you look into the find() method it returns below line
\yii\db\ActiveRecord
return Yii::createObject(ActiveQuery::className(), [get_called_class()]);
you can see here customizing-query-class to add an extra condition
class ActiveRecord extends BaseActiveRecord{
return parent::find ()
->onCondition ( [ 'and' ,
[ '=' , static::tableName () . '.application_id' , 1 ] ,
[ '=' , static::tableName () . '.branch_id' , 2 ]
] );
}
Now if you call
\common\models\Order::find()->where(['status'=>'3'])->createCommand()->rawSql;
it will generate the following query
SELECT * FROM `order`
WHERE (`status` = 3)
AND
((`order`.`application_id` = 1) AND (`order`.`branch_id` = 2))

This is the way how the Yii2 ActiveRecord works. When you call method where() it reset conditions, even if was not empty and when you call andWhere() it add new condition to existing ones.

Related

Query builder don't accept onlyTrashed()

I'm a beginner and cant find a solution to my problem. I trying to get "onlyTrashed" from my DB but Laravel 8 don't accept my query commands :( I tried many scenarios but unsuccessful.
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use App\Models\Category;
class CategoryController extends Controller{
public function AllCat(){
$categories = $trashCat = DB::table('categories')
->join('users','categories.user_id','users.id')
->select('categories.*','users.name')
->latest()
->paginate(5);
//$categories = Category::latest()->paginate(5);
// $trashCat = Category::onlyTrashed()->latest()->paginate(3);
return view('admin.category.index', compact('trashCat','categories'));
public function SoftDelete($id){
$delete = Category::find($id);`enter code here`
return Redirect()->back()->wiht('success',' Category Delete Successfuly');
Route::get('/softdelete/category/{id}', [CategoryController::class,'SoftDelete']);
Please make sure that your model imports and uses SoftDeletes trait to implement soft delete in your model and make sure that your table has deleted_at field, your model may look like below codes.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Category extends Model
{
use SoftDeletes;
And in your SoftDelete function within the controller, I think you miss delete() function, your function should look like:
public function SoftDelete($id) {
$delete = Category::find($id)->delete();
return redirect()->back()->with('success',' Category Delete Successfully');
}
Once you implement SoftDeletes trait into your model, delete() function should mark deleted by filling the deleted_at with timestamp or DateTime automatically. Once you implement the above codes, onlyTrashed() should work.
$categories = Category::latest()->paginate(5);
$trashCat = Category::onlyTrashed()->latest()->paginate(3);

Sort the parent model based on the child model / relationship

I have a model called appointments, each appointment has an option_id that is linked through a one to one relationship and the option_id can also be null. The option model has a property datetime_start. I want to sort the appointments based on the option.datetime_start.
Here is my code :
$appointments = $user->appointments()
->with(['option'
])
->get();
Edit :
Appointment model :
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Appointment extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function option()
{
return $this->hasOne(Option::class, 'id', 'option_id');
}
}
Option model :
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Option extends Model
{
protected $fillable = ["appointment_id",
"datetime_start"
];
public function appointment()
{
return $this->belongsTo(Appointment::class, 'id','appointment_id');
}
}
Any help would be greatly appreciated.
In order to sort Appointment model by a column from related Option model, you need to add a JOIN to your query so that you can sort by related fields:
$appointments = $user->appointments()
->leftJoin('options', 'options.appointment_id', '=', 'appointments.id')
->orderBy('options.datetime_start', 'ASC')
->with(['option'])
->get();
This will give you all appointments, including those without Option - the ones without an Option and, hence, without datetime_start, will be all returned either at the beginning or the end of the result list, you'll need to check that. If you only want appointments with Option, replace leftJoin() with join().
$appointments = Appointment::with(['option' => function ($query){ $query->orderBy('datetime_start', 'desc'); } ])->get();

Laravel: orderBy relationship's field

I have an issue with ordering model by it's relationship's latest object field.
I have a class named Coin that is related to DailyPrices, that 1 coin can have many DailyPrices. I want to sort coins by latest DailyPrices field named vol.
I tried doing
$coins = Coin::join('daily_prices', 'daily_prices.coin_id', '=', 'coins.id')
->orderBy('daily_prices.vol', 'desc')
->paginate(100);
and many variations about it, but I can't get it to work. What am I doing wrong?
why don't you use created_at instead of vol
$coins = Coin::join('daily_prices', 'daily_prices.coin_id', '=', 'coins.id')->order_by('created_at', 'desc')->paginate(100);
You can do with creating two separate model for coin and daily price.
class Coin extends Model {
public function dailyPrices(){
return $this->hasMany('App\DailyPrice')->orderBy('vol', 'DESC');
}
}
class DailyPrice extends Model {
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'daily_prices';
/**
* Get the coin that owns the daily price.
*/
public function coin()
{
return $this->belongsTo('App\Coin');
}
}
and call with ID in your controller like this
$coinData = Coin::find($coin_id)->with('dailyPrices')->first();
You made two mistakes in your code:
Condition check
Not select table
Here is a code after making changes:
$order='desc';
Coin::join('daily_prices', 'coins.id', '=', 'daily_prices.coin_id')->orderBy('daily_prices.vol', $order)->select('coins.*')->paginate(100);
You can also see more details from here:Order by on relationship
You can use One to Many relationship :
class Coin {
public function dailyPrices() {
return $this->hasMany(DailyPrice::class);
}
}
DailyPrice :
class DailyPrice {
public function coin() {
return $this->belongsTo(Coin::class);
}
}
Then in your controller :
Coin::with('dailyPrices' => function ($query) {
$query->orderBy('vol', 'desc');
))->paginate(100);
The with function will load dailyPrice relationship on your item and the value argument will execute a query related to the relation loaded (here an order by).
Hopes it helps you.
Maybe the question was unclear, I don't know. But neither of these were relevant to an issue. I have resolved the problem with this query:
SELECT c.* FROM daily_prices AS dp JOIN coins AS c ON dp.coin_id=c.id WHERE dp.id IN (SELECT MAX(id) FROM daily_prices GROUP BY coin_id) ORDER BY vol DESC
and transformed to laravel call:
Coin::join('daily_prices', 'daily_prices.coin_id', '=', 'coins.id')
->whereIn('daily_prices.id', function ($query) {
$query->selectRaw('MAX(id)')
->from('daily_prices')
->groupBy('coin_id');
})
->orderBy('daily_prices.vol', 'desc')
->paginate(100);

Yii2 - replacement for beforeFind to optimize nested views in MySql

Yii1 used to have beforeFind method in which you could modify the query or whatever else you might want to do.
In Yii2 the suggested alternative is to use the modelQuery solution for example
class MyModel extends \yii\db\ActiveRecord
{
public static function find()
{
return new MyModelQuery(get_called_class());
}
/* ... */
}
and
class MyModelQuery extends \yii\db\ActiveQuery
{
public function init( )
{
/** do something here **/
}
}
But how do I pass or reference MyModel within MyModelQuery?
For example:-
class MyModelQuery extends \yii\db\ActiveQuery
{
public function init( )
{
$sql = "SET #variable = {$MyModel->variable1}";
}
}
EDIT
For completeness, I've added a use case to help others in future.
I have nested views with group by's running under MySql and it runs VERY badly.
In my case, I have orders, order-items and order-item-fees tables, each one-to-many to the next and I want to sum the order totals. I have nested view, one at each level to sum to the level above, but at the order-item and order-item-fee levels MySql is grouping the whole table first (I cannot use algorithm=merge as I have a GROUP BY).
I'm implementing the Pushdown method where you define a SQL variable to use in sub-views to narrow down the search as outlined here: http://code.openark.org/blog/mysql/views-better-performance-with-condition-pushdown
and also here
https://www.percona.com/blog/2010/05/19/a-workaround-for-the-performance-problems-of-temptable-views/
In this way, if I can add a 'WHERE order_id=' to the where clause of the two sub-views, I reduce a 3.5 second query down to 0.003 second query.
So using, Salem's suggestion below, I can execute a SQL statement 'SET #order_id=1234' before my query, which is then picked up in the order-item and order-item-fee views using a function. Note: this is connection specific, so no danger of collisions between sessions.
A bit convoluted but fast.
It would be interesting, though, to see a performance comparison between SQL and looping in PHP perhaps....
EDIT 2
In fact, you normally use find() as a static method, so there is no way of using $this->order_id, so I changed this to over-ride the findOne method
public static function findOne( $orderId )
{
if ( isset($orderId) )
{
$sql = "SET #orderId='{$orderId}'";
Yii::$app->db->createCommand($sql)->execute();
}
return parent::findOne( $orderId );
}
I also use this view with other searches, so in the view I need to check whether the orderId is set or not ...
where (
CASE
WHEN ( NOT isnull( get_session_orderId() ) )
THEN `order`.order_id = get_session_cartref()
ELSE `order`.order_id LIKE '%'
END
)
About how to involve an ActiveQuery class check my answer here:
Yii2 : ActiveQuery Example and what is the reason to generate ActiveQuery class separately in Gii?
But if what you are trying to do doesn't require building named scopes then you may simply override the find method by something like this:
public static function find()
{
return parent::find()->where(['variable' => 'some value']);
}

How to use constant in the ON condition in Yii2 hasMany relation

I try to create a polymorphic association, what is common in Rails but unfortunately not in Yii2. As part of the implementation I need to define the relation:
public function getImages()
{
return $this->hasMany(RecipeImage::className(),
['imageable_id' => 'id', 'imageable_type' => 'Person']);
}
But this doesn't work, because 'Person' is treated as an attribute of the current model, but it is a constant (class name for the polymorphic association).
If I try to use 'andWhere' it adds the condition of course in a WHERE clause instead of the ON clause, causing that only records with existing image returned.
public function getImages()
{
return $this->hasMany(RecipeImage::className(), ['imageable_id' => 'id'])->
andWhere(['imageable_type' => 'Ingredient']);
}
How can I define the relation? There is no andOn method.
In this case you can modify ON condition with andOnCondition method:
public function getImages()
{
return $this->hasMany(RecipeImage::className(), ['imageable_id' => 'id'])
->andOnCondition(['imageable_type' => 'Person']);
}
Official docs:
andOnCondition: