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 ?: [];
$settingsRules = null;
foreach ($rules[static::RULE_TYPE_FILTERABLE] as $property) {
$key = null;
//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)) {
if (isset($addCriteria[AbstractEntity::STMT_FROM])) {
if (!isset($criteria[AbstractEntity::STMT_FROM])) {
$criteria[AbstractEntity::STMT_FROM] = $entity->table();
}
$criteria[AbstractEntity::STMT_FROM] .= " " . $addCriteria[AbstractEntity::STMT_FROM];
}
if (isset($addCriteria[AbstractEntity::STMT_WHERE])) {
if (!empty($criteria[AbstractEntity::STMT_WHERE])) {
$criteria[AbstractEntity::STMT_WHERE] .= " AND (" . $addCriteria[AbstractEntity::STMT_WHERE] . ")";
} else {
$criteria[AbstractEntity::STMT_WHERE] = $addCriteria[AbstractEntity::STMT_WHERE];
}
}
//Latter value should not overwrite the previous
$criteria = array_merge($addCriteria, $criteria);
}
continue;
}
$property = $key;
}
}
}
if (empty($key) && !empty($rules[static::RULE_TYPE_SETTINGS]) && method_exists($entity, 'getSettingCriteria')) {
if (!isset($settingsRules)) {
$settingsRules = $this->getSettingsRules();
}
if (($key = array_search($property, $settingsRules)) !== false) {
if (is_object($filterValue) || is_array($filterValue)) {
throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Filter value must be a string");
}
if (empty($criteria[AbstractEntity::STMT_FROM])) {
$criteria[AbstractEntity::STMT_FROM] = " {$entity->table()} ";
}
$criteria = $entity->getSettingCriteria($key, $filterValue, $criteria);
continue;
}
}
//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 => static::convertInputValue($field->column->type, $filterValue, $property)];
}
}
//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;
}