Normally Eloquent model is used as following:
class Article extends Eloquent
{
// Eloquent Article implementation
}
class MyController extends BaseController
{
public function getIndex()
{
$articles = Article::all(); // call static method
return View::make('articles.index')->with('articles', $articles);
}
}
But when restructing use Dependency Injection, it looks like that:
interface IArticleRepository
{
public function all();
}
class EloquentArticleRepository implements IArticleRepository
{
public function __construct(Eloquent $article)
{
$this->article = $article;
}
public function all()
{
return $this->article->all(); // call instance method
}
}
So why we can call the static method Article::all() in form of instance method $this->article->all()?
P/S: Sorry for my bad English.
Good question.
Laravel utilize the Facade design pattern. when you call Article::all(), a lot of things happened behind the screen. First, PHP try to call the static method if it fails php immediately call a magic method _callStatic. then Laravel cleverly capture the static call and create instance of the original class.
From Laravel doc:
Facades provide a "static" interface to classes that are available in the application's IoC container. Laravel ships with many facades, and you have probably been using them without even knowing it!
More info:
http://laravel.com/docs/facades
http://usman.it/laravel-4-uses-static-not-true/
Related
How can I mock/stub a method on a model correctly (in laravel)? Currently I am trying...
$mock = Mockery::spy(Organisation::class);
$mock->shouldReceive('findByCustomerId')->once();
and the code under test is
use App\Organisation;
...
public function handle()
{
$org = Organisation::findByCustomerId(1234);
However when I run the tests I get an error Call to undefined method App\Organisation::findByCustomerId(), which tells me that the class/model is not being mocked correctly, does anyone know where I could be going wrong?
Mockery doesn't automatically overload classes when you create a mock for them (like some other libraries might do). It does support overloading, but I couldn't figure out how to overload a class with a partial mock.
So it looks like you'll have to use dependency injection to "inject" the mock object into the class that is being tested:
class TestedClass
{
private $organisation;
public function __construct(Organisation $organisation)
{
$this->organisation = $organisation;
}
public function handle()
{
$org = $this->organisation->findByCustomerId(1234);
}
}
Laravel with automatically inject the correct object for you. To inject your mock object instead of the Organization class you can do something like this in your test:
$mock = \Mockery::spy(Organization::class)->makePartial();
$mock->shouldReceive('findByCustomerId')->once();
$this->app->instance(Organization::class, $mock);
I'm building an API via Slim, and using class based controller methods. Looking at the router docs (under "Allow Slim to instantiate the controller"), it seems like the DI should insert the ContainerInterface into the class constructor, and then I should be able to access $this->container in the class method to access the container.
I created a base class:
class BaseController
{
protected $container;
public function __construct(\Interop\Container\ContainerInterface $container) {
$this->container = $container;
}
}
And then tried this:
class PMsController extends BaseController
{
public function index(Request $request, Response $response, $args)
{
var_dump($this); exit;
}
}
And my route:
$app->group('/pms', function () {
$this->get('', '\App\Controllers\PMsController::index');
})->add(authMiddlware());
But I get the error: Using $this when not in object context. I have no idea how that gets there, when it's a class method. I'm not positive if I should be using another method of accessing the container?
Try changing your route to
$app->group('/pms', function () {
$this->get('', '\App\Controllers\PMsController:index');
})->add(authMiddlware());
Please note the single : rather than using double ::. You also don't need a BaseController if you aren't performing any additional feature in it. Slim 3 will inject the Container by default for you.
I'm still in the process of learning about Laravel and Dependency Injection. I understand the concept, but I don't know how to mock a dependency in this specific case:
MyController.php
use Illuminate\Routing\Controller;
use MyPackage\Services\ServiceInterface;
class MyController extends Controller{
protected $service;
public function __construct(ServiceInterface $service)
{
$this->service = $service;
}
}
MyServiceProvider.php
use Illuminate\Support\ServiceProvider;
class MyServiceProvider extends ServiceProvider{
public function register()
{
$this->app->bind('MyPackage\Services\ServiceInterface', function ($app) {
return new MyPackage\Services\ConcreteService(['key'=>'123'], $app->make('GuzzleHttp\Client'));
});
}
}
So, as you can see, I have a controller that requires an instance of ServiceInterface. That instance is being resolved in the ServiceProvider. The constructor of ConcreteService requires a client to perform Http request to an API. This Http is being resolved by the Service container (It will be an instance of Guzzle).
Now, how can I mock this instance of Guzzle on my tests?
The ideal result is doing something like this:
MyTest.php
...
$this->post(route('routeToActionInMyController'), $params);
So, in my tests I just need to hit the route that will be using an specific method of MyController.php but I don't need a "real" Guzzle instance. I just need to mock the response to test if MyController behaves in the expected way (and stores things in the database properly).
How can I instruct the Service Container to inject a Mocked object during tests only? Or am I doing this in the completely wrong way?
Any help will be appreciated.
Thanks in advance
In your test class:
class TestingSomething extends TestCase {
protected function setUp() {
parent::setUp();
$mockServiceInterface = $this->getMockBuilder(ServiceInterface::class)->getMock();
$this->app->instance(ServiceInterface::class,$mockServiceInterface);
}
public function testPostToRoute() {
$this->post(route('routeToActionInMyController'), $params);
}
}
This should replace what's already bound in the service container with that mock instance.
Refer to the PHPUnit manual on chapter 9. Test doubles for what you can do with the mock builder and resulting mocks.
EDITED (Code is updated and working for others)
For the overall idea of what's happening.
I'm trying to access post data from the view in the controller, without refreshing the page.
To do this I am executing the page controller by using a ViewHelper to call the Service below which then forwards back to the controller; afterwards I can manage the posted data in the page controller.
Everything works except the last step which is the forward(), I receive the error Call to undefined method AlbumModule\Service\postAlbumService::forward()
I understand I must implement the ServiceLocatorAwareInterface in order to use the forward() class, but what I've written doesn't seem to work.
<?php
namespace AlbumModule\Service;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class postAlbumService implements
ServiceLocatorAwareInterface
{
protected $services;
public function __construct() {
echo '<script>console.log("postAlbumService is Started")</script>';
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
public function test(){
$cpm = $this->getServiceLocator()->get('controllerpluginmanager');
$fwd = $cpm->get('forward');
echo '<script>console.log("postAlbumService TEST() is Started")</script>';
return $fwd->dispatch('newAlbum', array('action' => 'submitAlbum'));
}
}
It seems as though I'm just having a dependency issue with the forward() class, but I'm not sure what the issue is.
EDIT-
Here is how I am calling the postAlbumService from the viewHelper
<?php
namespace AlbumModule\View\Helper;
use Zend\View\Helper\AbstractHelper;
class invokeIndexAction extends AbstractHelper
{
protected $sm;
public function test()
{
$this->sm->getServiceLocator()->get('AlbumModule\Service\postAlbumService')->test();
}
public function __construct($sm) {
$this->sm = $sm;
}
}
Is there any way to call a specific class in the service being requested, after the dependencies are injected into the service?
You're doing a couple of things wrong and you're misunderstanding some things...
First of all, forward() is a ControllerPlugin. You'll gain access to this method by accessing said manager via the ServiceLocator. An example could be this:
$cpm = $serviceLocator->get('controllerpluginmanager');
$fwd = $cpm->get('forward');
return $fwd->dispatch('foo/bar');
Now, to get the ServiceLocator into any of your Service-Classes you need Dependency Injection. One of the ways is to implement the ServiceLocatorAwareInterface. The ServiceManager of ZF2 has so called Listeners. These Listeners check for implemented interfaces and stuff like this. Whenever it finds a match, it injects the required dependencies via the interfaces given functions. The workflow looks like this:
ServiceManager get('FooBar');
$ret = new FooBar();
foreach (Listener)
if $ret instanceof Listener
doInjectDependenciesInto($ret)
end
end
return $ret
Now what does this tell you. This tells you, that within the __construct() of any of your classes NONE of your required dependencies are actually there. They only get injected AFTER the class/service has been instantiated.
On a last side-note, the given code example doesn't really make much sense ;) No matter what ServiceAction i'd like to access, you'd always return me to the "newAlbum" action...
I have an interface that declares the implementation needs methods such as find, findOrFail etc, basically Laravel eloquent methods.
I declare these methods in the interface because not everything that implements the interface will extend eloquent so I declare them in the interface so my app always knows the methods are going to be there.
What I want to know is, other than having a bunch of public function find($id){return parent::find($id)} type methods in the models that do extend the eloquent model is there an easy way to let the interface know that the method is handled via __call?
Although there may be a larger question as to the cleanliness of such a design, you can accomplish something akin to this by using a trait which implements the methods of the interface:
interface FindableContract {
public function find($id);
}
trait MagicFindableTrait {
public function find($id) {
return static::__call(__FUNCTION__, func_get_args());
}
}
class MagicalParent {
public function __call($method, $args) {
if ($method == 'find') {
return "User " . $args[0] . " is a witch! May we burn her?!";
}
}
}
class User extends MagicalParent implements FindableContract {
use MagicFindableTrait;
}
class NonmagicalUser implements FindableContract {
public function find($id) {
return "User $id was found to be non-magical. Let's burn him anyway.";
}
}
print (new User)->find(123);
print (new NonmagicalUser)->find(321);
No this will not work. While __call() is really nice for a dynamic coding style it's disadvantages are that you cannot enforce the signatures of the dynamic methods in an interface and you won't get an automated documentation for it.
But I think if you are at a point where you want to create an interface for those methods, there should be no need to use __call() anymore. I would just hardcode the methods.