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
  • 1
    $form_state->set('foo', 'bar'), $form_state->get('foo'). Possibly with $form_state->setRebuild() in case of ajax.
    – user21641
    Aug 21, 2017 at 0:48

2 Answers 2

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.

3
  • 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, 2017 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, 2017 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, 2017 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)

4
  • 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, 2017 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?
    – 4uk4
    Aug 21, 2017 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, 2017 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.
    – 4uk4
    Aug 21, 2017 at 17:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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