Chris@18: ]. Chris@18: * Chris@18: * @var string Chris@18: */ Chris@18: const PATH_KEY = 'path'; Chris@18: Chris@18: /** Chris@18: * The value key in the filter condition: filter[lorem][condition][]. Chris@18: * Chris@18: * @var string Chris@18: */ Chris@18: const VALUE_KEY = 'value'; Chris@18: Chris@18: /** Chris@18: * The operator key in the condition: filter[lorem][condition][]. Chris@18: * Chris@18: * @var string Chris@18: */ Chris@18: const OPERATOR_KEY = 'operator'; Chris@18: Chris@18: /** Chris@18: * The allowed condition operators. Chris@18: * Chris@18: * @var string[] Chris@18: */ Chris@18: public static $allowedOperators = [ Chris@18: '=', '<>', Chris@18: '>', '>=', '<', '<=', Chris@18: 'STARTS_WITH', 'CONTAINS', 'ENDS_WITH', Chris@18: 'IN', 'NOT IN', Chris@18: 'BETWEEN', 'NOT BETWEEN', Chris@18: 'IS NULL', 'IS NOT NULL', Chris@18: ]; Chris@18: Chris@18: /** Chris@18: * The field to be evaluated. Chris@18: * Chris@18: * @var string Chris@18: */ Chris@18: protected $field; Chris@18: Chris@18: /** Chris@18: * The condition operator. Chris@18: * Chris@18: * @var string Chris@18: */ Chris@18: protected $operator; Chris@18: Chris@18: /** Chris@18: * The value against which the field should be evaluated. Chris@18: * Chris@18: * @var mixed Chris@18: */ Chris@18: protected $value; Chris@18: Chris@18: /** Chris@18: * Constructs a new EntityCondition object. Chris@18: */ Chris@18: public function __construct($field, $value, $operator = NULL) { Chris@18: $this->field = $field; Chris@18: $this->value = $value; Chris@18: $this->operator = ($operator) ? $operator : '='; Chris@18: } Chris@18: Chris@18: /** Chris@18: * The field to be evaluated. Chris@18: * Chris@18: * @return string Chris@18: * The field upon which to evaluate the condition. Chris@18: */ Chris@18: public function field() { Chris@18: return $this->field; Chris@18: } Chris@18: Chris@18: /** Chris@18: * The comparison operator to use for the evaluation. Chris@18: * Chris@18: * For a list of allowed operators: Chris@18: * Chris@18: * @see \Drupal\jsonapi\Query\EntityCondition::allowedOperators Chris@18: * Chris@18: * @return string Chris@18: * The condition operator. Chris@18: */ Chris@18: public function operator() { Chris@18: return $this->operator; Chris@18: } Chris@18: Chris@18: /** Chris@18: * The value against which the condition should be evaluated. Chris@18: * Chris@18: * @return mixed Chris@18: * The condition comparison value. Chris@18: */ Chris@18: public function value() { Chris@18: return $this->value; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Creates an EntityCondition object from a query parameter. Chris@18: * Chris@18: * @param mixed $parameter Chris@18: * The `filter[condition]` query parameter from the request. Chris@18: * Chris@18: * @return self Chris@18: * An EntityCondition object with defaults. Chris@18: */ Chris@18: public static function createFromQueryParameter($parameter) { Chris@18: static::validate($parameter); Chris@18: $field = $parameter[static::PATH_KEY]; Chris@18: $value = (isset($parameter[static::VALUE_KEY])) ? $parameter[static::VALUE_KEY] : NULL; Chris@18: $operator = (isset($parameter[static::OPERATOR_KEY])) ? $parameter[static::OPERATOR_KEY] : NULL; Chris@18: return new static($field, $value, $operator); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Validates the filter has the required fields. Chris@18: */ Chris@18: protected static function validate($parameter) { Chris@18: $valid_key_combinations = [ Chris@18: [static::PATH_KEY, static::VALUE_KEY], Chris@18: [static::PATH_KEY, static::OPERATOR_KEY], Chris@18: [static::PATH_KEY, static::VALUE_KEY, static::OPERATOR_KEY], Chris@18: ]; Chris@18: Chris@18: $given_keys = array_keys($parameter); Chris@18: $valid_key_set = array_reduce($valid_key_combinations, function ($valid, $set) use ($given_keys) { Chris@18: return ($valid) ? $valid : count(array_diff($set, $given_keys)) === 0; Chris@18: }, FALSE); Chris@18: Chris@18: $has_operator_key = isset($parameter[static::OPERATOR_KEY]); Chris@18: $has_path_key = isset($parameter[static::PATH_KEY]); Chris@18: $has_value_key = isset($parameter[static::VALUE_KEY]); Chris@18: Chris@18: $cacheability = (new CacheableMetadata())->addCacheContexts(['url.query_args:filter']); Chris@18: if (!$valid_key_set) { Chris@18: // Try to provide a more specific exception is a key is missing. Chris@18: if (!$has_operator_key) { Chris@18: if (!$has_path_key) { Chris@18: throw new CacheableBadRequestHttpException($cacheability, "Filter parameter is missing a '" . static::PATH_KEY . "' key."); Chris@18: } Chris@18: if (!$has_value_key) { Chris@18: throw new CacheableBadRequestHttpException($cacheability, "Filter parameter is missing a '" . static::VALUE_KEY . "' key."); Chris@18: } Chris@18: } Chris@18: Chris@18: // Catchall exception. Chris@18: $reason = "You must provide a valid filter condition. Check that you have set the required keys for your filter."; Chris@18: throw new CacheableBadRequestHttpException($cacheability, $reason); Chris@18: } Chris@18: Chris@18: if ($has_operator_key) { Chris@18: $operator = $parameter[static::OPERATOR_KEY]; Chris@18: if (!in_array($operator, static::$allowedOperators)) { Chris@18: $reason = "The '" . $operator . "' operator is not allowed in a filter parameter."; Chris@18: throw new CacheableBadRequestHttpException($cacheability, $reason); Chris@18: } Chris@18: Chris@18: if (in_array($operator, ['IS NULL', 'IS NOT NULL']) && $has_value_key) { Chris@18: $reason = "Filters using the '" . $operator . "' operator should not provide a value."; Chris@18: throw new CacheableBadRequestHttpException($cacheability, $reason); Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: }