You are here

class DataProviderDbQuery in RESTful 7.2

Hierarchy

Expanded class hierarchy of DataProviderDbQuery

File

src/Plugin/resource/DataProvider/DataProviderDbQuery.php, line 23
Contains \Drupal\restful\Plugin\resource\DataProvider\DataProviderDbQuery.

Namespace

Drupal\restful\Plugin\resource\DataProvider
View source
class DataProviderDbQuery extends DataProvider implements DataProviderDbQueryInterface {

  /**
   * The name of the table to query.
   *
   * @var string
   */
  protected $tableName;

  /**
   * The name of the column(s) in the table to be used as the unique key.
   *
   * @var array
   */
  protected $idColumn;

  /**
   * The separator used to divide a key into its table columns when there is
   * more than one column.
   */
  const COLUMN_IDS_SEPARATOR = '::';

  /**
   * Holds the primary field.
   *
   * @var string
   */
  protected $primary;

  /**
   * {@inheritdoc}
   */
  public function __construct(RequestInterface $request, ResourceFieldCollectionInterface $field_definitions, $account, $plugin_id, $resource_path = NULL, array $options = array(), $langcode = NULL) {
    parent::__construct($request, $field_definitions, $account, $plugin_id, $resource_path, $options, $langcode);

    // Validate keys exist in the plugin's "data provider options".
    $required_keys = array(
      'tableName',
      'idColumn',
    );
    $required_callback = function ($required_key) {
      if (!$this->options[$required_key]) {
        throw new ServiceUnavailableException(sprintf('%s is missing "%s" property in the "dataProvider" key of the $plugin', get_class($this), $required_key));
      }
    };
    array_walk($required_keys, $required_callback);
    $this->tableName = $this->options['tableName'];
    $this->idColumn = $this->options['idColumn'];
    $this->primary = empty($this->options['primary']) ? NULL : ($this->primary = $this->options['primary']);
  }

  /**
   * {@inheritdoc}
   */
  public function getTableName() {
    return $this->tableName;
  }

  /**
   * {@inheritdoc}
   */
  public function setTableName($table_name) {
    $this->tableName = $table_name;
  }

  /**
   * {@inheritdoc}
   */
  public function getPrimary() {
    return $this->primary;
  }

  /**
   * {@inheritdoc}
   */
  public function setPrimary($primary) {
    $this->primary = $primary;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheFragments($identifier) {
    if (is_array($identifier)) {

      // Like in https://example.org/api/articles/1,2,3.
      $identifier = implode(ResourceInterface::IDS_SEPARATOR, $identifier);
    }
    $fragments = new ArrayCollection(array(
      'resource' => CacheDecoratedResource::serializeKeyValue($this->pluginId, $this
        ->canonicalPath($identifier)),
      'table_name' => $this
        ->getTableName(),
      'column' => implode(',', $this
        ->getIdColumn()),
    ));
    $options = $this
      ->getOptions();
    switch ($options['renderCache']['granularity']) {
      case DRUPAL_CACHE_PER_USER:
        if ($uid = $this
          ->getAccount()->uid) {
          $fragments
            ->set('user_id', (int) $uid);
        }
        break;
      case DRUPAL_CACHE_PER_ROLE:
        $fragments
          ->set('user_role', implode(',', $this
          ->getAccount()->roles));
        break;
    }
    return $fragments;
  }

  /**
   * {@inheritdoc}
   */
  public function count() {
    return intval($this
      ->getQueryForList()
      ->countQuery()
      ->execute()
      ->fetchField());
  }

  /**
   * {@inheritdoc}
   */
  public function isPrimaryField($field_name) {
    return $this->primary == $field_name;
  }

  /**
   * Get ID column.
   *
   * @return array
   *   An array with the name of the column(s) in the table to be used as the
   *   unique key.
   */
  protected function getIdColumn() {
    return is_array($this->idColumn) ? $this->idColumn : array(
      $this->idColumn,
    );
  }

  /**
   * {@inheritdoc}
   */
  public function create($object) {
    $save = FALSE;
    $original_object = $object;
    $id_columns = $this
      ->getIdColumn();
    $record = array();
    foreach ($this->fieldDefinitions as $public_field_name => $resource_field) {

      /* @var ResourceFieldDbColumnInterface $resource_field */
      if (!$this
        ->methodAccess($resource_field)) {

        // Allow passing the value in the request.
        unset($original_object[$public_field_name]);
        continue;
      }
      $property_name = $resource_field
        ->getProperty();

      // If this is the primary field, skip.
      if ($this
        ->isPrimaryField($property_name)) {
        unset($original_object[$public_field_name]);
        continue;
      }
      if (isset($object[$public_field_name])) {
        $record[$property_name] = $object[$public_field_name];
      }
      unset($original_object[$public_field_name]);
      $save = TRUE;
    }

    // No request was sent.
    if (!$save) {
      throw new BadRequestException('No values were sent with the request.');
    }

    // If the original request is not empty, then illegal values are present.
    if (!empty($original_object)) {
      $error_message = format_plural(count($original_object), 'Property @names is invalid.', 'Property @names are invalid.', array(
        '@names' => implode(', ', array_keys($original_object)),
      ));
      throw new BadRequestException($error_message);
    }

    // Once the record is built, write it and view it.
    if (drupal_write_record($this
      ->getTableName(), $record)) {

      // Handle multiple id columns.
      $id_values = array();
      foreach ($id_columns as $id_column) {
        $id_values[$id_column] = $record[$id_column];
      }
      $new_id = implode(self::COLUMN_IDS_SEPARATOR, $id_values);
      return array(
        $this
          ->view($new_id),
      );
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function view($identifier) {
    $query = $this
      ->getQuery();
    foreach ($this
      ->getIdColumn() as $index => $column) {
      $identifier = is_array($identifier) ? $identifier : array(
        $identifier,
      );
      $query
        ->condition($this
        ->getTableName() . '.' . $column, current($this
        ->getColumnFromIds($identifier, $index)));
    }
    $this
      ->addExtraInfoToQuery($query);
    $result = $query
      ->range(0, 1)
      ->execute()
      ->fetch(\PDO::FETCH_OBJ);
    return $this
      ->mapDbRowToPublicFields($result);
  }

  /**
   * {@inheritdoc}
   */
  protected function mapDbRowToPublicFields($row) {
    $resource_field_collection = $this
      ->initResourceFieldCollection($row);

    // Loop over all the defined public fields.
    foreach ($this->fieldDefinitions as $public_field_name => $resource_field) {
      $value = NULL;

      /* @var ResourceFieldDbColumnInterface $resource_field */
      if (!$this
        ->methodAccess($resource_field)) {

        // Allow passing the value in the request.
        continue;
      }
      $resource_field_collection
        ->set($resource_field
        ->id(), $resource_field);
    }
    return $resource_field_collection;
  }

  /**
   * Adds query tags and metadata to the EntityFieldQuery.
   *
   * @param \SelectQuery $query
   *   The query to enhance.
   */
  protected function addExtraInfoToQuery($query) {
    $query
      ->addTag('restful');
    $query
      ->addMetaData('account', $this
      ->getAccount());
    $query
      ->addMetaData('restful_handler', $this);
  }

  /**
   * {@inheritdoc}
   */
  public function viewMultiple(array $identifiers) {

    // Get a list query with all the sorting and pagination in place.
    $query = $this
      ->getQueryForList();
    if (empty($identifiers)) {
      return array();
    }
    foreach ($this
      ->getIdColumn() as $index => $column) {
      $query
        ->condition($this
        ->getTableName() . '.' . $column, $this
        ->getColumnFromIds($identifiers, $index), 'IN');
    }
    $results = $query
      ->execute();
    $return = array();
    foreach ($results as $result) {
      $return[] = $this
        ->mapDbRowToPublicFields($result);
    }
    return $return;
  }

  /**
   * {@inheritdoc}
   */
  protected function getQueryForList() {
    $query = $this
      ->getQuery();
    $this
      ->queryForListSort($query);
    $this
      ->queryForListFilter($query);
    $this
      ->queryForListPagination($query);
    $this
      ->addExtraInfoToQuery($query);
    return $query;
  }

  /**
   * {@inheritdoc}
   */
  public function update($identifier, $object, $replace = FALSE) {

    // Build the update array.
    $save = FALSE;
    $original_object = $object;
    $id_columns = $this
      ->getIdColumn();
    $record = array();
    foreach ($this->fieldDefinitions as $public_field_name => $resource_field) {

      /* @var ResourceFieldDbColumnInterface $resource_field */
      if (!$this
        ->methodAccess($resource_field)) {

        // Allow passing the value in the request.
        unset($original_object[$public_field_name]);
        continue;
      }
      $property = $resource_field
        ->getProperty();

      // If this is the primary field, skip.
      if ($this
        ->isPrimaryField($property)) {
        continue;
      }
      if (isset($object[$public_field_name])) {
        $record[$property] = $object[$public_field_name];
      }
      elseif ($replace) {
        $record[$property] = NULL;
      }
      unset($original_object[$public_field_name]);
      $save = TRUE;
    }

    // No request was sent.
    if (!$save) {
      throw new BadRequestException('No values were sent with the request.');
    }

    // If the original request is not empty, then illegal values are present.
    if (!empty($original_object)) {
      $error_message = format_plural(count($original_object), 'Property @names is invalid.', 'Property @names are invalid.', array(
        '@names' => implode(', ', array_keys($original_object)),
      ));
      throw new BadRequestException($error_message);
    }

    // Add the id column values into the record.
    foreach ($this
      ->getIdColumn() as $index => $column) {
      $record[$column] = current($this
        ->getColumnFromIds(array(
        $identifier,
      ), $index));
    }

    // Once the record is built, write it.
    if (!drupal_write_record($this
      ->getTableName(), $record, $id_columns)) {
      throw new ServiceUnavailableException('Record could not be updated to the database.');
    }
    return array(
      $this
        ->view($identifier),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function remove($identifier) {

    // If it's a delete method we will want a 204 response code.
    // Set the HTTP headers.
    $this
      ->setHttpHeader('Status', 204);
    $query = db_delete($this
      ->getTableName());
    foreach ($this
      ->getIdColumn() as $index => $column) {
      $query
        ->condition($column, current($this
        ->getColumnFromIds(array(
        $identifier,
      ), $index)));
    }
    $query
      ->execute();
  }

  /**
   * {@inheritdoc}
   */
  public function getIndexIds() {
    $results = $this
      ->getQueryForList()
      ->execute();
    $ids = array();
    foreach ($results as $result) {
      $ids[] = array_map(function ($id_column) use ($result) {
        return $result->{$id_column};
      }, $this
        ->getIdColumn());
    }
    return $ids;
  }

  /**
   * {@inheritdoc}
   */
  public function index() {
    $results = $this
      ->getQueryForList()
      ->execute();
    $return = array();
    foreach ($results as $result) {
      $return[] = $this
        ->mapDbRowToPublicFields($result);
    }
    return $return;
  }

  /**
   * Defines default sort columns if none are provided via the request URL.
   *
   * @return array
   *   Array keyed by the database column name, and the order ('ASC' or 'DESC')
   *   as value.
   */
  protected function defaultSortInfo() {
    $sorts = array();
    foreach ($this
      ->getIdColumn() as $column) {
      if (!$this->fieldDefinitions
        ->get($column)) {

        // Sort by the first ID column that is a public field.
        $sorts[$column] = 'ASC';
        break;
      }
    }
    return $sorts;
  }

  /**
   * Sort the query for list.
   *
   * @param \SelectQuery $query
   *   The query object.
   *
   * @throws BadRequestException
   *
   * @see \RestfulEntityBase::getQueryForList
   */
  protected function queryForListSort(\SelectQuery $query) {

    // Get the sorting options from the request object.
    $sorts = $this
      ->parseRequestForListSort();
    $sorts = $sorts ? $sorts : $this
      ->defaultSortInfo();
    foreach ($sorts as $sort => $direction) {

      /* @var ResourceFieldDbColumnInterface $sort_field */
      if ($sort_field = $this->fieldDefinitions
        ->get($sort)) {
        $query
          ->orderBy($sort_field
          ->getColumnForQuery(), $direction);
      }
    }
  }

  /**
   * Filter the query for list.
   *
   * @param \SelectQuery $query
   *   The query object.
   *
   * @throws BadRequestException
   *
   * @see \RestfulEntityBase::getQueryForList
   */
  protected function queryForListFilter(\SelectQuery $query) {
    foreach ($this
      ->parseRequestForListFilter() as $filter) {

      /* @var ResourceFieldDbColumnInterface $filter_field */
      if (!($filter_field = $this->fieldDefinitions
        ->get($filter['public_field']))) {
        continue;
      }
      $column_name = $filter_field
        ->getColumnForQuery();
      if (in_array(strtoupper($filter['operator'][0]), array(
        'IN',
        'NOT IN',
        'BETWEEN',
      ))) {
        $query
          ->condition($column_name, $filter['value'], $filter['operator'][0]);
        continue;
      }
      $condition = db_condition($filter['conjunction']);
      for ($index = 0; $index < count($filter['value']); $index++) {
        $condition
          ->condition($column_name, $filter['value'][$index], $filter['operator'][$index]);
      }
      $query
        ->condition($condition);
    }
  }

  /**
   * Set correct page (i.e. range) for the query for list.
   *
   * Determine the page that should be seen. Page 1, is actually offset 0 in the
   * query range.
   *
   * @param \SelectQuery $query
   *   The query object.
   *
   * @throws BadRequestException
   *
   * @see \RestfulEntityBase::getQueryForList
   */
  protected function queryForListPagination(\SelectQuery $query) {
    list($range, $offset) = $this
      ->parseRequestForListPagination();
    $query
      ->range($range, $offset);
  }

  /**
   * Get a basic query object.
   *
   * @return \SelectQuery
   *   A new SelectQuery object for this connection.
   */
  protected function getQuery() {
    $table = $this
      ->getTableName();
    return db_select($table)
      ->fields($table);
  }

  /**
   * Given an array of string ID's return a single column.
   *
   * Strings are divided by the delimiter self::COLUMN_IDS_SEPARATOR.
   *
   * @param array $identifiers
   *   An array of object IDs.
   * @param int $column
   *   0-N Zero indexed
   *
   * @return array
   *   Returns an array at index $column
   */
  protected function getColumnFromIds(array $identifiers, $column = 0) {

    // Get a single column.
    $get_part = function ($identifier) use ($column) {
      $parts = explode(static::COLUMN_IDS_SEPARATOR, $identifier);
      if (!isset($parts[$column])) {
        throw new ServerConfigurationException('Invalid ID provided.');
      }
      return $parts[$column];
    };
    return array_map($get_part, $identifiers);
  }

  /**
   * {@inheritdoc}
   */
  protected function initDataInterpreter($identifier) {
    return new DataInterpreterArray($this
      ->getAccount(), new ArrayWrapper((array) $identifier));
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DataProvider::$account protected property The account authenticated from the request for entity access checks.
DataProvider::$fieldDefinitions protected property The field definitions.
DataProvider::$langcode protected property Determines the language of the items that should be returned.
DataProvider::$metadata protected property Array of metadata. Use this as a mean to pass info to the render layer.
DataProvider::$options protected property User defined options.
DataProvider::$pluginId protected property Resource identifier.
DataProvider::$range protected property Determines the number of items that should be returned when viewing lists.
DataProvider::$request protected property The request
DataProvider::$resourcePath protected property The resource path.
DataProvider::addOptions public function Adds the options in the provided array to the data provider options. Overrides DataProviderInterface::addOptions
DataProvider::canonicalPath public function Generates the canonical path for a given path. Overrides DataProviderInterface::canonicalPath 1
DataProvider::discover public function Return the discovery information for the given entity. Overrides DataProviderInterface::discover
DataProvider::getAccount public function Gets the authenticated account. Overrides DataProviderInterface::getAccount
DataProvider::getLangCode public function Get the language code. Overrides DataProviderInterface::getLangCode
DataProvider::getLanguage protected static function Gets the global language.
DataProvider::getMetadata public function Returns the metadata collection. Overrides DataProviderInterface::getMetadata
DataProvider::getOptions public function Gets the data provider options. Overrides DataProviderInterface::getOptions
DataProvider::getRange public function Gets the range. Overrides DataProviderInterface::getRange
DataProvider::getRequest public function Gets the request. Overrides DataProviderInterface::getRequest
DataProvider::getResourcePath public function Get the resource path. Overrides DataProviderInterface::getResourcePath
DataProvider::initResourceFieldCollection protected function Initialize the empty resource field collection to bundle the output.
DataProvider::isNestedField public static function Checks if the passed in string is a dot-nested field. Overrides DataProviderInterface::isNestedField
DataProvider::isValidConjunctionForFilter protected static function Check if a conjunction is valid for filtering. 1
DataProvider::isValidOperatorsForFilter protected static function Check if an operator is valid for filtering. 1
DataProvider::methodAccess public function Checks if the provided field can be used with the current method. Overrides DataProviderInterface::methodAccess
DataProvider::parseRequestForListFilter protected function Filter the query for list.
DataProvider::parseRequestForListPagination protected function Parses the request object to get the pagination options.
DataProvider::parseRequestForListSort protected function Parses the request to get the sorting options.
DataProvider::processFilterInput public static function Processes the input for a filter and adds the appropriate defaults. Overrides DataProviderInterface::processFilterInput
DataProvider::setAccount public function Sets the authenticated account. Overrides DataProviderInterface::setAccount
DataProvider::setHttpHeader protected function Sets an HTTP header.
DataProvider::setLangCode public function Sets the language code. Overrides DataProviderInterface::setLangCode
DataProvider::setOptions public function Sets the options. Overrides DataProviderInterface::setOptions
DataProvider::setRange public function Sets the range. Overrides DataProviderInterface::setRange
DataProvider::setRequest public function Sets the request. Overrides DataProviderInterface::setRequest
DataProvider::setResourcePath public function Set the resource path. Overrides DataProviderInterface::setResourcePath
DataProviderDbQuery::$idColumn protected property The name of the column(s) in the table to be used as the unique key.
DataProviderDbQuery::$primary protected property Holds the primary field.
DataProviderDbQuery::$tableName protected property The name of the table to query.
DataProviderDbQuery::addExtraInfoToQuery protected function Adds query tags and metadata to the EntityFieldQuery. Overrides DataProvider::addExtraInfoToQuery
DataProviderDbQuery::COLUMN_IDS_SEPARATOR constant The separator used to divide a key into its table columns when there is more than one column.
DataProviderDbQuery::count public function Counts the total results for the index call. Overrides CrudInterface::count
DataProviderDbQuery::create public function Create operation. Overrides CrudInterface::create
DataProviderDbQuery::defaultSortInfo protected function Defines default sort columns if none are provided via the request URL.
DataProviderDbQuery::getCacheFragments public function Gets the entity context. Overrides DataProvider::getCacheFragments
DataProviderDbQuery::getColumnFromIds protected function Given an array of string ID's return a single column.
DataProviderDbQuery::getIdColumn protected function Get ID column.
DataProviderDbQuery::getIndexIds public function Returns the ID to render for the current index GET request. Overrides DataProviderInterface::getIndexIds
DataProviderDbQuery::getPrimary public function Gets the primary field. Overrides DataProviderDbQueryInterface::getPrimary
DataProviderDbQuery::getQuery protected function Get a basic query object.
DataProviderDbQuery::getQueryForList protected function
DataProviderDbQuery::getTableName public function Get the name of the table to query. Overrides DataProviderDbQueryInterface::getTableName
DataProviderDbQuery::index public function List operation. Overrides DataProvider::index
DataProviderDbQuery::initDataInterpreter protected function Get the data interpreter. Overrides DataProvider::initDataInterpreter
DataProviderDbQuery::isPrimaryField public function Checks if the current field is the primary field. Overrides DataProviderDbQueryInterface::isPrimaryField
DataProviderDbQuery::mapDbRowToPublicFields protected function
DataProviderDbQuery::queryForListFilter protected function Filter the query for list.
DataProviderDbQuery::queryForListPagination protected function Set correct page (i.e. range) for the query for list.
DataProviderDbQuery::queryForListSort protected function Sort the query for list.
DataProviderDbQuery::remove public function Delete operation. Overrides CrudInterface::remove
DataProviderDbQuery::setPrimary public function Sets the primary field. Overrides DataProviderDbQueryInterface::setPrimary
DataProviderDbQuery::setTableName public function Set the name of the table to query. Overrides DataProviderDbQueryInterface::setTableName
DataProviderDbQuery::update public function Update operation. Overrides CrudInterface::update
DataProviderDbQuery::view public function Read operation. Overrides CrudInterface::view
DataProviderDbQuery::viewMultiple public function Read operation. Overrides CrudInterface::viewMultiple
DataProviderDbQuery::__construct public function Constructor. Overrides DataProvider::__construct