phpyii2pjax

does not handle class switching correctly yii2 Pjax


I have a form, when clicking on the icon the class should switch to enabled/disabled

$options = [
    'enablePushState' => false,
    'timeout' => 5000,
    'options' => ['tag'=>'span', 'class'=>'access']
];

$id = \Yii::$app->request->get('_pjax');
if($id){$options['id'] = str_replace('#', '', $id);}
?>

<?php Pjax::begin($options); ?>
    <?= Html::a('<i class="fa fa-users"></i>', ['/request/'.$id.'/access'], [
        'class' => ($access['provider'] ?? null) ? 'enabled' : 'disabled'
    ]) ?>
<?php Pjax::end(); ?>
public function actionAccess($id){
        $requestModel = $this->model->getRequestModel($id);
        $this->model->requestToggleAccess($requestModel);
        $access = $requestModel->checkAccess();

        return $this->renderPartial('access.php', [
            'id' => $id,
            'access' => $access
        ]);
    }
public function getRequestModel($id)
    {
        $requestModel = RequestModel::findOne($id);

        return $requestModel;
    }
public function requestToggleAccess($requestModel)
    {
        if (Yii::$app->request->isPjax) {
            $access = $requestModel->checkAccess();
            if( $access['provider'] ){
                $request->accessRoleIds = null;
            }else{
                $request->accessRoleIds = [ROLE_PROVIDER, ROLE_PROVIDER_WORKER];
            }
            $request->saveAccess();
        }
    }
public function checkAccess()
    {
        return self::checkAccessBy($this->access);
    }

    public static function checkAccessBy($arr)
    {
        $access = [];
        if(empty($arr)){return $access;}
        foreach ($arr as $acc){
            if(in_array($acc->role_id, [ROLE_PROVIDER, ROLE_PROVIDER_WORKER])){
                $access['provider'] = true;
            }
        }

        return $access;
    }

The thing is that when initially the class is enabled and I switch to disabled, then everything works well, but when I switch in the reverse order from disabled to enabled, then I just go to this link '/request /'.$id.'/access' and returns an empty page, it gives an error 500 without any details.

I'm trying to dump to find the problem

In the actionAccess function after calling the function checkAccess(), I added

var_dump($access['provider']);
exit();

And got an error

Undefined array key "provider"

Although in the neighboring function requestToggleAccess there is the same checkAccess() call, after which I added the same dump, and instead of an error I get boolean(true), which is what it should be

What could be the problem?


Solution

  • Look into requestToggleAccess. The problem is that you assume $access['provider'] to exist. But inside checkAccessBy (which is called by checkAccess and whose return value will be assigned to $access) you will see that $access['provider'] is not necessarily defined. We will return to this problem at the end of this answer, but first, let's make requestToggleAccess less error-prone:

    public function requestToggleAccess($requestModel)
        {
            if (Yii::$app->request->isPjax) {
                $access = $requestModel->checkAccess();
                if( $access['provider'] ?? false ){
                    $request->accessRoleIds = null;
                }else{
                    $request->accessRoleIds = [ROLE_PROVIDER, ROLE_PROVIDER_WORKER];
                }
                $request->saveAccess();
            }
        }
    

    I have merely added a ?? false to your if condition, that is, we know that $access is defined at this point, but we avoid assuming that it has a value. Instead, we evaluate it and gather its value if it exists, defaulting to false if it did not exist.

    Now, let's see why checkAccessBy did not specify $access['provider']. There you receive an array (after self::checkAccessBy($this->access) is called) and you loop that array. For each item in that array, you check whether in_array($acc->role_id, [ROLE_PROVIDER, ROLE_PROVIDER_WORKER]) and if so, then you set $access['provider'] to true. But you never ever set it to false. This is an improved version of the same method:

        public static function checkAccessBy($arr)
        {
            $access = [
                'provider' => false //We initialize provider with false and override it later if needed
            ];
            if(empty($arr)){return $access;}
            foreach ($arr as $acc){
                if(in_array($acc->role_id, [ROLE_PROVIDER, ROLE_PROVIDER_WORKER])){
                    $access['provider'] = true;
                }
            }
    
            return $access;
        }
    

    I have changed the initialization of $access so it will have a provider element, which is initialized with true and overriden later if needed.

    Another change you might want to make is to return $access inside the loop if its provider is to be set to false, but I did not do that, because you might prefer to return it at the end of the method for styling or other purposes.