I would always use Dependency Injection without manual binding in the container. So the first option for me is the only option.
instantiation of chained filters
I have different Filter classes and each class has a number of filter methods. There is ( so far ) one case where I need to apply filters from 2 classes. What I did is that I created another class named FilterManager which stores a list of all the Filter classes you need and then it iterates over the list and applies the filters.
class ThreadFilters
{
protected $builder;
protected $supportedFilters = ['newThreads'];
public function newThreads($userId)
{
$this->builder->orderBy('created_at', 'DESC');
}
}
class FilterManager
{
protected $filters = [];
public function addFilter($filter)
{
$this->filters[] = $filter;
}
public function apply($builder)
{
foreach($this->filters as $filter)
{
$filter->builder = $builder;
foreach($filter->supportedFilters as $filterMethod)
{
$filter->$filterMethod()
}
$builder = $filter->builder;
}
}
}
Now in the controller I can do something like
First Approach
class ThreadController(FilterManager $filterManager)
{
$filters = $filterManager->addFilter(new ThreadFilter());
$filters->apply($someBuilder);
}
On the other hand I could use the app function
app(ThreadFilters::class)
and in the service container basically I could do
$this->app->bind(ThreadFilters::class, function($app){
$filterManager = new FilterManager();
$filterManager->addFilter(new ThreadFilters());
return $filterManager;
});
and therefore my controller would look like
Second Approach
class ThreadController()
{
$filters = app(ThreadFilters::class);
$filter->apply($someBuilder);
}
My question is which solution is good/acceptable.
In the First Approach the FilterManager dependency is injected and the controller has to add the ThreadFilters
In the Second Approach no dependencies are injected and the controller has to use the service container to hide the specifics and make the controller has less knowledge about the filters.
Then put all that managing logic inside FilterManager.
Problem that you have now is following:
- if you want to use the same set of filters you would have to duplicate your code
- someone who didn't work on this feature would have to search through the codebase to see how it is being done
- there is no business logic showing which filters should be used together cause you might have some that should not be mixed
- and many more
Put everything inside FilterManager class.
class ThreadController extends Controller
{
public function index(FilterManager $filterManager)
{
$filterManager->withThreadFilters()->apply($builder);
}
}
class FilterManager
{
// ... your code
public function withThreadFilters(): self
{
$this->filters = []; // reset the filters array so it is not mixed with some other set of filters
$this->filters[] = new ThreadFilter;
$this->filters[] = new PostFilter;
// ... and so on
return $this;
}
}
This way it is clear which filters go together and which purpose they serve. You can remove addFilter() method and simplification is always good. There are many more options you can use here, but this is as simple as it gets.
Please or to participate in this conversation.