diff --git a/src/EntityCollection.php b/src/EntityCollection.php index 6ebc2fc..6ed54ed 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -100,7 +100,7 @@ class EntityCollection extends \ArrayObject implements \JsonSerializable { return $this; } - public function removeOne($value, string $field, bool $strict = true) : ? object + public function removeOne($value, callable|string $field, bool $strict = true) : ? object { foreach($this->search($value, $field, $strict) as $key => $item) { $this->offsetUnset($key); @@ -111,7 +111,7 @@ class EntityCollection extends \ArrayObject implements \JsonSerializable { return null; } - public function remove(/* mixed */ $value, string $field, bool $strict = true) : array + public function remove(/* mixed */ $value, callable|string $field, bool $strict = true) : array { $removed = []; @@ -131,14 +131,21 @@ class EntityCollection extends \ArrayObject implements \JsonSerializable { return $this; } - public function search(mixed $value, string $field, bool $strict = true) : Generator + public function search(mixed $value, callable|string $field, bool $strict = true) : Generator { - foreach($this->filters(fn($v) => isset($v->$field) ? ( $strict ? $v->$field === $value : $v->$field == $value ) : false) as $key => $item) { + if (is_string($field)) { + $filtering = fn($v) => isset($v->$field) ? ($strict ? $v->$field === $value : $v->$field == $value) : false; + } + elseif (is_callable($field)) { + $filtering = fn($v) => ( $fieldValue = call_user_func($field, $v) ) !== null ? ($strict ? $fieldValue === $value : $fieldValue == $value) : false; + } + + foreach($this->filters($filtering) as $key => $item) { yield $key => $item; } } - public function searchOne(mixed $value, string $field, bool $strict = true) : ? object + public function searchOne(mixed $value, callable|string $field, bool $strict = true) : ? object { # Returning first value only foreach($this->search($value, $field, $strict) as $item) { @@ -148,7 +155,7 @@ class EntityCollection extends \ArrayObject implements \JsonSerializable { return null; } - public function searchAll(mixed $values, string $field, bool $strict = true, bool $compareArray = false) : self + public function searchAll(mixed $values, callable|string $field, bool $strict = true, bool $compareArray = false) : self { $collection = new static(); @@ -163,7 +170,7 @@ class EntityCollection extends \ArrayObject implements \JsonSerializable { return $collection; } - public function diffAll(mixed $values, string $field, bool $strict = true, bool $compareArray = false) : self + public function diffAll(mixed $values, callable|string $field, bool $strict = true, bool $compareArray = false) : self { $obj = new static($this->getArrayCopy()); diff --git a/src/SearchRequest/SearchRequestFromRequestTrait.php b/src/SearchRequest/SearchRequestFromRequestTrait.php index 35f0f19..60fb46b 100644 --- a/src/SearchRequest/SearchRequestFromRequestTrait.php +++ b/src/SearchRequest/SearchRequestFromRequestTrait.php @@ -25,7 +25,91 @@ trait SearchRequestFromRequestTrait protected array $orders = []; - public function fromRequest(ServerRequestInterface $request) + public function fromArray(array|\ArrayAccess $data) : static + { + $this->page = $data['page'] ?? 1; + + $classReflection = ObjectReflection::fromClass(static::class)->reflectClass(); + + foreach($classReflection->getProperties() as $propertyName => $property) { + $attributeList = $property->getAttributes(Attribute\SearchParameter::class); + + 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; + } + } + } + + return $this; + } + + public function fromRequest(ServerRequestInterface $request) : static { if (method_exists($this, 'prepare')) { $this->prepare($request);