• Что лучше: yii\db\Query или Active Record?

    maxkh
    @maxkh
    Web developer
    В батле Query VS ActiveRecord - нет победителя - это инструменты для решения разных задач.

    Но я хочу отвлечься от вашего вопроса и поинтересоваться настроены ли в вашей БД внешние ключи ? Если нет, то советую начать изучение именно с этого момента, понимание внешних ключей в MySQL.

    После чего, нужно очень вдумчиво и хорошо прочесть официальную документацию на тему ActiveRecord (www.yiiframework.com/doc-2.0/guide-db-active-recor...

    После этого, у вас должна сложиться более целостная картинка о том, как это использовать вместе и что за что отвечает.

    Теперь вернемся к использованию Query и AR, ну тут все просто, приведу просто пример кода

    /**
     * Class VehicleQuery
     * @package frontend\modules\v1\models\vehicle
     */
    class VehicleQuery extends ActiveQuery
    {
        public $type;
    
        /**
         * @param \yii\db\QueryBuilder $builder
         * @return \yii\db\Query
         */
        public function prepare($builder)
        {
            if ($this->type !== null) {
                $this->andWhere([Vehicle::tableName() . '.vehicle_type' => $this->type]);
            }
            return parent::prepare($builder);
        }
    
        /**
         * Returns popular and active vehicles based on vehicle type
         * @return $this
         */
        public function popular()
        {
            $this->andWhere([
                Vehicle::tableName() . '.promoted' => 1,
                Vehicle::tableName() . '.deleted' => 0,
                Vehicle::tableName() . '.status' => '1',
                Vehicle::tableName() . '.in_use' => 1
            ]);
            return $this;
        }
    
        /**
         * @return $this
         */
        public function active()
        {
            $this->andWhere([
                Vehicle::tableName() . '.status' => '1',
                Vehicle::tableName() . '.deleted' => 0
            ]);
            return $this;
        }
    
        /**
         * @return $this
         */
        public function inUse()
        {
            $this->andWhere([Vehicle::tableName() . '.in_use' => 1]);
            return $this;
        }
    
    
        /**
         * Returns most rated vehicles
         * @return $this
         */
        public function rated()
        {
            $this->addOrderBy([
                Vehicle::tableName() . '.rating' => SORT_DESC,
                Vehicle::tableName() . '.created_at' => SORT_DESC
            ]);
            return $this;
        }
    
        /**
         * Returns most recently vehicles
         * @return $this
         */
        public function recently()
        {
            $this->addOrderBy([
                Vehicle::tableName() . '.created_at' => SORT_DESC,
                Vehicle::tableName() . '.rating' => SORT_DESC
            ]);
            return $this;
        }
    }


    Мы имеем модель Vehicle и для работы с этой моделью нам необходимо строить запросы, именно этим и занимается класс VehicleQuery - он берет на себя эту ответственность. Очень важно что бы Query класс был ответственный только за построение запросов, методы не должны возвращать find или save, они должны возвращать только экземпляр Query.

    Приведу код модели Vehicle
    class Vehicle extends ActiveRecord
    {
        public static function find()
        {
            return new VehicleQuery(get_called_class());
        }
    
        /**
         * @return \yii\db\ActiveQuery
         */
        public function getCarBrand()
        {
            return $this->hasOne(CarBrand::className(), ['id' => 'car_brand_id']);
        }
    
        /**
         * @return \yii\db\ActiveQuery
         */
        public function getCarGeneration()
        {
            return $this->hasOne(CarGeneration::className(), ['id' => 'car_generation_id']);
        }
    
        /**
         * @return \yii\db\ActiveQuery
         */
        public function getLanguage()
        {
            return $this->hasOne(Language::className(), ['id' => 'language_id']);
        }
    
        /**
         * @return \yii\db\ActiveQuery
         */
        public function getUser()
        {
            return $this->hasOne(User::className(), ['id' => 'user_id']);
        }
    
        /**
         * @return \yii\db\ActiveQuery
         */
        public function getPosts()
        {
            return $this->hasMany(Post::className(), ['car_id' => 'id']);
        }
    }


    Модель описывает entity, rules, relations, attributes - собственно это главная ответственность модели, никакую дополнительную логику в моделе не рекомендуется имплементировать.

    Из выше описанного ваш запрос может выглядеть следующим образом

    Vehicle::find()->with(['user', 'posts'])->active()->recently();


    Этот запрос выберет активные транспортные средства за последнее время со связями getUser(владелец) и getPosts(блоги).
    Ответ написан
    1 комментарий
  • Как сделать в join дополнительное условие?

    maxkh
    @maxkh
    Web developer
    www.yiiframework.com/doc-2.0/yii-db-activequery.ht...

    public function getActiveUsers()
    {
        return $this->hasMany(User::className(), ['id' => 'user_id'])
                    ->onCondition(['active' => true]);
    }


    А так же www.yiiframework.com/doc-2.0/guide-db-active-recor...
    Ответ написан
    Комментировать
  • Yii2: Логин по email или username?

    maxkh
    @maxkh
    Web developer
    Если вы делаете все это в модели - лучшим решением было бы завести отдельную форму, где были бы прописаны rules() для валидации и фильтрации. В этой же форме, можно реализовать вашу логику авторизации.

    class Login extends Model
    {
        public $identity;
        public $password;
    
        public function rules()
        {
            return [
                ['identity', 'filter', 'filter' => 'trim'],
                ['identity', 'required'],
    
                ['password', 'required'],
            ];
        }
    
        /**
         * @return bool|null|static|User
         */
        public function authenticate()
        {
            if (!$user = User::findIdentityByUsernameOrEmail($this->identity)) {
                $this->addError('identity', Yii::t('app', 'invalid_credentials_on_login'));
                return false;
            }
    
            if (!$user->validatePassword($this->password)) {
                $this->addError('identity', Yii::t('app', 'invalid_credentials_on_login'));
                return false;
            }
    
            return $user;
        }
    }


    /**
         * Finds user by username or email
         *
         * @param string $identity
         * @return static|null
         */
        public static function findIdentityByUsernameOrEmail($identity)
        {
            /**
             * Try to find identity by nickname
             * @var User $user
             */
            if ($user = static::findOne(['username' => $identity, 'status' => self::STATUS_ACTIVE])) {
                return $user;
            }
    
            /**
             * Try to find identity by email
             * @var LoginAccount $loginAccount
             */
            if ($loginAccount = LoginAccount::findByEmail($identity)) {
                return $loginAccount->getUser()->one();
            }
    
            return null;
        }


    В моем случае авторизация по email или nickname в одном поле, которое имеет общее название identity, а при поиске пользователя я использую 2 модели, так как email в другой таблице.
    Ответ написан
  • Блок условий в методе afterFind

    maxkh
    @maxkh
    Web developer
    Попробуйте в ModelClass::model() - передать сценарий, на основании сценария можно будет сообразить условие. А так же выносите все в отдельные методы.
    Ответ написан