Giter Club home page Giter Club logo

Comments (7)

Ocramius avatar Ocramius commented on June 3, 2024

Sounds like an OK idea, TBH (same for validators).

Note that some of these require external dependencies, and cannot really be safely used as attributes.

from laminas-filter.

delolmo avatar delolmo commented on June 3, 2024

Just as a proof of concept, perhaps this could be an interesting implementation:

namespace Laminas\Filter;

use ReflectionAttribute;
use ReflectionClass;

use function class_exists;
use function is_object;

final class AnnotatedObjectFilter implements FilterInterface
{
    public function filter(mixed $object): mixed
    {
        if (! is_object($object)) {
            return $object;
        }

        $className = $object::class;

        if (! class_exists($className)) {
            return $object;
        }

        $reflectionClass = new ReflectionClass($className);

        $properties = $reflectionClass->getProperties();

        foreach ($properties as $property) {
            $attributes = $property->getAttributes(
                name: FilterInterface::class,
                flags: ReflectionAttribute::IS_INSTANCEOF,
            );

            foreach ($attributes as $attribute) {
                $filter = $attribute->newInstance();
                $value  = $filter->filter($property->getValue($object));
                $property->setValue($object, $value);
            }
        }

        return $object;
    }
}

from laminas-filter.

froschdesign avatar froschdesign commented on June 3, 2024

@delolmo
Your implementation ignores external dependencies of filters. The filter plugin manager must be used to retrieve a filter, as the manager resolves the dependencies and also the alias names of filters:

/**
* Plugin manager implementation for the filter chain.
*
* Enforces that filters retrieved are either callbacks or instances of
* FilterInterface. Additionally, it registers a number of default filters
* available, as well as aliases for them.
*
* @final
* @extends AbstractPluginManager<FilterInterface|callable(mixed): mixed>
*/
class FilterPluginManager extends AbstractPluginManager

More on this topic can be found in the documentation of laminas-servicemanager: Plugin Managers

from laminas-filter.

delolmo avatar delolmo commented on June 3, 2024

What about something like this then? I am sure there are better ways to do it, just trying to contribute something here. Let me know if there is something else I need to take into account.

use Laminas\ServiceManager\ServiceManager;
use ReflectionAttribute;
use ReflectionClass;

use function is_object;

final class AnnotatedObject implements Filter
{
    /** @var FilterPluginManager|null */
    protected $plugins;

    /**
     * Get plugin manager instance
     *
     * @return FilterPluginManager
     */
    public function getPluginManager()
    {
        $plugins = $this->plugins;
        if (! $plugins instanceof FilterPluginManager) {
            $plugins = new FilterPluginManager(new ServiceManager());
            $this->setPluginManager($plugins);
        }

        return $plugins;
    }

    public function filter(mixed $value): mixed
    {
        if (! is_object($value)) {
            return $value;
        }

        $reflectionClass = new ReflectionClass($value);

        $properties = $reflectionClass->getProperties();

        foreach ($properties as $property) {
            $attributes = $property->getAttributes(
                name: Filter::class,
                flags: ReflectionAttribute::IS_INSTANCEOF,
            );

            foreach ($attributes as $attribute) {
            	$filter = $this->plugin(
            		name: $attribute->getName(), 
            		options: $attribute->getArguments()
        		);
                /** @var mixed $origValue */
                $origValue = $property->getValue($value);
                /** @var mixed $newValue */
                $newValue = $filter->filter($origValue);
                $property->setValue($value, $newValue);
            }
        }

        return $value;
    }

    /**
     * Retrieve a filter plugin by name
     *
     * @param string $name
     * @return FilterInterface|callable(mixed): mixed
     */
    public function plugin($name, array $options = [])
    {
        $plugins = $this->getPluginManager();
        return $plugins->get($name, $options);
    }
    
    /**
     * Set plugin manager instance
     *
     * @return self
     */
    public function setPluginManager(FilterPluginManager $plugins)
    {
        $this->plugins = $plugins;
        return $this;
    }
}

from laminas-filter.

boesing avatar boesing commented on June 3, 2024

Maybe we find a way that it is somehow related to laminas-form as well, we do have Filters and Validators there as well and afair there are attributes.

But Overall I like the idea.

from laminas-filter.

delolmo avatar delolmo commented on June 3, 2024

I was revisiting this topic and I think the idea still is as an interesting one. Would you like to revisit it?

I have been thinking about what you said about laminas-form, but laminas-filter and laminas-validator should work in a standalone manner, shouldn't they? Perhaps we can move towards implementing the feature in filters and validators and then try to use this new feature in the laminas-form library.

Should I open a PR with my AnnotatedObject implementation?

from laminas-filter.

boesing avatar boesing commented on June 3, 2024

I think a PR would be very welcome.
However, you might want to have a look into #118 first and maybe wait some time until we decided on how we want to proceed with this library (esp. the option passing via __construct).

@froschdesign I am not sure if we need the filter plugin manager. Attributes are not annotations which can be invalid.
Not every filter can be an attribute either, so those filters which can be used as attributes are not allowed to have external dependencies anyways, as these cannot be resolved via the attribute notation.

I am not 100% sure if we should actually have this in-place as (at least I) do not see a real use-case for this (besides the very limited functionality provided by @delolmo).
One of my concerns are, that one has to have a mixed property and just by adding attributes, no static analyzer will understand what value is in those properties.

If we provide plugins for analyzers, that might work with analyzers but there is no way of actually verifying that the object has already applied filters or not and thus static analyzers will have a tough time.

That is why I do not use filters outside of forms (in which I can just pass array).
I'd rather have something like:

final class CreateNewBookDto extends Dto
{
    /** 
      * @param non-empty-string $author
      * @param uppercase-string $title
      */
    public function __construct(public readonly string $author, public readonly string $title)
    {
    }
}

With having StringTrim being applied prior instantiating the class.
This provides proper types without having mixed somewhere.

But thats most probably a personal thing and there might be projects and developers who are not that strict when it comes to their types. I would never pass any code review in our company with that but thats just because I have other expectations.

from laminas-filter.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.