I’ve used Lithium for 2 projects now, and I have one major issue – I honestly don’t see the benefit of “filters” vs. “callback” methods.
I understand that conceptually it’s supposed to “decouple” the filter logic from the model itself. However, every example of applying filters to Models show the filter being applied to an individual model directly using the 2 methods below:
<?php class MyModel extends \lithium\data\Model { public static function __init() { // ... static::applyFilter('save', function(...) { // ... }); } }
or
<?php class MyModel extends \lithium\data\Model { } MyModel::applyFilter('save', function(...) { // ... });
In my humble opinion, neither of this is “decoupling”, since the actual code for the filters exists directly within the model itself anyway.
One would think you can overcome this my applying the filter to the “Model” class directly – WRONG! The filter never fires.
So, since I have a filter which is shared, I created a method in some other class which my individual filters call. How is this any different than simply having a callback which invokes the external method, too?
No – to me filters are thus-far fairly useless in the Model context (I’ve successfully used it on the Dispatcher).
Hi Nathan, Thanks for trying out Lithium. Even though closures and the aspect-oriented paradigm are a fairly well-defined and well-understood programming concepts in their own right, they’re still pretty new to the PHP community, so we’re still exploring how to use them and what the best practices are. And as with any tool, the value is all in how you use it. As such, the code examples you see won’t always make the best use of the tools, since a lot of the code out there is from the early days when the concepts were still being explored. In the above example, you’re right: there’s no point in putting a filter on a class inside the class itself. It’s better to just extend the appropriate method. To your point about filters in base classes not triggering, there actually hasn’t been a huge demand for it, but if you have a suggested approach that wouldn’t be too much overhead, I’m all ears. Finally, to the main issue of your question (i.e. ‘what’s the point?’), allow me to explain: whereas callbacks require specific knowledge of the class or method they’re being applied to, the Lithium filter API is uniform across the framework. This allows tools like loggers or debuggers to be applied consistently to any class method and “just work”. Also, one thing we found working on CakePHP was that often, when implementing complex behaviors, there was often a relationship between a task required before a method, and some post-processing operation required after. Anything involving querying is a classic example: often you’ll want to modify the query being executed in some way, and then post-process the results. Typically, the relationship between these two operations requires some shared state. Separating the before and after callbacks introduces the problem of having to store that state somewhere, such as a variable in the class. However, as soon as you’re doing more than one operation at a time, you’re in trouble. The filter system means never having to store that state, since the before and after parts of the operation exist in the same scope. Caching is a great example: when a query is executed, you can generate a cache key, then check to see if a cached entry exists. If not, continue executing the query, and use the cache key to store the results. Since all this happens in the same scope, you never have to do anything awkward or complicated to maintain your state. Also, because the filter API is uniform, and filter semantics work with native language constructs, you have way more control over method calls than you otherwise would, with way more consistency. With filters, you have full control over a method’s parameters, return value, and whether or not the method even executes. With filters, you have to hack on custom API semantics, such as having return values with special meanings (i.e. return null in beforeFilter to abort execution), and your whole API ends up awkward and inconsistent. Anyway, I hope that helps.