Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for typed attributes in BaseHtml::activeListInput() #19447

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

WinterSilence
Copy link
Contributor

@WinterSilence WinterSilence commented Jun 15, 2022

Q A
Is bugfix? ✔️
New feature?
Breaks BC?
Fixed issues #19446

@samdark samdark added the pr:request for unit tests Unit tests are needed. label Jun 17, 2022
@yii-bot
Copy link

yii-bot commented Jun 17, 2022

Thank you for putting effort in the improvement of the Yii framework.
We have reviewed your pull request.

In order for the framework and your solution to remain stable in the future, we have a unit test requirement in place. Therefore we can only accept your pull request if it is covered by unit tests.

Could you add these please?

Thanks!

P.S. If you have any questions about the creation of unit tests? Don't hesitate to ask for support. More information about unit tests

This is an automated comment, triggered by adding the label pr:request for unit tests.

@samdark samdark added the type:bug Bug label Jun 17, 2022
@WinterSilence WinterSilence changed the title Fix for typed /attributes in BaseHtml::activeListInput() Fix for typed attributes in BaseHtml::activeListInput() Jun 18, 2022
@WinterSilence
Copy link
Contributor Author

@samdark test added

@rob006
Copy link
Contributor

rob006 commented Jul 4, 2022

This does not fix this error, it hides it (unselect no longer works).

I don't think it is worth to fix #19446, since by design you should not use typed propertied (or setters) for form models. In Yii 2 validation has 2 steps:

  1. assign untrusted data to model
  2. validate model properties

Therefore, your model must accept any data type for validation to work.

@WinterSilence
Copy link
Contributor Author

@rob006

This does not fix this error, it hides it (unselect no longer works).

"unselect" not works when user don't want it and strictly declare attribute/property as array. this method should have created hidden input for each checkbox to keep data type.

since by design you should not use typed propertied (or setters) for form models

Nonsense, by your logic, adaptation to PHP8 is also not needed. 10 years ago typing was just beginning to be introduced into PHP - it's not "desing", just wasn't any other solutions to data typing.

@MarkoNV
Copy link
Contributor

MarkoNV commented Oct 19, 2022

@WinterSilence maybe it would be more appropriate to handle this by load() method. $_POST doesn't have any mean to send empty array, so to be able to set variable to empty array we need to send empty string. load() could handle this either by reflection (if string -> set empty string, if non-nullable array -> set empty array, if nullable non-string set null); or by manualy defined casting rules (e.g. filters() method which would behave similarly to rules() but supporting only filters, no errors, and executing before setting value to property).

@WinterSilence
Copy link
Contributor Author

@MarkoNV Yii 2 is freezed i.e. just fixes are allowed

@MarkoNV
Copy link
Contributor

MarkoNV commented Oct 21, 2022

@WinterSilence I think we had disagreement about cause of #19446.

Your view is that setting $options['unselect'] = ''; is issue. But I disagree, I have to agree with @rob006 here. Any property which is intended to be populated from $_POST or $_GET should accept any value that can be sent by $_POST and/or $_GET method without exception. If value isn't valid, that should be solved by filters or rejected by validators.

Solution would be:

  • casting on load on framework level; or
  • documenting fact that typed attributes are not appropriate for $_POST or $_GET data without casting/filtering on model (project) level

If we conclude that first solution would be new feature, we end up on what @rob006 said.

Also, your solution is BC break. You are changing behavior of BaseHtml::activeListInput() for models which implement casting on load or use proxy properties for casting.

Example casting/filtering on load:
Model:

class Form extends \yii\base\Model
{
    public array $values = [];

    public function setAttributes($values, $safeOnly = true)
    {
        if (empty($values['values']) && array_key_exists('values', $values)) {
            $values['values'] = [];
        }
        parent::setAttributes($values, $safeOnly);
    }

    public function rules()
    {
       return [
        [['values'], 'safe']
      ];
    }
}

This example is valid project code, works with current implementation and allows unsetting properties with default BaseHtml::activeListInput() options. After your bugfix, $options['unselect'] = ''; would need to be set explicitly to get same behavior.

Example proxy property:
Model:

class Form extends \yii\base\Model
{
    public array $values = [];

    public function setFormValues($values)
    {
        if (empty($values)) {
            $this->values = [];
        }
        elseif (is_array($values)) {
            $this->values = [$values];
        }
        // additional code if needed
    }

    public function rules()
    {
       return [
        [['formValues'], 'safe']
      ];
    }
}

View:

use \yii\widgets\ActiveForm;
$form = ActiveForm::begin();
echo $form->field($model, 'values[]')->checkboxList([1 => 'foo', 2 => 'bar'], [
   'name' => Html::getInputName($model, 'formValues')
]);
echo \yii\widgets\Html::submitButton();
ActiveForm::end();

This is also valid project code. FormField allows to set name and to submit form value to other attribute or setter. Value is loaded from attribute $values, but it's posted as 'formValues' which is set only proxy property. Your PR would try to set $model->values = '', that would fail and default behavior would be changed.

As I said, that should be solved by casting on loading data. Solving it on framework level would be nice, but if you consider it new feature, then only documentation should be changed explaining limitations, consequences and possible solutions for typed attributes.

@WinterSilence
Copy link
Contributor Author

@MarkoNV

Also, your solution is BC break.

nope, in this case the old version throws an exception

empty($values['values']) && array_key_exists('values', $values) no reasons to run array_key_exists() after empty()

@MarkoNV
Copy link
Contributor

MarkoNV commented Oct 21, 2022

@WinterSilence

nope, in this case the old version throws an exception

Example from bug report throws exception because property doesn't accepts all possible values from user.

In my two examples, there is no exception because model implementation handles accepting values from user and by default there is possibility to unselect, as is per documentation. After your PR, ability to unselect is by default disabled unless manually enabled, which is BC break.

empty($values['values']) && array_key_exists('values', $values) no reasons to run array_key_exists() after empty()

This submission should set property to empty value:

$data = [
    'id' => '1',
    'values' => '',
];

but this submission shouldn't change property $values since it isn't submitted:

$data = [
    'id' => '1',
];

Both submissions match empty(), but only first one matches array_key_exists().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr:request for unit tests Unit tests are needed. type:bug Bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants