- Heavy rework on search request handling of entities

This commit is contained in:
Dev committed 2025-12-02 00:15:54 +00:00
1 parent a9b9c2cce9
commit 11e436236c
8 files changed
+290 -167

No files matched your search

@@ -2,6 +2,7 @@
namespace Ulmus\SearchRequest;
use Notes\Common\ReflectedClass;
use Notes\ObjectReflection;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
@@ -14,103 +15,65 @@ use Ulmus\SearchRequest\Attribute\{PropertyValueSource,
SearchOrderBy,
SearchGroupBy,
SearchRequestParameter};
use Ulmus\Repository;
trait SearchRequestFromRequestTrait
{
protected array $wheres = [];
protected ReflectedClass $objectReflection;
protected array $likes = [];
protected array $parsedParameters = [];
protected array $groups = [];
protected array $orders = [];
public function fromArray(array|\ArrayAccess $data) : static
public function fromParsedParameters(Repository $repository) : SearchRequestInterface
{
$this->page = $data['page'] ?? 1;
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;
$classReflection = ObjectReflection::fromClass(static::class)->reflectClass();
case SearchMethodEnum::Where:
$repository->where($param->field, $param->value(), $param->operator(), $param->condition());
break;
foreach($classReflection->getProperties() as $propertyName => $property) {
$attributeList = $property->getAttributes(Attribute\SearchParameter::class);
case SearchMethodEnum::OrderBy:
$repository->orderBy($param->field, $param->value());
break;
if ($attributeList) {
$attribute = $attributeList[0]->object;
$fieldName = $attribute->field ?? $propertyName;
# 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 = $classReflection->getAttributes(SearchRequestParameter::class)) {
$searchRequestAttribute = $classAttributes[0]->object;
$className = $searchRequestAttribute->class;
$field = $className::field($fieldName, $searchRequestAttribute->alias);
}
# Untouched string from the attribute
else {
$field = $fieldName;
}
$value = $data[$propertyName] ?? null;
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 SearchWhere:
case $attribute instanceof SearchLike:
case $attribute instanceof SearchManual:
if ($attribute->toggle) {
$this->$propertyName = ! empty($value);
}
elseif ($value !== null) {
foreach ($property->getTypes() as $type) {
$enum = $type->type;
if (enum_exists($enum)) {
try {
$this->$propertyName = $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 ) {
$this->$propertyName = $value;
}
}
}
$this->parseAttributeMethod($attribute, $field, $propertyName);
break;
case $attribute instanceof SearchOrderBy:
if ($value !== null) {
$this->$propertyName = $value;
}
$this->parseAttributeOrderBy($attribute, $field, $propertyName);
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);
}
@@ -119,32 +82,81 @@ trait SearchRequestFromRequestTrait
$this->page = $queryParams->offsetExists('page') ? $queryParams['page'] : 1;
$classReflection = ObjectReflection::fromClass(static::class)->reflectClass();
$this->applyValues(
fn($propertyName, $attribute) => $this->getValueFromSource($request, $propertyName, $attribute)
);
foreach($classReflection->getProperties() as $propertyName => $property) {
$attributeList = $property->getAttributes(Attribute\SearchParameter::class);
$operators = $request->getAttribute(SearchRequestInterface::SEARCH_REQUEST_OPERATORS);
if ($attributeList) {
$attribute = $attributeList[0]->object;
if ($operators) {
$this->applyOperators(
fn($propertyName) => $operators[$propertyName] ?? null
);
}
$fieldName = $attribute->field ?? $propertyName;
$conditions = $request->getAttribute(SearchRequestInterface::SEARCH_REQUEST_CONDITIONS);
# 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 = $classReflection->getAttributes(SearchRequestParameter::class)) {
$searchRequestAttribute = $classAttributes[0]->object;
$className = $searchRequestAttribute->class;
$field = $className::field($fieldName, $searchRequestAttribute->alias);
}
# Untouched string from the attribute
else {
$field = $fieldName;
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,
};
}
}
}
}
$value = $this->getValueFromSource($request, $propertyName, $attribute);
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);
@@ -195,40 +207,24 @@ trait SearchRequestFromRequestTrait
}
}
}
return $this;
}
/* [[ Boilerplates which are ready to be copied */
public function wheres() : iterable
protected function getEntityField(array|string $fieldName) : string|\Stringable
{
return array_filter($this->wheres + [
# 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);
}
], fn($i) => ! is_null($i) ) + [ ];
return $field ?? $fieldName;
}
public function likes(): iterable
{
return array_filter($this->likes + [
], fn($i) => ! is_null($i) ) + [];
}
public function groups(): iterable
{
return array_filter($this->groups + [
], fn($e) => ! is_null($e) && $e !== "" );
}
public function orders(): iterable
{
return array_filter($this->orders + [
], fn($e) => ! is_null($e) && $e !== "" );
}
/* ]] */
protected function getValueFromSource(RequestInterface $request, string $propertyName, SearchParameter $attribute) : mixed
{
$queryParamName = $attribute->getParameters() ?: [ $propertyName ];
@@ -262,7 +258,16 @@ trait SearchRequestFromRequestTrait
return null;
}
protected function transformValue(array $valueModifierAttributes, mixed $value, ) : mixed
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);
@@ -271,42 +276,44 @@ trait SearchRequestFromRequestTrait
return $value;
}
protected function parseAttributeMethod(object $attribute, string $field, string $propertyName,) : void
protected function parseAttributeMethod(object $attribute, string|\Stringable $field, string $propertyName) : void
{
if (! isset($this->$propertyName)) {
return;
}
switch ($attribute->method) {
case SearchMethodEnum::Where:
$this->wheres[$field] = $this->$propertyName;
break;
case SearchMethodEnum::Like:
$this->likes[$field] = "%{$this->$propertyName}%";
break;
case SearchMethodEnum::LikeLeft:
$this->likes[$field] = "%{$this->$propertyName}";
break;
case SearchMethodEnum::LikeRight:
$this->likes[$field] = "{$this->$propertyName}%";
break;
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
protected function parseAttributeOrderBy(object $attribute, string $field, mixed $propertyName) : void
{
if ( ! empty($this->$propertyName) ) {
$this->orders[$field] = $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
protected function parseAttributeGroupBy(object $attribute, string $field, mixed $propertyName) : void
{
if (! empty($this->$propertyName)) {
$this->groups[] = $field;
$this->parsedParameters[$propertyName] = new ParsedSearchParameter(
searchMethod: SearchMethodEnum::GroupBy,
field: $field,
value: null,
);
}
}
}