3

So I want to do the following and I'm really just looking for advice on how to design it.

  • I have Filters which can be run on certain entity types
  • Filters have Rules to define their behavior
  • Filters process entities, using their rules, and return a boolean
  • As a concrete example I may want to filter which says that some regular expression on a given field is rejected, and I can define rules for it. I could also have different rules that use simple prefix matching in the same filter.

So I've come up with the following

interface FilterInterface
{
    /**
     * Load the rules for this filter
     */
    function loadRules();

    /**
     * Filter an entity in an optional context
     * @param mixed $entity Should return true for $this->supportsEntity($entity)
     * @param array $context Other information
     * 
     * @return boolean True if the $entity passes this filter
     */
    function filter($entity, $context = array());

    /**
     * Check if filter supports the entity type
     * @param mixed $entity
     * 
     * @return boolean true if this Filter can be run for that entity type
     */
    function supportsEntity($entity);
}

Then I plan to make:

class ImageFilter implements FilterInterface

Which will load rules from the database and implement the filter.

So initially I though of having an Entity like

FilterRule:

  • scope (enum type to define which filter uses this)
  • type (can have different types of rules, like the regex vs. prefix I mentioned)
  • value (string, depends on the type of rule)

And ImageFilter would load FilterRules where the scope is set to some constant. Then ImageFilter would do the heavy lifting of checking the type of filter and applying it to the entity, one after another. The following psuedocode demonstrates this.

public function filter($entity)
{
    foreach ($this->rules as $rule) {
        switch($rule->getType()) {
            case RULE_TYPE_REGEX:
                $this->doRegex();
            break;
            case RULE_TYPE_PREFIX:
                $this->doPrefix();
            break;
        }
    }
}

But then I wondered, should the entity be responsible for the above switch block? So it would become something like:

public function filter($entity)
{
    foreach ($this->rules as $rule) {
        $rule->process($entity);
    }
}

This seems cleaner to me, the Filter doesn't care about new rules and how to handle them as long as the "scope" is correct. But I was under the impression that entities should be simple POPO so it seems like having this kind of logic is a bad pattern (but I was never 100% sure on how much an entity should really do).

I had also considered using Inheritance Mapping so each Rule "type" could be a different class and it's logic would be pretty simple (only a couple of lines at most). Finally, I was thinking maybe the different type of Rule should somehow use composition to have a "behavior" or something which would do the actual work...

So to summarize, here are my current thoughts:

  1. Filter does all the work checking the type of Rule and acting accordingly. Rule is just a "dumb" entity to hold some strings
  2. Rule does all the work of checking its own type and doing something to a given entity. Now the Rule entity is a bit "fatter" but it hides the implementation from Filter and Filter doesn't need to change to add a new Rule type
  3. Rule uses inheritance to do the above, effectively the same idea but no more switch statement
  4. Either 2. or 3. but Rule instantiates a different "behavior" object for each type (not really using dependency injection seems bad, encapsulating the behavior seems like a good design though)
  5. Combine 1. and 4. so the Filter can get the proper behavior from the Rule (can now use dependency injection with like a "behavior" factory)

I guess I'm leaning more toward the last few options the more I think about it, but I don't know the best practices in this area. I'm sure this is a relatively common example so I'm hoping to get some input on which direction to take. (Sorry for such a long question, hopefully it's complete enough to answer)

4

1 に答える 1