Form context object is lost in view blocks

CakePHP 3.8.5

I have a form that is created by two cooperating .ctps and view blocks.

The Form context is being lost. I’m wondering if there is something about view blocks that looses the context object.

//the element I call, target_variation.ctp

$this->start('special_form_control');
   echo $this->Form->control('special_control');
$this->end();

echo $this->element('common_form_wrapper');
//the element common_form_wrapper.ctp

echo $this->Form->create($Context);
//echo common inputs here
echo $this->fetch('special_form_control');
//echo more common stuff
echo $this->Form->end();

I had trouble with the results, and when I checked the return from FormHelper::context() I see that common_wrapper_form has a valid context object in the helper, target_variation has lost that object and has a null object instead.

The order of events looked like trouble so I switched things around:

//the element call, target_variation.ctp

$this->element('common_form_wrapper');

$this->start('special_form_control');
   echo $this->Form->control('special_control');
$this->end();

echo fetch('begin_form');
echo fetch('special_form_control');
echo fetch('end_form');
//the element common_form_wrapper.ctp

$this->start('begin_form');
    echo $this->Form->create($Context);
    //echo common inputs here
$this->end();
$this->start('end_form');
    //echo more common stuff
    echo $this->Form->end();
$this->end();

But I’m still getting a null context object in target_variation.

I tried using FormHelper::context($contextObject) to set or reset the context in taget_variation but still got a null object. ??

In your first example, the special_form_control is created before you call the wrapper, so the context has not yet been initialized. Your fetch call gets the results that were previously created, it doesn’t do the rendering at that time.

In your second example, the form has already been closed when you try to render special_form_control, so not surprising that the context would be lost.

I expect that what you will need to do is replace common_form_wrapper with common_form_header and common_form_footer. Slightly more code, but will give you the flexibility that you need.

Ah! I missed the fact that the second version closes the form before I get to in with the middle block.

But I’m not sure why Form::context($context) still returns a null context. From the api:

context() public

context( Cake\View\Form\ContextInterface |null $context = null )

Get the context instance for the current form set.

If there is no active form null will be returned.

Parameters

Cake\View\Form\ContextInterface |null $context optional null

Either the new context when setting, or null to get.

Returns

Cake\View\Form\ContextInterface
The context for the form.

That is a bit ambiguous. Possibly this method is in transition on its way to the new get/set style?

How did you get the context object that you tried to set? Get it from the helper when the form is “open” and then set to the same again after ending the form? I don’t know whether it would remain viable after that.

But you’re going to have other problems with this, if you were ever to want to use the form security stuff, because that’s all added by the helper’s end function, which thus cannot be called before you’ve finished creating all your inputs.

You’re right about problems with the security system. I tried using a non-printing Form::create($context) to accomplish what Form::context($context) would not. Got the exact security problems you indicate.

The context I tried to set was a Form object, the same one I used for the initial form creation. But I tried a variety of alternatives. I believe getting a reference from the FormHelper and using it again might have been one of them.

However, I just approached the problem from the opposite side. I do have to make sure that new method can supply the correct answer for the situation… We’ll it’s a work in progress.

This does honor the required sequence of events though. And so, works fine with Security.

//path/form.ctp (the element I render)

echo $this->Form->create($Context);
//echo common inputs here

echo $this->element('path/' . $Prefs->getFormVariant());

//echo more common stuff
echo $this->Form->end();

I don’t think Form::context() can actually set a value though. This is the code from FormHelper:

  /**
     * Get the context instance for the current form set.
     *
     * If there is no active form null will be returned.
     *
     * @param \Cake\View\Form\ContextInterface|null $context Either the new context when setting, or null to get.
     * @return \Cake\View\Form\ContextInterface The context for the form.
     */
    public function context($context = null)
    {
        if ($context instanceof ContextInterface) {
            $this->_context = $context;
        }

        return $this->_getContext();
    }

    /**
     * Find the matching context provider for the data.
     *
     * If no type can be matched a NullContext will be returned.
     *
     * @param mixed $data The data to get a context provider for.
     * @return \Cake\View\Form\ContextInterface Context provider.
     * @throws \RuntimeException when the context class does not implement the
     *   ContextInterface.
     */
    protected function _getContext($data = [])
    {
        if (isset($this->_context) && empty($data)) {
            return $this->_context;
        }
        $data += ['entity' => null];

        return $this->_context = $this->contextFactory()
            ->get($this->_View->getRequest(), $data);
    }

It looks to me like that will always end up passing a null entity as the context. It’s just a point of curiosity though since I have a reasonable alternative.

public function context($context = null) means that if you don’t provide a parameter to the function, then it’ll be null. If you do provide a parameter, then it’ll be whatever you send.

public function context($context = null) means…

Right. Got that.

I mis-read the _getContext() logic. I thought that was ignoring my just set context.

My error turned out to be a faulty assumption about the nature of a Cake\Form object. I thought it satisfied the requirement of implementing ContextInterface. It does not.