/**
* Adjusts search criteria according to RULE_TYPE_FILTERABLE rules and Request
*
* @param array $criteria optional Default search criteria
* @return array|null Returns adjusted criteria
* @throws ApiErrorException
*/
public function getCriteria($criteria = null)
{
$rules = $this->getRules();
if (!empty($rules[static::RULE_TYPE_FILTERABLE])) {
if (!is_array($rules[static::RULE_TYPE_FILTERABLE]) && !$rules[static::RULE_TYPE_FILTERABLE] instanceof \ArrayAccess) {
throw new \InvalidArgumentException(sprintf("[%s::RULE_TYPE_FILTERABLE] offset of the rules is expected to be an Array", get_class($this)));
}
/* @var $entity AbstractEntity */
$entityClass = $this->getEntityClass();
$entity = new $entityClass();
$it = $entity->getIterator();
//Search criteria
$criteria = $criteria ?: [];
foreach ($rules[static::RULE_TYPE_FILTERABLE] as $property) {
//Gets value from the request
$filterValue = $this->controller->params($property);
if ($filterValue === null) {
continue;
}
//As the name of the property that goes into response may be different from the
//real property name in the Entity object it should be mapped at first
if (!empty($rules[static::RULE_TYPE_TO_DATA])) {
//if toData rule is null it means all properties are allowed
if (($key = array_search($property, $rules[static::RULE_TYPE_TO_DATA])) !== false) {
if (is_string($key)) {
//In this case the real name of the property is the key of the array
if ($key[0] === '_' && method_exists($this, $key)) {
//It is callable
$from = (object) [$property => $filterValue];
$addCriteria = $this->{$key}($from, null, self::ACT_GET_FILTER_CRITERIA);
if (!empty($addCriteria)) {
//Latter value should not overwrite the previous
$criteria = array_merge($criteria, $addCriteria);
}
continue;
}
$property = $key;
}
}
}
//Fetches the definition of the field from the Entity model
$field = $it->getField($property);
if (!$field instanceof Field) {
throw new \InvalidArgumentException(sprintf("Invalid value is in the [%s::RULE_TYPE_FILTERABLE] offset of the rules. " . "Property '%s' is not defined in the %s entity.", get_class($this), $property, get_class($entity)));
}
//Different column type values should be converted
$criteria[] = [$field->name => self::convertInputValue($field->column->type, $filterValue)];
}
}
//We should make sure users do not send requests with unavailable filters.
$notProcessed = array_diff(array_keys($this->controller->request->get()), array_keys($this->controller->getCommonQueryParams()), !empty($rules[static::RULE_TYPE_FILTERABLE]) ? $rules[static::RULE_TYPE_FILTERABLE] : array_values($rules[static::RULE_TYPE_TO_DATA]));
if (!empty($notProcessed)) {
//It means user sent request to filter on not filterable property.
throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_STRUCTURE, sprintf("Unsupported filter. Fields which are available for filtering: [%s]", join(', ', $rules[static::RULE_TYPE_FILTERABLE])));
}
return $criteria;
}