annotate core/modules/jsonapi/src/Query/EntityCondition.php @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents
children
rev   line source
Chris@5 1 <?php
Chris@5 2
Chris@5 3 namespace Drupal\jsonapi\Query;
Chris@5 4
Chris@5 5 use Drupal\Core\Cache\CacheableMetadata;
Chris@5 6 use Drupal\Core\Http\Exception\CacheableBadRequestHttpException;
Chris@5 7
Chris@5 8 /**
Chris@5 9 * A condition object for the EntityQuery.
Chris@5 10 *
Chris@5 11 * @internal JSON:API maintains no PHP API since its API is the HTTP API. This
Chris@5 12 * class may change at any time and this will break any dependencies on it.
Chris@5 13 *
Chris@5 14 * @see https://www.drupal.org/project/jsonapi/issues/3032787
Chris@5 15 * @see jsonapi.api.php
Chris@5 16 */
Chris@5 17 class EntityCondition {
Chris@5 18
Chris@5 19 /**
Chris@5 20 * The field key in the filter condition: filter[lorem][condition][<field>].
Chris@5 21 *
Chris@5 22 * @var string
Chris@5 23 */
Chris@5 24 const PATH_KEY = 'path';
Chris@5 25
Chris@5 26 /**
Chris@5 27 * The value key in the filter condition: filter[lorem][condition][<value>].
Chris@5 28 *
Chris@5 29 * @var string
Chris@5 30 */
Chris@5 31 const VALUE_KEY = 'value';
Chris@5 32
Chris@5 33 /**
Chris@5 34 * The operator key in the condition: filter[lorem][condition][<operator>].
Chris@5 35 *
Chris@5 36 * @var string
Chris@5 37 */
Chris@5 38 const OPERATOR_KEY = 'operator';
Chris@5 39
Chris@5 40 /**
Chris@5 41 * The allowed condition operators.
Chris@5 42 *
Chris@5 43 * @var string[]
Chris@5 44 */
Chris@5 45 public static $allowedOperators = [
Chris@5 46 '=', '<>',
Chris@5 47 '>', '>=', '<', '<=',
Chris@5 48 'STARTS_WITH', 'CONTAINS', 'ENDS_WITH',
Chris@5 49 'IN', 'NOT IN',
Chris@5 50 'BETWEEN', 'NOT BETWEEN',
Chris@5 51 'IS NULL', 'IS NOT NULL',
Chris@5 52 ];
Chris@5 53
Chris@5 54 /**
Chris@5 55 * The field to be evaluated.
Chris@5 56 *
Chris@5 57 * @var string
Chris@5 58 */
Chris@5 59 protected $field;
Chris@5 60
Chris@5 61 /**
Chris@5 62 * The condition operator.
Chris@5 63 *
Chris@5 64 * @var string
Chris@5 65 */
Chris@5 66 protected $operator;
Chris@5 67
Chris@5 68 /**
Chris@5 69 * The value against which the field should be evaluated.
Chris@5 70 *
Chris@5 71 * @var mixed
Chris@5 72 */
Chris@5 73 protected $value;
Chris@5 74
Chris@5 75 /**
Chris@5 76 * Constructs a new EntityCondition object.
Chris@5 77 */
Chris@5 78 public function __construct($field, $value, $operator = NULL) {
Chris@5 79 $this->field = $field;
Chris@5 80 $this->value = $value;
Chris@5 81 $this->operator = ($operator) ? $operator : '=';
Chris@5 82 }
Chris@5 83
Chris@5 84 /**
Chris@5 85 * The field to be evaluated.
Chris@5 86 *
Chris@5 87 * @return string
Chris@5 88 * The field upon which to evaluate the condition.
Chris@5 89 */
Chris@5 90 public function field() {
Chris@5 91 return $this->field;
Chris@5 92 }
Chris@5 93
Chris@5 94 /**
Chris@5 95 * The comparison operator to use for the evaluation.
Chris@5 96 *
Chris@5 97 * For a list of allowed operators:
Chris@5 98 *
Chris@5 99 * @see \Drupal\jsonapi\Query\EntityCondition::allowedOperators
Chris@5 100 *
Chris@5 101 * @return string
Chris@5 102 * The condition operator.
Chris@5 103 */
Chris@5 104 public function operator() {
Chris@5 105 return $this->operator;
Chris@5 106 }
Chris@5 107
Chris@5 108 /**
Chris@5 109 * The value against which the condition should be evaluated.
Chris@5 110 *
Chris@5 111 * @return mixed
Chris@5 112 * The condition comparison value.
Chris@5 113 */
Chris@5 114 public function value() {
Chris@5 115 return $this->value;
Chris@5 116 }
Chris@5 117
Chris@5 118 /**
Chris@5 119 * Creates an EntityCondition object from a query parameter.
Chris@5 120 *
Chris@5 121 * @param mixed $parameter
Chris@5 122 * The `filter[condition]` query parameter from the request.
Chris@5 123 *
Chris@5 124 * @return self
Chris@5 125 * An EntityCondition object with defaults.
Chris@5 126 */
Chris@5 127 public static function createFromQueryParameter($parameter) {
Chris@5 128 static::validate($parameter);
Chris@5 129 $field = $parameter[static::PATH_KEY];
Chris@5 130 $value = (isset($parameter[static::VALUE_KEY])) ? $parameter[static::VALUE_KEY] : NULL;
Chris@5 131 $operator = (isset($parameter[static::OPERATOR_KEY])) ? $parameter[static::OPERATOR_KEY] : NULL;
Chris@5 132 return new static($field, $value, $operator);
Chris@5 133 }
Chris@5 134
Chris@5 135 /**
Chris@5 136 * Validates the filter has the required fields.
Chris@5 137 */
Chris@5 138 protected static function validate($parameter) {
Chris@5 139 $valid_key_combinations = [
Chris@5 140 [static::PATH_KEY, static::VALUE_KEY],
Chris@5 141 [static::PATH_KEY, static::OPERATOR_KEY],
Chris@5 142 [static::PATH_KEY, static::VALUE_KEY, static::OPERATOR_KEY],
Chris@5 143 ];
Chris@5 144
Chris@5 145 $given_keys = array_keys($parameter);
Chris@5 146 $valid_key_set = array_reduce($valid_key_combinations, function ($valid, $set) use ($given_keys) {
Chris@5 147 return ($valid) ? $valid : count(array_diff($set, $given_keys)) === 0;
Chris@5 148 }, FALSE);
Chris@5 149
Chris@5 150 $has_operator_key = isset($parameter[static::OPERATOR_KEY]);
Chris@5 151 $has_path_key = isset($parameter[static::PATH_KEY]);
Chris@5 152 $has_value_key = isset($parameter[static::VALUE_KEY]);
Chris@5 153
Chris@5 154 $cacheability = (new CacheableMetadata())->addCacheContexts(['url.query_args:filter']);
Chris@5 155 if (!$valid_key_set) {
Chris@5 156 // Try to provide a more specific exception is a key is missing.
Chris@5 157 if (!$has_operator_key) {
Chris@5 158 if (!$has_path_key) {
Chris@5 159 throw new CacheableBadRequestHttpException($cacheability, "Filter parameter is missing a '" . static::PATH_KEY . "' key.");
Chris@5 160 }
Chris@5 161 if (!$has_value_key) {
Chris@5 162 throw new CacheableBadRequestHttpException($cacheability, "Filter parameter is missing a '" . static::VALUE_KEY . "' key.");
Chris@5 163 }
Chris@5 164 }
Chris@5 165
Chris@5 166 // Catchall exception.
Chris@5 167 $reason = "You must provide a valid filter condition. Check that you have set the required keys for your filter.";
Chris@5 168 throw new CacheableBadRequestHttpException($cacheability, $reason);
Chris@5 169 }
Chris@5 170
Chris@5 171 if ($has_operator_key) {
Chris@5 172 $operator = $parameter[static::OPERATOR_KEY];
Chris@5 173 if (!in_array($operator, static::$allowedOperators)) {
Chris@5 174 $reason = "The '" . $operator . "' operator is not allowed in a filter parameter.";
Chris@5 175 throw new CacheableBadRequestHttpException($cacheability, $reason);
Chris@5 176 }
Chris@5 177
Chris@5 178 if (in_array($operator, ['IS NULL', 'IS NOT NULL']) && $has_value_key) {
Chris@5 179 $reason = "Filters using the '" . $operator . "' operator should not provide a value.";
Chris@5 180 throw new CacheableBadRequestHttpException($cacheability, $reason);
Chris@5 181 }
Chris@5 182 }
Chris@5 183 }
Chris@5 184
Chris@5 185 }