339 lines
12 KiB
PHP
339 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Ulmus\SearchRequest;
|
|
|
|
use Notes\Common\ReflectedClass;
|
|
use Notes\ObjectReflection;
|
|
use Psr\Http\Message\RequestInterface;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
|
|
use Ulmus\SearchRequest\Attribute\{PropertyValueSource,
|
|
SearchManual,
|
|
SearchParameter,
|
|
SearchWhere,
|
|
SearchLike,
|
|
SearchOrderBy,
|
|
SearchGroupBy,
|
|
SearchRequestParameter};
|
|
use Ulmus\Repository;
|
|
|
|
trait SearchRequestFromRequestTrait
|
|
{
|
|
protected ReflectedClass $objectReflection;
|
|
|
|
protected array $parsedParameters = [];
|
|
|
|
public function toArray() : array
|
|
{
|
|
return json_decode(json_encode($this), true);
|
|
}
|
|
|
|
public function fromParsedParameters(Repository $repository) : SearchRequestInterface
|
|
{
|
|
foreach($this->parsedParameters as $property => $param) {
|
|
switch($param->searchMethod) {
|
|
case SearchMethodEnum::Like:
|
|
case SearchMethodEnum::LikeLeft:
|
|
case SearchMethodEnum::LikeRight:
|
|
$repository->like($param->field, $param->value());
|
|
break;
|
|
|
|
case SearchMethodEnum::Where:
|
|
$repository->where($param->field, $param->value(), $param->operator(), $param->condition());
|
|
break;
|
|
|
|
case SearchMethodEnum::OrderBy:
|
|
$value = $param->value();
|
|
|
|
$repository->orderBy($param->field, $value instanceof \BackedEnum ? $value->value : $value);
|
|
break;
|
|
|
|
case SearchMethodEnum::GroupBy:
|
|
$repository->groupBy($param->field);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function fromArray(array|\ArrayAccess $data) : static
|
|
{
|
|
$this->objectReflection = ObjectReflection::fromClass(static::class)->reflectClass();
|
|
|
|
$this->page = $data['page'] ?? 1;
|
|
|
|
$this->applyValues(
|
|
fn($propertyName, $attribute) => $data[$propertyName] ?? null
|
|
);
|
|
|
|
$this->applyOperators(
|
|
fn($propertyName) => $data["$propertyName:operator"] ?? null
|
|
);
|
|
|
|
$this->applyConditions(
|
|
fn($propertyName) => $data["$propertyName:condition"] ?? null
|
|
);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function fromRequest(ServerRequestInterface $request) : static
|
|
{
|
|
$this->objectReflection = ObjectReflection::fromClass(static::class)->reflectClass();
|
|
|
|
if (method_exists($this, 'prepare')) {
|
|
$this->prepare($request);
|
|
}
|
|
|
|
$queryParams = new \ArrayObject(array_filter($request->getQueryParams(), function($i) { return ! is_null($i) && $i !== ""; }));
|
|
|
|
$this->page = $queryParams->offsetExists('page') ? $queryParams['page'] : 1;
|
|
|
|
$this->applyValues(
|
|
fn($propertyName, $attribute) =>$this->getValueFromSource($request, $propertyName, $attribute)
|
|
);
|
|
|
|
$operators = $request->getAttribute(SearchRequestInterface::SEARCH_REQUEST_OPERATORS);
|
|
|
|
if ($operators) {
|
|
$this->applyOperators(
|
|
fn($propertyName) => $operators[$propertyName] ?? null
|
|
);
|
|
}
|
|
|
|
$conditions = $request->getAttribute(SearchRequestInterface::SEARCH_REQUEST_CONDITIONS);
|
|
|
|
if ($conditions) {
|
|
$this->applyConditions(
|
|
fn($propertyName) => $conditions[$propertyName] ?? null
|
|
);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
protected function applyConditions(\Closure $getCondition) : void
|
|
{
|
|
foreach($this->objectReflection->getProperties() as $propertyName => $property) {
|
|
if (! isset($this->parsedParameters[$propertyName])) {
|
|
continue;
|
|
}
|
|
|
|
$attribute = $property->getAttribute(Attribute\SearchParameter::class)->object ?? false;
|
|
|
|
if ( $condition = $getCondition($propertyName) ) {
|
|
match(true) {
|
|
$attribute instanceof SearchManual => throw new \LogicException("Impossible to add a condition to a manually filtered elements."),
|
|
$attribute instanceof SearchGroupBy => throw new \LogicException("Impossible to add a condition to a group by field."),
|
|
$attribute instanceof SearchOrderBy => throw new \LogicException("Impossible to add a condition to a order by field."),
|
|
default => $this->parsedParameters[$propertyName]->condition = $condition,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function applyOperators(\Closure $getOperator) : void
|
|
{
|
|
foreach($this->objectReflection->getProperties() as $propertyName => $property) {
|
|
if (! isset($this->parsedParameters[$propertyName])) {
|
|
continue;
|
|
}
|
|
|
|
$attribute = $property->getAttribute(Attribute\SearchParameter::class)->object ?? false;
|
|
|
|
if ($attribute) {
|
|
if ($operator = $getOperator($propertyName)) {
|
|
match (true) {
|
|
$attribute instanceof SearchManual => throw new \LogicException("Impossible to add an operator to a manually filtered elements."),
|
|
$attribute instanceof SearchGroupBy => throw new \LogicException("Impossible to add an operator to a group by field."),
|
|
$attribute instanceof SearchOrderBy => throw new \LogicException("Impossible to add an operator to a order by field."),
|
|
default => $this->parsedParameters[$propertyName]->operator = $operator,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function applyValues(\Closure $getValue) : void
|
|
{
|
|
foreach($this->objectReflection->getProperties() as $propertyName => $property) {
|
|
$attribute = $property->getAttribute(Attribute\SearchParameter::class)->object ?? false;
|
|
|
|
if ($attribute) {
|
|
$field = $this->getEntityField($attribute->field ?? $propertyName);
|
|
$field = $this->transformField($property->getAttributes(Attribute\FieldValueModifier::class), $field);
|
|
|
|
$value = $getValue($propertyName, $attribute);
|
|
|
|
if ($value !== null) {
|
|
$value = $this->transformValue($property->getAttributes(Attribute\PropertyValueModifier::class), $value);
|
|
}
|
|
|
|
switch(true) {
|
|
case $attribute instanceof SearchGroupBy:
|
|
$this->parseAttributeGroupBy($attribute, $field, $propertyName);
|
|
break;
|
|
|
|
case $attribute instanceof SearchOrderBy:
|
|
if ($value !== null) {
|
|
$this->$propertyName = $this->parseValue($property, $value);
|
|
}
|
|
|
|
$this->parseAttributeOrderBy($attribute, $field, $propertyName);
|
|
break;
|
|
|
|
case $attribute instanceof SearchWhere:
|
|
case $attribute instanceof SearchLike:
|
|
case $attribute instanceof SearchManual:
|
|
if ($attribute->toggle) {
|
|
$this->$propertyName = !empty($value);
|
|
}
|
|
elseif ($value !== null) {
|
|
$this->$propertyName = $this->parseValue($property, $value);
|
|
}
|
|
|
|
$this->parseAttributeMethod($attribute, $field, $propertyName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function parseValue(\Notes\Common\ReflectedProperty $property, mixed $value) : mixed
|
|
{
|
|
foreach ($property->getTypes() as $type) {
|
|
$enum = $type->type;
|
|
|
|
if (enum_exists($enum)) {
|
|
try {
|
|
return $value instanceof \UnitEnum ? $value : $type->type::from($value);
|
|
} catch (\ValueError $ex) {
|
|
$cases = implode(', ', array_map(fn($e) => $e->name, $enum::cases()));
|
|
|
|
throw new \ValueError(
|
|
sprintf("Given value '%s' do not exists within enum '%s'. Try one of those values instead : %s", $value, $enum, $cases)
|
|
);
|
|
}
|
|
}
|
|
elseif ($type->builtIn || $value instanceof \Stringable ) {
|
|
return $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function getEntityField(array|string $fieldName) : string|\Stringable
|
|
{
|
|
# Field could be defined for another entity class
|
|
if (is_array($fieldName)) {
|
|
$field = \Ulmus\Attribute\Attribute::handleArrayField($fieldName, false);
|
|
}
|
|
# Default class using it, if SearchRequestParameter is defined
|
|
elseif ($classAttributes = $this->objectReflection->getAttributes(SearchRequestParameter::class)) {
|
|
$searchRequestAttribute = $classAttributes[0]->object;
|
|
$className = $searchRequestAttribute->class;
|
|
$field = $className::field($fieldName, $searchRequestAttribute->alias);
|
|
}
|
|
|
|
return $field ?? $fieldName;
|
|
}
|
|
|
|
protected function getValueFromSource(RequestInterface $request, string $propertyName, SearchParameter $attribute) : mixed
|
|
{
|
|
$classAttributes = $this->objectReflection->getAttributes(SearchRequestParameter::class);
|
|
$searchRequestAttribute = $classAttributes[0]->object;
|
|
$className = $searchRequestAttribute->class;
|
|
|
|
if ( $override = $request->getAttribute(sprintf("searchRequest.%s:%s", $className, $propertyName)) ) {
|
|
return $override;
|
|
}
|
|
|
|
$queryParamName = $attribute->getParameters() ?: [ $propertyName ];
|
|
|
|
foreach($attribute->getSources() as $source) {
|
|
switch($source) {
|
|
case PropertyValueSource::QueryParams:
|
|
$queryParams = new \ArrayObject(array_filter($request->getQueryParams(), function($i) { return ! is_null($i) && $i !== ""; }));
|
|
|
|
foreach($queryParamName as $param) {
|
|
if ($queryParams->offsetExists($param)) {
|
|
return $queryParams[$param];
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case PropertyValueSource::RequestAttribute:
|
|
foreach($queryParamName as $param) {
|
|
$fromAttribute = $request->getAttribute($param, null);
|
|
|
|
if ($fromAttribute !== null) {
|
|
return $fromAttribute;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function transformField(array $fieldModifierAttributes, mixed $field) : mixed
|
|
{
|
|
foreach($fieldModifierAttributes as $transform) {
|
|
$field = $transform->object->run($field);
|
|
}
|
|
|
|
return $field;
|
|
}
|
|
|
|
protected function transformValue(array $valueModifierAttributes, mixed $value) : mixed
|
|
{
|
|
foreach($valueModifierAttributes as $transform) {
|
|
$value = $transform->object->run($value);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
protected function parseAttributeMethod(object $attribute, string|\Stringable $field, string $propertyName) : void
|
|
{
|
|
if (! isset($this->$propertyName)) {
|
|
return;
|
|
}
|
|
switch ($attribute->method) {
|
|
case SearchMethodEnum::Like:
|
|
case SearchMethodEnum::LikeLeft:
|
|
case SearchMethodEnum::LikeRight:
|
|
case SearchMethodEnum::Where:
|
|
$this->parsedParameters[$propertyName] = new ParsedSearchParameter(
|
|
searchMethod: $attribute->method,
|
|
field: $field,
|
|
value: $this->$propertyName,
|
|
);
|
|
}
|
|
}
|
|
|
|
protected function parseAttributeOrderBy(object $attribute, string $field, mixed $propertyName) : void
|
|
{
|
|
if ( ! empty($this->$propertyName) ) {
|
|
$this->parsedParameters[$propertyName] = new ParsedSearchParameter(
|
|
searchMethod: SearchMethodEnum::OrderBy,
|
|
field: $field,
|
|
value: $this->$propertyName,
|
|
);
|
|
}
|
|
}
|
|
|
|
protected function parseAttributeGroupBy(object $attribute, string $field, mixed $propertyName) : void
|
|
{
|
|
if (! empty($this->$propertyName)) {
|
|
$this->parsedParameters[$propertyName] = new ParsedSearchParameter(
|
|
searchMethod: SearchMethodEnum::GroupBy,
|
|
field: $field,
|
|
value: null,
|
|
);
|
|
}
|
|
}
|
|
} |