3

I've got a Form class, the constructor of which sets a private attribute for the Model. When the form is rendered, everything seems to work as expected. The last form element is a select which, based upon the selected value, adds another textfield to the form if needed. However, the problem is, when the ajax is called for the first time, everything is OK and the additional field is added. When I try to select a different value though, I get this error as if the constructor did not run thus not setting the $model attribute of the class.

js_zfg1OcYBPiNuiGY77hX8zdIjztYiO7UAz7wbokItO2I.js:2515 Uncaught AjaxError: An AJAX HTTP error occurred. HTTP Result Code: 200
Debugging information follows.
Path: /admin/asphira/contacts/add?ajax_form=1&_wrapper_format=drupal_ajax
StatusText: OK
ResponseText: Error: Call to a member function getKeyedList() on null in
Drupal\asphira\Form\ContactForm->buildForm() (line 112 of C:\xampp\htdocs\reality\modules\asphira\src\Form\ContactForm.php).

Here is the form class.

namespace Drupal\asphira\Form;

use Drupal\asphira\Model\ContactTypeModel;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Locale\CountryManager;

class ContactForm extends FormBase {

  private $model;

  public function __construct() {
    $this->model = new ContactTypeModel();
  }

  public function getFormId() {
    return 'contact_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['name'] = array(
      '#title' => t('Name'),
      '#type' => 'textfield',
      '#required' => TRUE,
    );

    $form['surname'] = array(
      '#title' => t('Surname'),
      '#type' => 'textfield',
      '#required' => TRUE,
    );

    $form['nee_surname'] = array(
      '#title' => t('Née surname'),
      '#type' => 'textfield',
      '#required' => TRUE,
    );

    $form['street'] = array(
      '#title' => t('Street'),
      '#type' => 'textfield',
      '#required' => TRUE,
    );

    $form['street_no'] = array(
      '#title' => t('Street no.'),
      '#type' => 'textfield',
      '#required' => TRUE,
    );

    $form['zip_code'] = array(
      '#title' => t('ZIP code'),
      '#type' => 'textfield',
      '#required' => TRUE,
    );

    $form['city'] = array(
      '#title' => t('City'),
      '#type' => 'textfield',
      '#required' => TRUE,
    );

    $form['country'] = array(
      '#title' => t('Country'),
      '#type' => 'select',
      '#options' => CountryManager::getStandardList(),
      '#required' => TRUE,
    );

    $form['phone'] = array(
      '#title' => t('Telephone'),
      '#type' => 'textfield',
    );

    $form['email'] = array(
      '#title' => t('Email'),
      '#type' => 'email',
      '#required' => TRUE,
    );

    $form['contact_type_container'] = array(
      '#type' => 'container',
      '#attributes' => array(
        'id' => 'contact-type-container',
      ),
    );

    $contact_types = $this->model->getKeyedList('t_contact_type',
      'contact_type_id', 'title');

    $form['contact_type_container']['contact_type'] = array(
      '#title' => t('Contact type'),
      '#type' => 'select',
      '#options' => $contact_types,
      '#required' => TRUE,
      '#ajax' => array(
        'callback' => array($this, 'ajaxSelectContactType'),
        'wrapper' => 'contact-type-container',
        'progress' => array(
          'type' => 'throbber',
          'message' => $this->t('Working...'),
        ),
      )
    );

    $form['actions'] = array('#type' => 'actions');
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
    );

    return $form;
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    // TODO: Implement submitForm() method.
  }

  public function ajaxSelectContactType(array &$form, FormStateInterface   $form_state) {
    $contact_type = $form_state->getValue('contact_type');
    $needs_specification = $this->model->needsFurtherSpeification($contact_type);

    if ($needs_specification) {
      $form['contact_type_container']['contact_type_specification'] = array(
        '#title' => t('Specification'),
        '#type' => 'textfield',
        '#description' => '',
        '#required' => TRUE,
      );
    }

    return $form['contact_type_container'];
  }

}

Do you have any idea how is it possible that the constructor gets passed by without calling?

2 Answers 2

13

Your problem is actually something different entirely.

It is your use of private. Don't use it, use protected. Drupal has a special logic to prevent services from being serialized but it doesn't work properly on private properties, as they are invisible to the trait that is used for that.

Also, like in a similar question recently, sounds like you're re-inventing the wheel a bit. Drupal uses entities for storage of most things, instead of making your own model classes, define it as a content or config entity, which will give you a ton of things, like storage and standard entity form classes that have $this->entity. For content entities, you can also easily use base fields and widget/formatter definitions to automatically build the form and output, at least to a certain degree.

The documentation is still very much in progress, but see https://www.drupal.org/developing/api/entity and some of the sub-points, also the examples module. there are also plenty of tutorials.

2
  • Hello there! Problem solved, it works with protected property. Thanks for suggesting the tutorials; i've read through this drupal.org/node/2192175 but I somehow find it too overcomplicated for creating simple, primitive entitites with all fields predefined programatically.
    – crzpiot
    Commented Aug 27, 2016 at 12:14
  • Predefining all fields is exactly what it does. It does require a bit of code, but I guarantee that it will require more in the end when you try to do it yourself. Have a look at drupal console (drupal.org/project/console), it can generate all that with a single command.
    – Berdir
    Commented Aug 27, 2016 at 12:36
1

The code is missing the most important method a class that derives from FormBase should implement: create(). That is the static method that instantiate any class derived from FormBase. Take ActionAdminManageForm::create() as example.

public static function create(ContainerInterface $container) {
  return new static(
  $container->get('plugin.manager.action')
  );
}

Its constructor is then the following one.

public function __construct(ActionManager $manager) {
  $this->manager = $manager;
}
3
  • Thanks for a quick answer. I've updated my code and added the create() method: public static function create(ContainerInterface $container) { return new static($container->get('asphira.contact_types')); } changed my constructor: public function __construct(ContactTypeModel $model) { $this->model = $model; } and created a service: services: asphira.contact_types: class: 'Drupal\asphira\Model\ContactTypeModel' but I still get the same error.
    – crzpiot
    Commented Aug 27, 2016 at 8:55
  • Wouldn't the create() automatically be inherited from FormBase, and since this class doesn't require any new injections, doesn't need to be recreated?
    – smccabe
    Commented Feb 1, 2019 at 21:46
  • @smccabe In that case, the class would not have a constructor that directly creates a new instance of another class. That is the task of the create() method.
    – avpaderno
    Commented Feb 2, 2019 at 8:02

Your Answer

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

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