Ответ 1
Несмотря на то, что вы используете инъекцию зависимостей на Option # 1, ваш контроллер все еще связан с Eloquent ORM. (Обратите внимание, что я не использую термин "Модель" здесь, потому что в MVC модель - это не просто класс или объект, а слой. Это ваша бизнес-логика.).
Инъекция зависимостей допускает инверсию зависимостей, но это не одно и то же. Согласно принципу инверсии зависимостей, код высокого и низкого уровня должен зависеть от абстракций. В вашем случае код высокого уровня является вашим контроллером, а низкоуровневый код является Eloquent ORM, который извлекает данные из MySQL, но, как вы видите, ни один из них не зависит от абстракций.
Как следствие, вы не можете изменить свой уровень доступа к данным, не влияя на ваш контроллер. Как бы вы перешли, например, из MySQL в MongoDB или в файловую систему? Для этого вам нужно использовать репозитории (или все, что вы хотите назвать).
Итак, создайте интерфейс репозиториев, который должен реализовать все ваши конкретные реализации репозитория (MySQL, MongoDB, Файловая система и т.д.).
interface PostRepositoriesInterface {
public function getAll();
}
а затем создайте свою конкретную реализацию, например. для 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 far from
ideal. In PHP you cannot force the return data type so this is something
that you have to keep in mind.*/
}
}
Теперь ваш контроллер должен ввести подсказку интерфейса, а не конкретную реализацию. Вот что такое "Код на интерфейсе, а не на реализацию". Это инверсия зависимостей.
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 ) );
}
}
Таким образом, ваш контроллер отключается от вашего уровня данных. Он открыт для расширения, но закрыт для модификации. Вы можете переключиться на MongoDB или в файловую систему, создав новую конкретную реализацию PostRepositoriesInterface (например, MongoPostRepository) и изменив только привязку (обратите внимание, что я не использую здесь пространства имен):
App:bind('PostRepositoriesInterface','DbPostRepository');
к
App:bind('PostRepositoriesInterface','MongoPostRepository');
В идеальной ситуации ваш контроллер должен содержать только прикладную, а не бизнес-логику. Если вы когда-нибудь захотите позвонить контроллеру с другого контроллера, это признак того, что вы сделали что-то неправильно. В этом случае ваши контроллеры содержат слишком много логики.
Это облегчает тестирование. Теперь вы можете протестировать свой контроллер без фактического попадания в базу данных. Обратите внимание, что тест контроллера должен проверяться только в том случае, если контроллер функционирует правильно, что означает, что контроллер вызывает правильный метод, получает результаты и передает его в представление. На этом этапе вы не проверяете достоверность результатов. Это не ответственность диспетчера.
public function testIndexActionBindsPostsFromRepository()
{
$repository = Mockery::mock('PostRepositoriesInterface');
$repository->shouldReceive('all')->once()->andReturn(array('foo'));
App::instance('PostRepositoriesInterface', $repository);
$response = $this->action('GET', '[email protected]');
$this->assertResponseOk();
$this->assertViewHas('posts', array('foo'));
}
ИЗМЕНИТЬ
Если вы решите пойти с опцией №1, вы можете протестировать ее так:
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');
}
}