I recently watched this video and wanted to change my Laravel controllers so that they had their dependencies managed with Laravel's IoC container. The video talks about creating an interface for a Model and then implementing that interface for the specific data source used.
My question is: when implementing the interface with a class that extends Eloquent and binding that class to the controller so that it is accessible from $this->model, should I also create interfaces and implementations for the Eloquent models which may be returned when calling methods such as $this->model->find($id)? Should there be different classes for the Model and the ModelRepository?
Put it another way: how do I do new Model when my model is in $this->model.
Generally, yes, people doing that pattern (the repository pattern) have an interface which have some methods defined that your app will use:
interface SomethingInterface {
public function find($id);
public function all();
public function paged($offset, $limit);
}
Then you create an implementation of this. If you're using Eloquent, then you can make an Eloquent implementation
use Illuminate\Database\Model;
class EloquentSomething {
protected $something;
public function __construct(Model $something)
{
$this->something = $something;
}
public function find($id)
{
return $this->something->find($id);
}
public function all() { ... }
public function paged($offset, $limit) { ... }
}
Then you make a service provider to put it all together, and add it into app/config/app.php.
use Something; // Eloquent Model
use Namespace\Path\To\EloquentSomething;
use Illuminate\Support\ServiceProvider;
class RepoServiceProvider extends ServiceProvider {
public function register()
{
$app = $this->app;
$app->bind('Namespace/Path/To/SomethingInterface', function()
{
return new EloquentSomething( new Something );
});
}
}
Finally, your controller can use that interface as a type hint:
use Namespace/Path/To/SomethingInterface;
class SomethingController extends BaseController {
protected $something;
public function __construct(SomethingInterface $something)
{
$this->something = $something;
}
public function home() { return $this->something->paged(0, 10); }
}
That should be it. Apologies on any errors, this isn't tested, but is something I do a lot.
Downsides:
More code :D
Upsides:
Able to switch out implementations (instead of EloquentSomething, can use ArraySomething, MongoSomething, whatever), without changing your controller code or any code that uses an implementation of your interface.
Testable - you can mock your Eloquent class and test the repository, or mock your constructor dependency and test your controller
Re-usable - you can App::make() to get the concrete EloquentSomething anywhere in your app and re-use the Something repository anywhere in your code
Repository is a good place to add additional logic, like a layer of cacheing, or even validation rules. Stock mucking about in your controllers.
Finally:, since I likely typed all that out and STILL DIDN'T ANSWER YOUR QUESTION (wtf?!), you can get a new instance of the model using $this->model. Here's an example for creating a new Something:
// Interface:
public function create(array $data);
// EloquentSomething:
public function create(array $data)
{
$something = this->something->newInstance();
// Continue on with creation logic
}
Key is this method, newInstance().
I've used $newModel = $this->model and it's worked for me.
Related
I am using Laravel 5.1 and would like to access an array on the Model from the Trait when the Model before the model uses the appends array.
I would like to add certain items to the appends array if it exists from my trait. I don't want to edit the model in order to achieve this. Are traits actually usable in this scenario or should I use inheritance?
array_push($this->appends, 'saucedByCurrentUser');
Here is how my current setup works.
Trait
<?php namespace App;
trait AwesomeSauceTrait {
/**
* Collection of the sauce on this record
*/
public function awesomeSauced()
{
return $this->morphMany('App\AwesomeSauce', 'sauceable')->latest();
}
public function getSaucedByCurrentUserAttribute()
{
if(\Auth::guest()){
return false;
}
$i = $this->awesomeSauced()->whereUserId(\Auth::user()->id)->count();
if ($i > 0){
return true;
}
return false;
}
}
Model
<?php namespace App;
use App\AwesomeSauceTrait;
use Illuminate\Database\Eloquent\Model;
class FairlyBlandModel extends Model {
use AwesomeSauceTrait;
protected $appends = array('age','saucedByCurrentUser');
}
What I would like to do is something to achieve the same effect as extending a class. I have a few similar traits, so using inheritance gets somewhat ugly.
trait AwesomeSauceTrait {
function __construct() {
parent::__construct();
array_push($this->appends, 'saucedByCurrentUser');
}
}
I have seen some workarounds for this, but none of them seem better/cleaner than just adding the item to the array manually. Any ideas are appreciated.
Update
I discovered this way of accomplishing what I need for one trait, but it only works for one trait and I don't see an advantage of using this over inheritance.
trait
protected $awesomeSauceAppends = ['sauced_by_current_user'];
protected function getArrayableAppends()
{
array_merge($this->appends, $this->awesomeSauceAppends);
parent::getArrayableAppends();
}
How I am currently handling my Model, for what it is worth.
model
public function __construct()
{
array_merge($this->appends, $this->awesomeSauceAppends);
}
Traits are sometimes described as "compiler-assisted copy-and-paste"; the result of using a Trait can always be written out as a valid class in its own right. There is therefore no notion of parent in a Trait, because once the Trait has been applied, its methods are indistinguishable from those defined in the class itself, or imported from other Traits at the same time.
Similarly, as the PHP docs say:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.
As such, they are not very suitable for situations where you want to mix in multiple variants of the same piece of behaviour, because there is no way for base functionality and mixed in functionality to talk to each other in a generic way.
In my understanding the problem you're actually trying to solve is this:
add custom Accessors and Mutators to an Eloquent model class
add additional items to the protected $appends array matching these methods
One approach would be to continue to use Traits, and use Reflection to dynamically discover which methods have been added. However, beware that Reflection has a reputation for being rather slow.
To do this, we first implement a constructor with a loop which we can hook into just by naming a method in a particular way. This can be placed into a Trait of its own (alternatively, you could sub-class the Eloquent Model class with your own enhanced version):
trait AppendingGlue {
public function __construct() {
// parent refers not to the class being mixed into, but its parent
parent::__construct();
// Find and execute all methods beginning 'extraConstruct'
$mirror = new ReflectionClass($this);
foreach ( $mirror->getMethods() as $method ) {
if ( strpos($method->getName(), 'extraConstruct') === 0 ) {
$method->invoke($this);
}
}
}
}
Then any number of Traits implementing differently named extraConstruct methods:
trait AwesomeSauce {
public function extraConstructAwesomeSauce() {
$this->appends[] = 'awesome_sauce';
}
public function doAwesomeSauceStuff() {
}
}
trait ChocolateSprinkles {
public function extraConstructChocolateSprinkles() {
$this->appends[] = 'chocolate_sprinkles';
}
public function doChocolateSprinklesStuff() {
}
}
Finally, we mix in all the traits into a plain model, and check the result:
class BaseModel {
protected $appends = array('base');
public function __construct() {
echo "Base constructor run OK.\n";
}
public function getAppends() {
return $this->appends;
}
}
class DecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
}
$dm = new DecoratedModel;
print_r($dm->getAppends());
We can set the initial content of $appends inside the decorated model itself, and it will replace the BaseModel definition, but not interrupt the other Traits:
class ReDecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
protected $appends = ['switched_base'];
}
However, if you over-ride the constructor at the same time as mixing in the AppendingGlue, you do need to do a bit of extra work, as discussed in this previous answer. It's similar to calling parent::__construct in an inheritance situation, but you have to alias the trait's constructor in order to access it:
class ReConstructedModel extends BaseModel {
use AppendingGlue { __construct as private appendingGlueConstructor; }
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Call the mixed-in constructor explicitly, like you would the parent
// Note that it will call the real parent as well, as though it was a grand-parent
$this->appendingGlueConstructor();
echo "New constructor executed!\n";
}
}
This can be avoided by inheriting from a class which either exists instead of the AppendingGlue trait, or already uses it:
class GluedModel extends BaseModel {
use AppendingGlue;
}
class ReConstructedGluedModel extends GluedModel {
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Standard call to the parent constructor
parent::__construct();
echo "New constructor executed!\n";
}
}
Here's a live demo of all of that put together.
I thought I'd add an update for 2019 since this was one of the first discussions that popped up when trying to do a similar thing. I'm using Laravel 5.7 and nowadays Laravel will do the reflection that IMSoP mentioned.
After the trait has been booted, Laravel will then call initializeTraitName() on the constructed object (where TraitName is the full name of the trait).
To add extra items to $appends from a trait, you could simply do this...
trait AwesomeSauceTrait {
public function initializeAwesomeSauceTrait()
{
$this->appends[] = 'sauced_by_current_user';
}
public function getSaucedByCurrentUserAttribute()
{
return 'whatever';
}
}
KISS:
I don't see any reason why you should use trait when your are simply appending attributes.
I would only recommend using trait without a constructor like you were doing, only if you model is getting pretty bulky and you wish to slim down things.
Please also note this not the correct way of appending attribute
protected $appends = array('age','saucedByCurrentUser');
You could do this:
protected $appends = array('age','sauced_by_current_user');
Appends attribute names should the snake_case of its method Name
Edited:
The idea behind appends is to dynamically add fields that doesn't exist in your database table to your model so after you can do like:
$model = FairlyBlandModel ::find(1);
dd($model->sauced_by_current_user);
In Laravel 4, query scopes are available on all queries (including ones generated by relations queries). This means that for the following (example) models:
Customer.php:
<?php
class Customer extends Eloquent {
public function order() { return $this->hasMany('Order'); }
}
Order.php:
<?php
class Order extends Eloquent {
public function scopeDelivered($query) { return $query->where('delivered', '=', true); }
public function customer() { return $this->belongsTo('Customer'); }
}
Both of the following work:
var_dump(Order::delivered()->get()); // All delivered orders
var_dump(Customer::find(1)->orders()->delivered()->get()); // only orders by customer #1 that are delivered
This is useful from within a controller because the query logic for finding delivered orders doesn't have to be repeated.
Recently, though, I've been convinced that the Repository pattern is optimal for not only separation of concerns but also for the possibility of a ORM/DB switch or the necessity of adding middleware like a cache. Repositories feel very natural, because now instead of having scopes bloat my models, the associated queries are instead part of the Repository (which makes more sense because naturally this would be a method of the collection not the item).
For example,
<?php
class EloquentOrderRepository {
protected $order;
public function __construct(Order $order) { $this->order = $order; }
public function find($id) { /* ... */ }
/* etc... */
public function allDelievered() { return $this->order->where('delivered', '=', true)->get(); }
}
However, now I have the delivered scope repeated, so to avoid violating DRY, I remove it from the model (which seems logical as per the justification above). But now, I can no longer can use scopes on relations (like $customer->orders()->delivered()). The only workaround here I see is somehow instantiating the Repository with the pre-made query (similar to what is passed to the scopes in the models) in the Relation base class. But this involves changing (and overriding) a lot of code and default behavior and seems to make things more coupled than they should be.
Given this dilemma, is this is misuse of a repository? If not, is my solution the only way to regain the functionality that I would like? Or is having the scopes in the models not tight enough coupling to justify this extra code? If the scopes aren't tight coupling, then is there a way to use both the Repository pattern and scopes while still being DRY?
Note: I am aware of some similar questions on similar topics but none of them address the issue presented here with queries generated by relationships, which do not rely on the Repository.
I've managed to find a solution. It's rather hacky and I'm not sure whether I consider it acceptable (it uses a lot of things in ways that they likely weren't meant to be used). To summarize, the solution allows you to move scopes to the repository. Each repository (on instantiation) is booted once, and during this process all of the scope methods are extracted and added to each query created by the eloquent model (via macros) by way of a Illuminate\Database\Eloquent\ScopeInterface.
The (Hack-y) solution
Repository Pattern Implementation
app/lib/PhpMyCoder/Repository/Repository.php:
<?php namespace PhpMyCoder\Repository;
interface Repository {
public function all();
public function find($id);
}
app/lib/PhpMyCoder/Repository/Order/OrderRepository.php:
<?php namespace PhpMyCoder\Repository\Order;
interface OrderRepository extends PhpMyCoder\Repository\Repository {}
Adding Eloquent Repositories (and a hack)
app/lib/PhpMyCoder/Repository/Order/EloquentOrderRepository.php:
<?php namespace PhpMyCoder\Repository\Order;
use PhpMyCoder\Repository\EloquentBaseRepository;
class EloquentOrderRepository extends EloquentBaseRepository implements OrderRepository {
public function __construct(\Order $model) {
parent::__construct($model);
}
public function finished() {
return $this->model->finished()->get();
}
public function scopeFinished($query) {
return $query->where('finished', '=', true);
}
}
Notice how the repository contains the scope that would normally be stored in the Order model class. In the database (for this example), Order needs to have a boolean column finished. We'll cover the details of EloquentBaseRepository below.
app/lib/PhpMyCoder/Repository/EloquentBaseRepository.php:
<?php namespace PhpMyCoder\Repository;
use Illuminate\Database\Eloquent\Model;
abstract class EloquentBaseRepository implements Repository {
protected $model;
// Stores which repositories have already been booted
protected static $booted = array();
public function __construct(Model $model) {
$this->model = $model;
$this->bootIfNotBooted();
}
protected function bootIfNotBooted() {
// Boot once per repository class, because we only need to
// add the scopes to the model once
if(!isset(static::$booted[get_class($this)])) {
static::$booted[get_class($this)] = true;
$this->boot();
}
}
protected function boot() {
$modelScope = new ModelScope(); // covered below
$selfReflection = new \ReflectionObject($this);
foreach (get_class_methods($this) as $method) {
// Find all scope methods in the repository class
if (preg_match('/^scope(.+)$/', $method, $matches)) {
$scopeName = lcfirst($matches[1]);
// Get a closure for the scope method
$scopeMethod = $selfReflection->getMethod($method)->getClosure($this)->bindTo(null);
$modelScope->addScope($scopeName, $scopeMethod);
}
}
// Attach our special ModelScope to the Model class
call_user_func([get_class($this->model), 'addGlobalScope'], $modelScope);
}
public function __call($method, $arguments) {
// Handle calls to scopes on the repository similarly to
// how they are handled on Eloquent models
if(method_exists($this, 'scope' . ucfirst($method))) {
return call_user_func_array([$this->model, $method], $arguments)->get();
}
}
/* From PhpMyCoder\Repository\Order\OrderRepository (inherited from PhpMyCoder\Repository\Repository) */
public function all() {
return $this->model->all();
}
public function find($id) {
return $this->model->find($id);
}
}
Each time an instance of a repository class is instantiated for the first time, we boot the repository. This involves aggregating all "scope" methods on the repository into a ModelScope object and then applying that to the model. The ModelScope will apply our scopes to each query created by the model (as seen below).
app/lib/PhpMyCoder/Repository/ModelScope.php:
<?php namespace PhpMyCoder\Repository;
use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;
class ModelScope implements ScopeInterface {
protected $scopes = array(); // scopes we need to apply to each query
public function apply(Builder $builder) {
foreach($this->scopes as $name => $scope) {
// Add scope to the builder as a macro (hack-y)
// this mimics the behavior and return value of Builder::callScope()
$builder->macro($name, function() use($builder, $scope) {
$arguments = func_get_args();
array_unshift($arguments, $builder->getQuery());
return call_user_func_array($scope, $arguments) ?: $builder->getQuery();
});
}
}
public function remove(Builder $builder) {
// Removing is not really possible (no Builder::removeMacro),
// so we'll just overwrite the method with one that throws a
// BadMethodCallException
foreach($this->scopes as $name => $scope) {
$builder->macro($name, function() use($name) {
$className = get_class($this);
throw new \BadMethodCallException("Call to undefined method {$className}::{$name}()");
});
}
}
public function addScope($name, \Closure $scope) {
$this->scopes[$name] = $scope;
}
}
The ServiceProvider and Composer File
app/lib/PhpMyCoder/Repository/RepositoryServiceProvider.php:
<?php namespace PhpMyCoder\Repository;
use Illuminate\Support\ServiceProvider;
use PhpMyCoder\Repository\Order\EloquentOrderRepository;
class RepositoryServiceProvider extends ServiceProvider {
public function register() {
// Bind the repository interface to the eloquent repository class
$this->app->bind('PhpMyCoder\Repository\Order\OrderRepository', function() {
return new EloquentOrderRepository(new \Order);
});
}
}
Be sure to add this service provider to the providers array in the app.php config:
'PhpMyCoder\Repository\RepositoryServiceProvider',
And then add the app/lib to composer's autoload
"autoload": {
"psr-0": {
"PhpMyCoder\\": "app/lib"
},
/* etc... */
},
This will require a composer.phar dump-autoload.
The Models
app/models/Customer.php:
<?php
class Customer extends Eloquent {
public function orders() {
return $this->hasMany('Order');
}
}
Notice that for brevity, I've excluded writing a repository for Customer, but in a real application you should.
app/model/Order.php:
<?php
class Order extends Eloquent {
public function customer() {
return $this->belongsTo('Customer');
}
}
Notice how the scope is not longer stored in the Order model. This makes more structural sense, because the collection level (repository) should be responsible for scopes applying to all orders while Order should only be concerned with details specific to one order. For this demo to work, order must have an integer foreign key customer_id to customers.id and a boolean flag finished.
Usage in the Controller
app/controllers/OrderController.php:
<?php
// IoC will handle passing our controller the proper instance
use PhpMyCoder\Repository\Order\OrderRepository;
class OrderController extends BaseController {
protected $orderRepository;
public function __construct(OrderRepository $orderRepository) {
$this->orderRepository = $orderRepository;
}
public function test() {
$allOrders = $this->orderRepository->all();
// Our repository can handle scope calls similarly to how
// Eloquent models handle them
$finishedOrders = $this->orderRepository->finished();
// If we had made one, we would instead use a customer repository
// Notice though how the relation query also has order scopes
$finishedOrdersForCustomer = Customer::find(1)->orders()->finished();
}
}
Our repository not only contains the scopes for the child model, which is more SOLID. They also come with the ability to handle calls to the scope like a real Eloquent model would. And they add all scopes to each query created by the model so that you have access to them when retrieving related models.
Problems with this Approach
A lot of code for little functionality: arguably too much to accomplish the desired result
It's hacky: macros on Illuminate\Database\Eloquent\Builder and Illuminate\Database\Eloquent\ScopeInterface (in conjunction with Illuminate\Database\Eloquent\Model::addGlobalScope) are likely used in ways they weren't intended to be
It requires instantiation of the repository (MAJOR ISSUE): if you're within the CustomerController and you only have instantiated CustomerRepository, $this->customerRepository->find(1)->orders()->finished()->get() won't work as expected (the finished() macro/scope won't be added to each Order query unless you instantiate OrderRepository).
I'll investigate if there is a more elegant solution (which remedies the issues listed above), but this is the best solution I can find thus far.
Related Resources on the Repository Pattern
Creating flexible Controllers in Laravel 4 using Repositories
Eloquent tricks for better Repositories
I've been using Laravel for a while now and I have been reading a lot about Dependency Injection an testable code. I've come to a point of confusion when talking about Facades and Mocked Objects. I see two patterns:
class Post extends Eloquent {
protected $guarded = array();
public static $rules = array();
}
This is my Post Model. I could run Post::all(); to get all the posts from my blog. Now I want to incorporate it into my controller.
Option #1: Dependency Injection
My first instinct would be to inject the Post model as a dependecy:
class HomeController extends BaseController {
public function __construct(Post $post)
{
$this->post = $post;
}
public function index()
{
$posts = $this->posts->all();
return View::make( 'posts' , compact( $posts );
}
}
My unit test would look like this:
<?php
use \Mockery;
class HomeControllerTest extends TestCase {
public function tearDown()
{
Mockery::close();
parent::tearDown();
}
public function testIndex()
{
$post_collection = new StdClass();
$post = Mockery::mock('Eloquent', 'Post')
->shouldRecieve('all')
->once()
->andReturn($post_collection);
$this->app->instance('Post',$post);
$this->client->request('GET', 'posts');
$this->assertViewHas('posts');
}
}
Option #2: Facade Mocks
class HomeController extends BaseController {
public function index()
{
$posts = Post::all();
return View::make( 'posts' , compact( $posts );
}
}
My unit test would look like this:
<?php
use \Mockery;
class HomeControllerTest extends TestCase {
public function testIndex()
{
$post_collection = new StdClass();
Post::shouldRecieve('all')
->once()
->andReturn($post_collection);
$this->client->request('GET', 'posts');
$this->assertViewHas('posts');
}
}
I understand both methods but I don't understand why I should or when I should use one method over the other. For example, I've tried to use the DI route with the Auth class but it doesn't work so I have to use the Facade Mocks. Any calcification on this issue would be greatly appreciated.
Although you use dependency injection on Option #1, your controller is still coupled with the Eloquent ORM. (Note that i avoid to use the term Model here because in MVC the Model is not just a class or an object but a layer. It's your business logic.).
Dependency Injection allows for Dependency Inversion but they are not the same thing. According to the Dependency Inversion principle both high and low level code should depend on abstractions. In your case the high level code is your controller and the low level code is the Eloquent ORM that fetches data from MySQL, but as you can see none of them depends on abstractions.
As a consequence, you are not able to change your data access layer without affecting your controller. How would you go about changing for example from MySQL to MongoDB or to the File System? To do this you have to use repositories (or whatever you want to call it).
So create a repositories interface that all your concrete repository implementations (MySQL, MongoDB , File System etc.) should implement.
interface PostRepositoriesInterface {
public function getAll();
}
and then create your concrete implementation e.g. for MySQL
class DbPostRepository implements PostRepositoriesInterface {
public function getAll()
{
return Post::all()->toArray();
/* Why toArray()? This is the L (Liskov Substitution) in SOLID.
Any implementation of an abstraction (interface) should be substitutable
in any place that the abstraction is accepted. But if you just return
Post:all() how would you handle the situation where another concrete
implementation would return another data type? Probably you would use an if
statement in the controller to determine the data type but that's far from
ideal. In PHP you cannot force the return data type so this is something
that you have to keep in mind.*/
}
}
Now your controller must type hint the interface and not the concrete implementation. This is what "Code on an interface an not on implementation" is all about. This is Dependency Inversion.
class HomeController extends BaseController {
public function __construct(PostRepositoriesInterface $repo)
{
$this->repo= $repo;
}
public function index()
{
$posts = $this->repo->getAll();
return View::make( 'posts' , compact( $posts ) );
}
}
This way your controller is decoupled from your data layer. It's open for extension but closed for modification. You can switch to MongoDB or to the File System by creating a new concrete implementation of PostRepositoriesInterface (e.g. MongoPostRepository) and change only the binding from (Note that i don't use any namespaces here):
App:bind('PostRepositoriesInterface','DbPostRepository');
to
App:bind('PostRepositoriesInterface','MongoPostRepository');
In an ideal situation your controller should contain only application and not business logic. If you ever find yourself wanting to call a controller from another controller its a sign that you've done something wrong. In this case your controllers contain too much logic.
This also makes testing easier. Now you are able to test your controller without actually hitting the database. Note that a controller test must test only if the controller functions properly which means that the controller calls the right method, gets the results and pass it to the view. At this point you are not testing the validity of the results. This is not controller's responsibility.
public function testIndexActionBindsPostsFromRepository()
{
$repository = Mockery::mock('PostRepositoriesInterface');
$repository->shouldReceive('all')->once()->andReturn(array('foo'));
App::instance('PostRepositoriesInterface', $repository);
$response = $this->action('GET', 'HomeController#index');
$this->assertResponseOk();
$this->assertViewHas('posts', array('foo'));
}
EDIT
If you choose to go with option #1 you can test it like this
class HomeControllerTest extends TestCase {
public function __construct()
{
$this->mock = Mockery::mock('Eloquent', 'Post');
}
public function tearDown()
{
Mockery::close();
}
public function testIndex()
{
$this->mock
->shouldReceive('all')
->once()
->andReturn('foo');
$this->app->instance('Post', $this->mock);
$this->call('GET', 'posts');
$this->assertViewHas('posts');
}
}
Say I have an interface CrawlerInterface with implementation PageCrawler and FeedCrawler; if we happen to need both classes in a controller, how can that be achieved with constructor injection?
Previously we use a central ServiceProvider to register (i.e. App::bind) such classes, but in most cases we only have 1 implementation of an interface, so said problem hasn't occured to us yet.
PS: I also wonder if this problem suggests we should split the controller.
Updates:
Thanks for the comments and response, to explain, said interface has only one public method: crawl($uri), and both page/feed crawler implements it as given a resource identifier, return resource.
My follow up question:
Say we are in a calculator scenario where Addition, Subtraction and Multiplication share the same interface Operation, which has only 1 public method run, at some point we will still encounter this problem right? How do we handle situation like these in general with ServiceProvider?
If each crawler exists for a different reason, you can use arbitrary names for your instances, for example:
App::bind('crawler.allArticles', 'PageCrawler');
App::bind('crawler.latestArticles', 'FeedCrawler');
For the controller:
App::bind('CrawlerController', function($app) {
return new CrawlerController(
App::make('crawler.allArticles'),
App::make('crawler.latestArticles')
);
});
Your controller code would then use each crawler differently:
public function showLatestArticlesAction()
$latestArticles = $this->latestArticlesCrawler->crawl();
// ...
}
public function showAllArticlesAction()
$allArticles = $this->allArticlesCrawler->crawl();
// ...
}
If you just have a list of crawlers where each is used for the same thing, you probably want to do something like:
App::bind('crawlers', function($app) {
return [
App::make('PageCrawler'),
App::make('FeedCrawler'),
];
});
In your controller, you'll get a list of "crawlers" by configuring it like so:
App::bind('CrawlerController', function($app) {
return new CrawlerController(App::make('crawlers'));
});
Your controller code could be something like this:
public function showArticlesAction()
$allArticles = array();
foreach ($this->crawlers as $crawler) {
$allArticles = array_merge($allArticles, $this->crawler->crawl());
}
// ...
}
Ok lets assume you have a CrawlerController
class CrawlerController extends BaseController
{
protected $crawler1;
protected $crawler2;
public function __construct(CrawlerInterface $c1, CrawlerInterface $c2)
{
$this->crawler1 = $c1;
$this->crawler2 = $c2;
}
}
an interface
interface CrawlerInterface{}
and concrete implementations of that intefrace called PageCrawler and FeedCrawler
class PageCrawler implements CrawlerInterface{}
class FeedCrawler implements CrawlerInterface{}
You would inject the dependencies by writing a service locator like
App::bind('CrawlerController', function($app) {
$controller = new CrawlerController(
new PageCrawler,
new FeedCrawler
);
return $controller;
});
But as suggested by others you should rethink your logic, use it only if this kind
of architecture is unavoidable
I think that the interface won't help you in this case.
By doing:
App::bind('CrawlerInterface', '<implementation>');
You need to choose one:
App::bind('CrawlerInterface', 'PageCrawler');
or
App::bind('CrawlerInterface', 'FeedCrawler');
And then Laravel will inject it:
class CrawlerController {
public function __construct(CrawlerInterface $crawler)
{
}
}
To have both you have 2 options
-Have 2 different interfaces
-Inject the implementations directly:
class CrawlerController {
public function __construct(PageCrawler $pageCrawler, FeedCrawler $feedCrawler)
{
}
}
But I also think that, if you need something like this, you better rethink your logic.
My Dispatcher is "choosing" correct Controller; then creating Controller's instance (DependencyInjectionContainer is passed to Controller constructor); then calling some Controller's method...
class UserController extends Controller
{
public function __construct(DependencyInjectionContainer $injection) {
$this->container = $injection;
}
public function detailsAction() {
...
}
}
DependencyInjectionContainer contains DB adapter object, Config object etc.
Now let's see what detailsAction() method contains...
public function detailsAction() {
$model = new UserModel();
$model->getDetails(12345);
}
As you see I'm creating new instance of UserModel and calling getDetails methods.
Model's getDetails() method should connect to db to get information about user. To connect to DB UserModel should be able to access DB adapter.
What is the right way to pass DependencyInjectionContainer to the UserModel?
I think that this way is wrong...
public function detailsAction() {
$model = new UserModel($this->container);
$model->getDetails(12345);
}
Instead of injecting the entire DI Container into your classes, you should inject only the dependencies you need.
Your UserController requires a DB Adapter (let's call this interface IDBAdapter). In C# this might look like this:
public class UserController
{
private readonly IDBAdapter db;
public UserController(IDBAdapter db)
{
if (db == null)
{
throw new ArgumentNullException("db");
}
this.db = db;
}
public void DetailsAction()
{
var model = new UserModel(this.db);
model.GetDetails(12345);
}
}
In this case we are injectiing the dependency into the UserModel. In most cases, however, I would tend to consider it a DI smell if the UserController only takes a dependency to pass it on, so a better approach might be for the UserController to take a dependency on an Abstract Factory like this one:
public interface IUserModelFactory
{
UserModel Create();
}
In this variation, the UserController might look like this:
public class UserController
{
private readonly IUserModelFactory factory;
public UserController(IUserModelFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException("factory");
}
this.factory = factory;
}
public void DetailsAction()
{
var model = this.factory.Create();
model.GetDetails(12345);
}
}
and you could define a concrete UserModelFactory that takes a dependency on IDBAdapter:
public class UserModelFactory : IUserModelFactory
{
private readonly IDBAdapter db;
public UserModelFactory(IDBAdapter db)
{
if (db == null)
{
throw new ArgumentNullException("db");
}
this.db = db;
}
public UserModel Create()
{
return new UserModel(this.db);
}
}
This gives you better separation of concerns.
If you need more than one dependency, you just inject them through the constructor. When you start to get too many, it's a sign that you are violating the Single Responsibility Principle, and it's time to refactor to Aggregate Services.
I'd use a singleton object for all config parameters :
You set it up in your bootstrap, then choose to use it directly or pass it as parameter in your objects.
The idea being to have one method all around to retrieve your config data.
You may then provide an abstract class for db manipulation which uses your config. singleton.
DependancyInjection can still be used to override your default data.
The above link in the comment (possible 'duplicate') concludes on using constructor injection : this is close to your current method.
However if I try to figure how your model works, I guess you will have many other model classes other than "userModel". Thus an abstract class using a config singleton might be a good solution : all your next model classes will just extend this abstract class, and you don't have to worry about your config.
On the other hand, your solution is good to me as long as your dependanceInjectionContainer changes often.