You are here

class FormCache in Drupal 8

Same name and namespace in other branches
  1. 9 core/lib/Drupal/Core/Form/FormCache.php \Drupal\Core\Form\FormCache
  2. 10 core/lib/Drupal/Core/Form/FormCache.php \Drupal\Core\Form\FormCache

Encapsulates the caching of a form and its form state.

Hierarchy

Expanded class hierarchy of FormCache

Related topics

1 file declares its use of FormCache
FormCacheTest.php in core/tests/Drupal/Tests/Core/Form/FormCacheTest.php
1 string reference to 'FormCache'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses FormCache
form_cache in core/core.services.yml
Drupal\Core\Form\FormCache

File

core/lib/Drupal/Core/Form/FormCache.php, line 20

Namespace

Drupal\Core\Form
View source
class FormCache implements FormCacheInterface {

  /**
   * The factory for expirable key value stores used by form cache.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface
   */
  protected $keyValueExpirableFactory;

  /**
   * The CSRF token generator to validate the form token.
   *
   * @var \Drupal\Core\Access\CsrfTokenGenerator
   */
  protected $csrfToken;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * A policy rule determining the cacheability of a request.
   *
   * @var \Drupal\Core\PageCache\RequestPolicyInterface
   */
  protected $requestPolicy;

  /**
   * The app root.
   *
   * @var string
   */
  protected $root;

  /**
   * Constructs a new FormCache.
   *
   * @param string $root
   *   The app root.
   * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
   *   The key value expirable factory, used to create key value expirable
   *   stores for the form cache and form state cache.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
   *   The CSRF token generator.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy
   *   A policy rule determining the cacheability of a request.
   */
  public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, ModuleHandlerInterface $module_handler, AccountInterface $current_user, CsrfTokenGenerator $csrf_token, LoggerInterface $logger, RequestStack $request_stack, RequestPolicyInterface $request_policy) {
    $this->root = $root;
    $this->keyValueExpirableFactory = $key_value_expirable_factory;
    $this->moduleHandler = $module_handler;
    $this->currentUser = $current_user;
    $this->logger = $logger;
    $this->csrfToken = $csrf_token;
    $this->requestStack = $request_stack;
    $this->requestPolicy = $request_policy;
  }

  /**
   * {@inheritdoc}
   */
  public function getCache($form_build_id, FormStateInterface $form_state) {
    if ($form = $this->keyValueExpirableFactory
      ->get('form')
      ->get($form_build_id)) {
      if (isset($form['#cache_token']) && $this->csrfToken
        ->validate($form['#cache_token']) || !isset($form['#cache_token']) && $this->currentUser
        ->isAnonymous()) {
        $this
          ->loadCachedFormState($form_build_id, $form_state);

        // Generate a new #build_id if the cached form was rendered on a
        // cacheable page.
        $build_info = $form_state
          ->getBuildInfo();
        if (!empty($build_info['immutable'])) {
          $form['#build_id_old'] = $form['#build_id'];
          $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
          $form['form_build_id']['#value'] = $form['#build_id'];
          $form['form_build_id']['#id'] = $form['#build_id'];
          unset($build_info['immutable']);
          $form_state
            ->setBuildInfo($build_info);
        }
        return $form;
      }
    }
  }

  /**
   * Loads the cached form state.
   *
   * @param string $form_build_id
   *   The unique form build ID.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  protected function loadCachedFormState($form_build_id, FormStateInterface $form_state) {
    if ($stored_form_state = $this->keyValueExpirableFactory
      ->get('form_state')
      ->get($form_build_id)) {

      // Re-populate $form_state for subsequent rebuilds.
      $form_state
        ->setFormState($stored_form_state);

      // If the original form is contained in include files, load the files.
      // @see \Drupal\Core\Form\FormStateInterface::loadInclude()
      $build_info = $form_state
        ->getBuildInfo();
      $build_info += [
        'files' => [],
      ];
      foreach ($build_info['files'] as $file) {
        if (is_array($file)) {
          $file += [
            'type' => 'inc',
            'name' => $file['module'],
          ];
          $this->moduleHandler
            ->loadInclude($file['module'], $file['type'], $file['name']);
        }
        elseif (file_exists($file)) {
          require_once $this->root . '/' . $file;
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function setCache($form_build_id, $form, FormStateInterface $form_state) {

    // Cache forms for 6 hours by default.
    $expire = Settings::get('form_cache_expiration', 21600);

    // Ensure that the form build_id embedded in the form structure is the same
    // as the one passed in as a parameter. This is an additional safety measure
    // to prevent legacy code operating directly with
    // \Drupal::formBuilder()->getCache() and \Drupal::formBuilder()->setCache()
    // from accidentally overwriting immutable form state.
    if (isset($form['#build_id']) && $form['#build_id'] != $form_build_id) {
      $this->logger
        ->error('Form build-id mismatch detected while attempting to store a form in the cache.');
      return;
    }

    // Cache form structure.
    if (isset($form)) {
      if ($this->currentUser
        ->isAuthenticated()) {
        $form['#cache_token'] = $this->csrfToken
          ->get();
      }
      unset($form['#build_id_old']);
      $this->keyValueExpirableFactory
        ->get('form')
        ->setWithExpire($form_build_id, $form, $expire);
    }
    if ($data = $form_state
      ->getCacheableArray()) {
      $this->keyValueExpirableFactory
        ->get('form_state')
        ->setWithExpire($form_build_id, $data, $expire);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function deleteCache($form_build_id) {
    $this->keyValueExpirableFactory
      ->get('form')
      ->delete($form_build_id);
    $this->keyValueExpirableFactory
      ->get('form_state')
      ->delete($form_build_id);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
FormCache::$configFactory protected property The config factory.
FormCache::$csrfToken protected property The CSRF token generator to validate the form token.
FormCache::$currentUser protected property The current user.
FormCache::$keyValueExpirableFactory protected property The factory for expirable key value stores used by form cache.
FormCache::$logger protected property Logger channel.
FormCache::$moduleHandler protected property The module handler.
FormCache::$requestPolicy protected property A policy rule determining the cacheability of a request.
FormCache::$requestStack protected property The request stack.
FormCache::$root protected property The app root.
FormCache::deleteCache public function Deletes a form in the cache. Overrides FormCacheInterface::deleteCache
FormCache::getCache public function Fetches a form from the cache. Overrides FormCacheInterface::getCache
FormCache::loadCachedFormState protected function Loads the cached form state.
FormCache::setCache public function Stores a form in the cache. Overrides FormCacheInterface::setCache
FormCache::__construct public function Constructs a new FormCache.