3

I did see some form classes which set form object properties in the buildForm() method.

However, these property values are no longer available in submitForm().

Simplified/incomplete example:

class MyForm {

  private $arg0;

  public function buildForm($form, $form_state, $nid) {
    $this->nid = $nid;
  }

  public function submitForm($form, $form_state) {
    $nid = $this->nid;  // $nid will be NULL.
  }
}

Actually there are some callbacks where these property values are available. E.g. if you register a #process => [[$this, 'process']] callback.

But it is not really surprising that the values are gone in some callbacks. ::buildForm() is not a constructor.

In some other form classes, they use $form_state->set($key, $value) to make data available to submitForm(). This works, but it somehow seems ugly.

Another way would be to store these values in the $form array as '#type' => 'value', or as #_my_data' => .... Again, I find this ugly.

Is there a more reliable way to initialize object properties on the form object based on url parameters? E.g. to have these parameters sent to the constructor?

Also, depending how I do this, do I need to worry that the data will be serialized?

(To me this seems like a flaw in the form API. But maybe I am missing something. I might open an issue on drupal.org, if the answer does not change my mind.)

  • 1
    $form_state->set('foo', 'bar'), $form_state->get('foo'). Possibly with $form_state->setRebuild() in case of ajax. – user21641 Aug 21 '17 at 0:48
3

Yes, you can use form_state storage.

Object properties should work fine as well as long as you do not use private, use protected. private conflicts with the DependencySerializationTrait.

And yes, everything you store like that is serialized (unless it is a service, then that trait will remove and restore it) because that's the only way to transport this data across different requests.

  • The reason I am not happy is lack of encapsulation (if stored in form_state or the form array), and lack of language-level invariants if stored in the form object - because buildForm() is not the constructor. It means the object properties would be in an incomplete state before the buildForm() method. – donquixote Aug 21 '17 at 21:52
  • So if you say everything is serialized you mean the $form array, the $form_state object and the form object? – donquixote Aug 21 '17 at 21:53
  • 2
    The $form array, parts of $form_state including the form object, yes. Which can get somewhat confusing, because on a cached request, there will be two instances of the form object, the one that was created and the one that is unserialized. Specifically if you use a callback like array($this, 'method'), then the object is serialized like that and it will call the method on that. That's why it is recommended and easier to use '::method', which will call it on the new instance. That's not something we can do anything about, that's just how PHP works. – Berdir Aug 22 '17 at 9:20
2

If you want to store parameters from the url the most reliable place for this is a form value. This is because the form gets built and rendered without that $form or $form_state is serialized and stored. And even if it would, the rendered form may be delivered later than 6 hours when the key_value storage has expired. You can store data in $form_state or in class properties of the form class under certain circumstances as @Berdir explained, but this depends on when and where you want to set and get the data.

So in general an example to store the parameter nid in the form would look like this:

public function buildForm(array $form, FormStateInterface $form_state, $nid) {

  $form['nid'] = [
    '#type' => 'hidden',
    '#value' => $nid,
  ];
  $form['#cache']['contexts'][] = 'url';

}

(I don't think this is a flaw, it's more about how html forms work)

  • 1
    type hidden and type value is very different. hidden should only be used for values that by design should be submitted/alterable by the browser, because they are. And type value and $form_state storage is technically being stored more or less in the same place, and $form_state storage is IMHO cleaner as it doesn't pollute the form structure (server side, it is not visible in the client) – Berdir Aug 21 '17 at 16:08
  • @Berdir, a hidden form element is only alterable by the browser if you use #default-value. I also use $form_state if possible, but where are the values stored when the rendered form is cached? – 4k4 Aug 21 '17 at 16:42
  • Fair enough, if you use #value then it can't be overridden, but it is still visible on the client and sent to/from the client. Well, as mentioned, the form is only cached after the first ajax request, then it is stored in key_value, like the rest of the form definition/state. For the first POST request, it will actually just re-create the form objects and will call buildForm() again, which will set the object property or $form_state storage – Berdir Aug 21 '17 at 17:10
  • @Berdir, yes, good summary of the time line. So you have to consider when the data is available to decide where you can store it. – 4k4 Aug 21 '17 at 17:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.