Yii2 - Assign a "fix" condition

165 Views Asked by At

I am building an app, where an account can have many services, all the information is related to a service. In example:

Account A has 3 services and each service has pages.

In order to avoid someone modifying the service_id when saving a page, at the moment I do:

if(Yii::$app->request->isPost) {

    $post = Yii::$app->request->post();
    $model->load($post);
    $model->service_id = $this->service->id;
}

Where $model->service_id = $this->service->id helps me assign the selected service_id after loading table to model and avoid someone sending service_id from the form.

But in case someone in the future, develops "documents" I would like to avoid the developer handling this service_id on all the queries.

So First it I thought I could try:

public function beforeFind($queryData) {
    parent::beforeFind();
    $queryData['conditions'] = array('service_id' => 2);    
    return $queryData;
}

But still needs the developer to implement it. So maybe is there a way to create a "BaseService" model where all other service related models should extend from but not sure how to:

  1. Add the condition from the parent model?
  2. How to pass the id to this model so it keeps it during all queries?

Maybe there is a simple solution, and I am overcomplicating myself due long hours working, not sure.

2

There are 2 best solutions below

2
On

That is a default condition to apply for all queries. In case your application is built on top of ActiveRecord classes (not performing direct SQL queries or on the fly QueryBuilder) then you can simply override the find() method inside your Model class:

public static function find()
{
    /* you can add more dynamic logic here */
    return parent::find()->where(['service_id' => 2]);
}

By default, all controllers in Yii2 are using Model::find() to retrieve data from database, adding such condition should be enough to not retrieve anything with a different service_id than 2. Direct http GET requests by ID should then output 404 if that condition isn't satisfied and retrieving them as relational data within a different model class should return a filtered array.

IMPORTANT: To not break that implementation you need to:

  1. Always use ActiveRecord. Otherwise you'll need to manually add the condition to your queries.

  2. (This is not correct) Be carful on when to use asArray() as it omits ActiveRecord features (See note and explanation here). Otherwise you need to manually re-declare the condition like: Account::find()->where(['service_id' => 2])->asArray()->all();

  3. Always use andWhere() to merge conditions because where() will override/ignore the default one. Example: Account::find()->andWhere('age>30')->all();

0
On

to reuse such filters you can put them into a custom ActiveQuery.

in your ActiveRecord:

public static function find() {
    return (new ActiveQuery(get_called_class()));
}

ActiveQuery:

public function service($service = 2) {
    return $this->andWhere(['service_id' => $service]);
}

your model based on your ActiveRecord:

public static function find() {
    return (new ActiveQuery(get_called_class()))->service(2);
}

alternatively

$model->find()->service(1);

also, this might be of interest (setting Default values per scenario)