class DatabaseBackend

Same name in this branch
  1. 8.9.x core/lib/Drupal/Core/Cache/DatabaseBackend.php \Drupal\Core\Cache\DatabaseBackend
Same name in other branches
  1. 9 core/lib/Drupal/Core/Flood/DatabaseBackend.php \Drupal\Core\Flood\DatabaseBackend
  2. 9 core/lib/Drupal/Core/Cache/DatabaseBackend.php \Drupal\Core\Cache\DatabaseBackend
  3. 10 core/lib/Drupal/Core/Flood/DatabaseBackend.php \Drupal\Core\Flood\DatabaseBackend
  4. 10 core/lib/Drupal/Core/Cache/DatabaseBackend.php \Drupal\Core\Cache\DatabaseBackend
  5. 11.x core/lib/Drupal/Core/Flood/DatabaseBackend.php \Drupal\Core\Flood\DatabaseBackend
  6. 11.x core/lib/Drupal/Core/Cache/DatabaseBackend.php \Drupal\Core\Cache\DatabaseBackend

Defines the database flood backend. This is the default Drupal backend.

Hierarchy

Expanded class hierarchy of DatabaseBackend

2 files declare their use of DatabaseBackend
FloodTest.php in core/modules/system/tests/src/Kernel/System/FloodTest.php
UserLoginHttpTest.php in core/modules/user/tests/src/Functional/UserLoginHttpTest.php
1 string reference to 'DatabaseBackend'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses DatabaseBackend
flood in core/core.services.yml
Drupal\Core\Flood\DatabaseBackend

File

core/lib/Drupal/Core/Flood/DatabaseBackend.php, line 12

Namespace

Drupal\Core\Flood
View source
class DatabaseBackend implements FloodInterface {
    
    /**
     * The database table name.
     */
    const TABLE_NAME = 'flood';
    
    /**
     * The database connection used to store flood event information.
     *
     * @var \Drupal\Core\Database\Connection
     */
    protected $connection;
    
    /**
     * The request stack.
     *
     * @var \Symfony\Component\HttpFoundation\RequestStack
     */
    protected $requestStack;
    
    /**
     * Construct the DatabaseBackend.
     *
     * @param \Drupal\Core\Database\Connection $connection
     *   The database connection which will be used to store the flood event
     *   information.
     * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
     *   The request stack used to retrieve the current request.
     */
    public function __construct(Connection $connection, RequestStack $request_stack) {
        $this->connection = $connection;
        $this->requestStack = $request_stack;
    }
    
    /**
     * {@inheritdoc}
     */
    public function register($name, $window = 3600, $identifier = NULL) {
        if (!isset($identifier)) {
            $identifier = $this->requestStack
                ->getCurrentRequest()
                ->getClientIp();
        }
        $try_again = FALSE;
        try {
            $this->doInsert($name, $window, $identifier);
        } catch (\Exception $e) {
            $try_again = $this->ensureTableExists();
            if (!$try_again) {
                throw $e;
            }
        }
        if ($try_again) {
            $this->doInsert($name, $window, $identifier);
        }
    }
    
    /**
     * Inserts an event into the flood table
     *
     * @param string $name
     *   The name of an event.
     * @param int $window
     *   Number of seconds before this event expires.
     * @param string $identifier
     *   Unique identifier of the current user.
     *
     * @see \Drupal\Core\Flood\DatabaseBackend::register
     */
    protected function doInsert($name, $window, $identifier) {
        $this->connection
            ->insert(static::TABLE_NAME)
            ->fields([
            'event' => $name,
            'identifier' => $identifier,
            'timestamp' => REQUEST_TIME,
            'expiration' => REQUEST_TIME + $window,
        ])
            ->execute();
    }
    
    /**
     * {@inheritdoc}
     */
    public function clear($name, $identifier = NULL) {
        if (!isset($identifier)) {
            $identifier = $this->requestStack
                ->getCurrentRequest()
                ->getClientIp();
        }
        try {
            $this->connection
                ->delete(static::TABLE_NAME)
                ->condition('event', $name)
                ->condition('identifier', $identifier)
                ->execute();
        } catch (\Exception $e) {
            $this->catchException($e);
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function isAllowed($name, $threshold, $window = 3600, $identifier = NULL) {
        if (!isset($identifier)) {
            $identifier = $this->requestStack
                ->getCurrentRequest()
                ->getClientIp();
        }
        try {
            $number = $this->connection
                ->select(static::TABLE_NAME, 'f')
                ->condition('event', $name)
                ->condition('identifier', $identifier)
                ->condition('timestamp', REQUEST_TIME - $window, '>')
                ->countQuery()
                ->execute()
                ->fetchField();
            return $number < $threshold;
        } catch (\Exception $e) {
            $this->catchException($e);
            return TRUE;
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function garbageCollection() {
        try {
            $return = $this->connection
                ->delete(static::TABLE_NAME)
                ->condition('expiration', REQUEST_TIME, '<')
                ->execute();
        } catch (\Exception $e) {
            $this->catchException($e);
        }
    }
    
    /**
     * Check if the flood table exists and create it if not.
     */
    protected function ensureTableExists() {
        try {
            $database_schema = $this->connection
                ->schema();
            if (!$database_schema->tableExists(static::TABLE_NAME)) {
                $schema_definition = $this->schemaDefinition();
                $database_schema->createTable(static::TABLE_NAME, $schema_definition);
                return TRUE;
            }
        } catch (DatabaseException $e) {
            return TRUE;
        }
        return FALSE;
    }
    
    /**
     * Act on an exception when flood might be stale.
     *
     * If the table does not yet exist, that's fine, but if the table exists and
     * yet the query failed, then the flood is stale and the exception needs to
     * propagate.
     *
     * @param $e
     *   The exception.
     *
     * @throws \Exception
     */
    protected function catchException(\Exception $e) {
        if ($this->connection
            ->schema()
            ->tableExists(static::TABLE_NAME)) {
            throw $e;
        }
    }
    
    /**
     * Defines the schema for the flood table.
     *
     * @internal
     */
    public function schemaDefinition() {
        return [
            'description' => 'Flood controls the threshold of events, such as the number of contact attempts.',
            'fields' => [
                'fid' => [
                    'description' => 'Unique flood event ID.',
                    'type' => 'serial',
                    'not null' => TRUE,
                ],
                'event' => [
                    'description' => 'Name of event (e.g. contact).',
                    'type' => 'varchar_ascii',
                    'length' => 64,
                    'not null' => TRUE,
                    'default' => '',
                ],
                'identifier' => [
                    'description' => 'Identifier of the visitor, such as an IP address or hostname.',
                    'type' => 'varchar_ascii',
                    'length' => 128,
                    'not null' => TRUE,
                    'default' => '',
                ],
                'timestamp' => [
                    'description' => 'Timestamp of the event.',
                    'type' => 'int',
                    'not null' => TRUE,
                    'default' => 0,
                ],
                'expiration' => [
                    'description' => 'Expiration timestamp. Expired events are purged on cron run.',
                    'type' => 'int',
                    'not null' => TRUE,
                    'default' => 0,
                ],
            ],
            'primary key' => [
                'fid',
            ],
            'indexes' => [
                'allow' => [
                    'event',
                    'identifier',
                    'timestamp',
                ],
                'purge' => [
                    'expiration',
                ],
            ],
        ];
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
DatabaseBackend::$connection protected property The database connection used to store flood event information.
DatabaseBackend::$requestStack protected property The request stack.
DatabaseBackend::catchException protected function Act on an exception when flood might be stale.
DatabaseBackend::clear public function Makes the flood control mechanism forget an event for the current visitor. Overrides FloodInterface::clear
DatabaseBackend::doInsert protected function Inserts an event into the flood table
DatabaseBackend::ensureTableExists protected function Check if the flood table exists and create it if not.
DatabaseBackend::garbageCollection public function Cleans up expired flood events. This method is called automatically on
cron run.
Overrides FloodInterface::garbageCollection
DatabaseBackend::isAllowed public function Checks whether a user is allowed to proceed with the specified event. Overrides FloodInterface::isAllowed
DatabaseBackend::register public function Registers an event for the current visitor to the flood control mechanism. Overrides FloodInterface::register
DatabaseBackend::schemaDefinition public function Defines the schema for the flood table.
DatabaseBackend::TABLE_NAME constant The database table name.
DatabaseBackend::__construct public function Construct the DatabaseBackend.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.