cakephp icon indicating copy to clipboard operation
cakephp copied to clipboard

Exception with `$requireFieldPresence` and the `_csrf` field

Open mirko-pagliai opened this issue 1 year ago • 9 comments

Description

Today I tried to use the $requireFieldPresence property on an entire entity (I've never done that before), so directly in its class as true.

Oddly enough, however, this knocks out all forms tied to that entity, apparently because of the hidden _csrf field.

An excerpt from the exception (I overridden FormHelper::create() as you can see, but only to add options to all forms on the entire application):

Cake\Datasource\Exception\MissingPropertyException: Property `_csrfToken` does not exist for the entity `App\Model\Entity\PatientsWeight`.
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php:337
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php:1107
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php:1028
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Form/EntityContext.php:738
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Form/EntityContext.php:712
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php:2434
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php:1731
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php:558
/home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php:480
/home/mirko/Server/climat/vendor/friendsofcake/bootstrap-ui/src/View/Helper/FormHelper.php:383
/home/mirko/Server/climat/src/View/Helper/FormHelper.php:68
/home/mirko/Server/climat/templates/PatientsWeights/form.php:23

I am using SessionCsrfProtectionMiddleware and the csfr token is correctly present, but I don't think it depends on these things. Nor do I think it depends on my configuration (although I'm not entirely sure, I'm investigating).

It seems to me that the problem arises when that field is initialized with FormHelper::_initInputField(): it checks if there are any errors (to add the relevant classes to the field), calling hasError() on the entity.

Of course, since that entity doesn't actually have the _csrfToken property (which it's looking for errors for), we get this exception.

I don't know if this is normal behavior, I don't think so. Am I doing something wrong? Maybe, normally, $requireFieldPresence should be used on a single piece of code, activating and deactivating it with its method, and not on the entire entity and always?

Thanks.

CakePHP Version

5.1.5

PHP Version

No response

mirko-pagliai avatar Jan 29 '25 08:01 mirko-pagliai

$requireFieldPresence should be used on a single piece of code, activating and deactivating it with its method, and not on the entire entity and always?

Nope, this is not meant to be a per field option. We are hoping to have an entity implementation which uses actual class properties in the future, so this feature is to provide a migration path towards that (since using dynamic class properties has been deprecated in PHP).

ADmad avatar Jan 29 '25 12:01 ADmad

Thanks for the pull request.

I'll try to say something that is perhaps a bit stupid or useless: in the future could we also think of a getOrFail() method?

Both in coherence with all the other ...OrFail() methods, and to allow a single access to a property that is expected to be defined, as an alternative to $requireFieldPresence (imagining that a getOrFail() is to be used for a single case/single call with $requireFieldPresence as false).

// For `Article`, all properties are required
// It will throw an exception if `title` or `subtitle` is not present.
$Article = new Article();
$Article->requireFieldPresence(true);
echo $Article->title . ' - ' . $Article->subtitle;

// For `Post`, by default no property is required.
// But in this statement I expect `title` to be.
// This will only throw an exception if `title` is not present, while `subtitle` is irrelevant to me
$Post = new Post();
$Post->requireFieldPresence(false);
echo $Post->getOrFail('title') . ($Post->subtitle ? ' - ' . $Post->subtitle : '');

Or, if we want, we could also intervene on the get() method here.

Imagining (just imagining, just to understand) that you could add an argument $required to the get() method:

public function &get(string $field, bool $required = false): mixed
{
   /// ...

   if (!$fieldIsPresent && ($this->requireFieldPresence || $required)) {
     /// ...
   }

   /// ...
}

So the previous example:

// For `Post`, by default no property is required.
// But in this statement I expect `title` to be.
// This will only throw an exception if `title` is not present, while `subtitle` is irrelevant to me
$Post = new Post();
$Post->requireFieldPresence(false);
echo $Post->get('title', true) . ($Post->subtitle ? ' - ' . $Post->subtitle : '');

It's just to throw out an idea that maybe you might like.

mirko-pagliai avatar Jan 29 '25 13:01 mirko-pagliai

Having a getOrFail() does sound interesting. Please open a separate issue for it so that it's easy to track and discuss.

ADmad avatar Jan 29 '25 15:01 ADmad

See also https://github.com/dereuromark/cakephp-shim/blob/master/docs/Model/Entity.md for OrFail() already usable in 5.x

dereuromark avatar Jan 29 '25 15:01 dereuromark

Reopening.

Nothing to do, the forms (at least the add one that uses a new entity) continue to be out of play if I set $requireFieldPresence to true on the entire entity.

An excerpt:

Possibly related to `Cake\Datasource\Exception\MissingPropertyException`: "Property `patient_id` does not exist for the entity `App\Model\Entity\PatientsWeight`."
#0 /home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Form/EntityContext.php(256): Cake\ORM\Entity->get('...')
#1 /home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php(2683): Cake\View\Form\EntityContext->val('...', Array)
#2 /home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php(2424): Cake\View\Helper\FormHelper->getSourceValue('...', Array)
#3 /home/mirko/Server/climat/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php(2164): Cake\View\Helper\FormHelper->_initInputField('...', Array)
#4 /home/mirko/Server/climat/vendor/friendsofcake/bootstrap-ui/src/View/Helper/FormHelper.php(420): Cake\View\Helper\FormHelper->select('...', Array, Array)

It is not strictly tied to that select input and that specific field, of course it always happens.

It seems that the behavior of $requireFieldPresence is unpredictable and that its behavior is to verify the field even when it would not make sense.

mirko-pagliai avatar Jan 30 '25 21:01 mirko-pagliai

I'm investigating, because I can't replicate this case in cakephp tests, probably my mistake, also because a test already covers this scenario.

mirko-pagliai avatar Jan 30 '25 22:01 mirko-pagliai

Ok, I confirm that the previous one was my mistake (I did a partial override, now I'm using the 5.x branch directly on my app).

Now it seems that the same problem occurs with patchEntity() and I replicated it:

public function testPatchEntityWithRequirePresence(): void
{
    $Table = $this->fetchTable('Articles');
    $Article = $Table->newEmptyEntity();
    $Article->requireFieldPresence(true);

    $Table->patchEntity($Article, ['title' => 'new title']);

    // We only need to check that Cake\Datasource\Exception\MissingPropertyException is not thrown
    $this->expectNotToPerformAssertions();
}
Cake\Datasource\Exception\MissingPropertyException: Property `title` does not exist for the entity `Cake\ORM\Entity`.
/home/mirko/Libs/cakephp/src/Datasource/EntityTrait.php:337
/home/mirko/Libs/cakephp/src/ORM/Marshaller.php:585
/home/mirko/Libs/cakephp/src/ORM/Table.php:3038
/home/mirko/Libs/cakephp/tests/TestCase/View/Helper/FormHelperTest.php:2838

mirko-pagliai avatar Jan 30 '25 22:01 mirko-pagliai

Thank you for continuing to try out this feature.

I see there are 4 $entity->get() in the marshaller which need to be addressed. Will make a PR soon.

ADmad avatar Jan 31 '25 02:01 ADmad

You are all the ones to be truly thanked for all that you do for the community. I would like to do more, unfortunately I do not have the same abilities as you, I limit myself to what I can.

Thanks again.

mirko-pagliai avatar Jan 31 '25 07:01 mirko-pagliai

Looks like with https://github.com/cakephp/cakephp/pull/18165 this is resolved.

dereuromark avatar Oct 26 '25 13:10 dereuromark