Closed phenaproxima closed 9 years ago
As of my most recent commit, the following three things would be equivalent (although I haven't actually written a filter for ClassNode, so it won't actually work):
// The old way
$node->find(Filter::isClass('Foobaz'));
// The "origin" way, where the filter searches outward from the node which created it
$node->filter('\Pharborist\ClassNode')->name('Foobaz')->find()
// Create a filter directly and pass it around as a callable
$node->find((new ClassFilter)->name('Foobaz'));
The difference is that, with the second way, it'll be possible to add more conditions, like checking specifically for abstract classes or whatever. Configurable filters FTW!
Also any thoughts on how you will combine filters (like Filter::any etc)?
Also how would you convert where chain filters now, eg:
$node->children(Filter::isInstanceOf('\Pharborist\Types\StatementNode'))
->filter(function ($node) { return someTest($node); });
Will it be like so?
$node->filter('\Pharborist\Filters\InstanceOfFilter')
->name('\Pharborist\Types\StatementNode')
->matchChildren()
->filter(new SomeTestFilter())
This isn't in the PoC code, but my intention is that, by their very nature, most filters will be implicitly bound to a node type. ClassNodeFilter, for example -- produced by ClassNode::createFilter() -- searches only for classes, so InstanceOf becomes unnecessary.
So the second example will actually look like this:
// Get StatementNodes which are children of $node.
$node->filter('\Pharborist\Types\StatementNode')->matchChildren()->filter(...)
What would also be neat, somehow, is for separate filters to be chainable:
// Create an "any" chain.
$node->filter('\Pharborist\Objects\ClassNode')->name('Wambooli', 'Foo')->or('\Pharborist\InterfaceNode')->name('Baz')
// Create an "all" chain.
$node->filter('MyNodeType')->name('Whatever')->and('AnotherNodeType')->etc(...)
The or() and and() methods -- I'll call it something else, obviously -- will let you daisy-chain filters together for complex filtering logic comprised of several smaller filters. Very "functional programming".
CombinatorInterface is nice.. I think thats the strategy pattern in Design pattern lingo.
$filter1 = FunctionFilter::create('hello');
$filter2 = SomeOtherFilter::create('world');
$filter = new AnyCombinator();
$filter->add($filter1)->add($filter2);
$results = $node->find($filter);
Is that the right? for when wanting to combine two different filters?
What about a shorthand something like
abstract class FilterBase implements FilterInterface {
public function and(FilterInterface $filter) {
$result = new AnyCombinator();
$result->add($this)->add($filter);
return $result;
}
}
$filter = FunctionFilter::create('hello')->and(SomeOtherFilter::create('world'));
That is FilterBase has and/or methods that take another filter and return a new filter that is combination of the two.
I dig that idea; the only problem is that and and or are reserved keywords in PHP, so we'll need a different name for those builder methods. Any thoughts?
I think this is ready for review. I've merged master back into it, updated all the tests, and made Pharborist\Filter's methods return executable filter objects. It's only upwards from here!
I want to take this back to the drawing board for some re-thinking. Closing, and deleting the branch.
This is the foundation/proof-of-concept of the fluent filtering API (rolls off the tongue nicely, doesn't it?), as described, more or less, in #101.
Filters will go in the Filters sub-namespace and implement the Filter interface (which, at the moment, only enforces the __invoke method).
Node now has a filter() method, which is really just a factory method. If it's passed a node class name, it will check that said class implements FilterFactoryInterface (if no class name is passed, it will run this check on itself). If it does, it'll call the class's createFilter() static method, which returns an implementation of the Filter interface. If not, DomainException is thrown.
The filter returned by createFilter() should be configurable using a fluent syntax. It can then be passed to any of the normal filtering and traversal methods.
Filters that extend FilterBase have the concept of an "origin", which is simply the node on which createFilter() was originally called. I haven't actually used this in the proof of concept code, but the idea is to be able to do things like this:
That is: create the filter, configure it, then execute the filter on the node which originally created it.