Laravel can't instantiate interface via __construct (using App::bind) - php

I am trying to resolve class via __construct using Laravel's bind() method.
Here what I do:
routes.php (of course I will move it away from here)
// Bindings
App::bind(
'License\Services\ModuleSelector\SelectorInterface',
'License\Services\ModuleSelector\ModuleSelector'
);
SelectorInterface.php - interface that I will expect in __construct method.
<?php namespace License\Services\ModuleSelector;
interface SelectorInterface {
/**
* Simply return query that will select needle module fields
*
* #return mixed
*/
public function make();
}
ModuleSelector.php - this is class that I want to resolve via Laravel's DI (see example below).
<?php namespace License\Services\ModuleSelector;
use License\Services\ModuleSelector\Selector;
class ModuleSelector extends Selector
{
/**
* Get module by it's code
*
* #return mixed
*/
public function find()
{
return $this->make()
->where('code', $module_code)
->first();
}
}
Module.php
<?php namespace License\Services\ModuleType;
use License\Services\ModuleType\TypeInterface;
use License\Services\ModuleSelector\SelectorInterface;
class Module
{
...
function __construct(SelectorInterface $selector)
{
$this->selector = $selector;
}
...
}
And the place when error occurs:
In my repo I have use License\Services\ModuleType\Module as ModuleService;.
Than there is method called find():
/**
* Find module by its code with all data (types, selected type)
* #return mixed
*/
public function find($module_code)
{
$module = new ModuleService;
// Get module id in order to use build in relations in framework
$module = $this->module->find($module_code);
...
}
So, in other words, I have 2 classes and one interface. What I am trying to do is:
1) Create Class1.php / Class2.php / Class2Interface.php.
2) In Class1.php in the __construct I specify __construct(Class2Interface $class2).
3) Instantiate Class2.
What I am doing wrong? Examples found here.

In this line:
$module = new ModuleService;
You are directly invoking the Module class and not passing in an instance of SelectorInterface.
For the IoC to work you bind and make classes using it. Try that line again with :
$module = App::make('License\Services\ModuleSelector\SelectorInterface');
An alernative is to inject it directly into your repos constructor, as long as the repo is created by the IoC container, your concrete will be automatically injected.

Nowhere do you have a class marked to actually "implement SelectorInterface".

Related

Symfony3 repository method doesn't work

I'm trying to call a method in the repository ArticleRepository from my controller ArticleController. However it says :
Undefined method 'afficheArticle'. The method name must start with either findBy or findOneBy!
my Entity Article : (Entity\Article.php)
/**
* Article
*
* #ORM\Table(name="Article", indexes={#ORM\Index(name="I_FK_Article_TypeArticle", columns={"idTypeArticle"})})
* #ORM\Entity(repositoryClass="erp-gkeep\new_erp\gkeepBundle\Repository\ArticleRepository")
*/
class Article
{
my ArticleController (Controller\ArticleController)
/**
* #Route("viewArticle2", name="viewArticle2")
*/
public function listAction2()
{
$data = $this->getDoctrine()->getRepository('gkeepBundle:Article')->afficheArticle();
my ArticleRepository
<?php
/**
* Created by PhpStorm.
*/
namespace gkeepBundle\Repository;
use Doctrine\ORM\EntityRepository;
class ArticleRepository extends EntityRepository
{
public function afficheArticle(){
$em=$this->getEntityManager();
$query = $em->createQuery(
'SELECT a.reference, a.designationfr, a.designationen, a.plan, a.url, a.datecreation, a.idtypearticle
FROM gkeepBundle:Article a
'
);
$articles = $query->getArrayResult();
return $articles;
}
}
if someone can help me please ! I'm pretty sure it's a stupid error :/
The value of repositoryClass in your mapping annotation needs to be the namespace + class name of your repository, it looks like you've maybe added some of the directory structure as well. - actually isn't a valid character in a PHP namespace, so the value you've got now definitely isn't right.
Try
#ORM\Entity(repositoryClass="gkeepBundle\Repository\ArticleRepository")
For reference: when this classname is not valid, Doctrine falls back to the default repository - this is what's throwing the error you're seeing.

Why does laravel IoC does not provisioning my class with my method?

I can't get why laravel tries to create my class itself, without using my method. I can see that IoC binding is executed (POINT 1 is shown). But singleton method is being never executed. Why?
In my service provider (not deferred):
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
echo "POINT 1"; // I can see this one
$this->app->singleton(\App\Services\FooBar::class, function($app)
{
echo "POINT 2\n"; // Does not comes here
return new FooBar($params);
});
}
I try to use typehinting to resolve dependencies when creating a class:
class Test
{
public function __construct(FooBar $fooBar)
{
}
}
I see that laravel tries to create FooBar to inject it, but can't resolve FooBar's dependencies. They could be resolved, if laravel would call service provider callback, but it does not. Why? How to make laravel use that callback for that class?
Instead of closure (that will not work), use boot() method to initiate your service.
/**
* #param \App\Services\FooBar $foobar
*/
public function boot(\App\Services\FooBar $foobar)
{
$foobar->setOptions(['option' => 'value']);
}
It will launch right after service will be instantiated.
It is because when you are binding a class to IoC container you are not immediately calling the closure. Instead when you need to actually do some action on your class from container you call App::make('class') which would fire the closure and give you the value that was returned from it. So for example
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
echo "POINT 1"; // I can see this one
$this->app->singleton(\App\Services\FooBar::class, function($app)
{
echo "POINT 2\n"; // Does not comes here
return new FooBar($params);
});
$this->app->make(\App\Services\FooBar::class); //here POINT 2 will be called first.
}

Laravel dependancy injection for views

I'm trying to get the hang of dependancy injection and the IoC container in Laravel.
Currently I have a class full of static methods that I use in my views. E.g.
class Templatizer {
/**
* Generates a colored FontAwsome check or cross from a true/false argument
* #param boolean $bool
* #return string The HTML markup of the icon
*/
public static function boolicon($bool)
{
return $bool ? '<span class="fa fa-check text-success"></span>' : '<span class="fa fa-times text-danger"></span>';
}
}
I have composer autoload the class and in my view I can just go {{ Templatizer::boolicon($trueOrFalseValue) }}.
Clearly this is bad practice and I'd like to move away from using static methods. I presume the correct way is to inject an instance of Templatizer and use the methods something like {{ $templatizer->boolicon($v) }}`.
How would I structure this? Presumably I'd need to inject an instance of Templatizer into my controller via the constructor? e.g.
class PagesController extends BaseController {
protected $templatizer;
public function __construct(Templatizer $templatizer)
{
$this->templatizer = $templatizer;
}
}
And then for, say, the method for the index page I pass the instance to the view? e.g.
# inside PagesController
public function index()
{
return View::make('pages.index', ['templatizer' => $this->templatizer]);
}
If this is correct, where is an appropriate place to put my Templatizer class? How would I bind it to the IoC container?
First of all I don't see anything wrong with calling these methods statically. It looks like this is just a HTML helper class of your own and you should be fine.
If you decide to go with Dependency Injection the "correct" way to register your class would be using Service Providers.
Assigning it to the views the way you wrote would work but you can also get it once you need it this way:
$templatizer = App::make('Yournamespace\Templatizer');
And finally probably the best solution in your case would be building your own Facade.
Yes you should inject it via the controller constructor, the files can live anywhere you like as long as they are autoloaded in.
I like to create a folder in the root directory called src so that my composer.json file looks like:
"autoload": {
"classmap": [
....
],
"psr-4": {
"Foo\\": "src/"
}
}
Then you can have src/Templatizer.php which would look like:
<?php namespace Foo;
class Templatizer {
}
Now you just need a service provider to bind your instance of Templatizer (this basically makes Laravel aware of your class and allows you to inject it into your controllers) in src/FooServiceProvider.php
<?php namespace Foo;
use Illuminate\Support\ServiceProvider;
class FooServiceProvider extends ServiceProvider {
/**
* Indicates if loading of the provider is deferred
*
* #var boolean
*/
protected $defer = false;
/**
* Register the service provider
*/
public function register() {
$this->app->bind('Foo\Templatizer', function($app) {
return new Templatizer();
});
}
}
Don't forget to add Foo\FooServiceProvider to the providers array in app config and you should be all set to do...
public function __construct(Foo\Templatizer $templatizer) {
You are also create instance on BaseController.
class BaseController extends Controller {
/**
* Setup the layout used by the controller.
*
* #return void
*/
var $templatizer;
protected function setupLayout()
{
if ( ! is_null($this->layout))
{
$this->layout = View::make($this->layout);
$templatizer = new \Templatizer();
View::share('templatizer', $templatizer);
}
}
}
use this $templatizer instance in a all view.
something like {{ $templatizer->boolicon($v) }}`.

Laravel ioc automatic resolution - works from controller but not from custom class

Namespaces omitted for brevity...
I have written the following service provider and registered in config/app.php:
class OfferServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerLossControlManager();
}
protected function registerLossControlManager()
{
$this->app->bind('LossControlInterface', 'LossControl');
}
}
Here is my LossControlInterface
interface LossControlInterface
{
/**
* #param int $demandId
* #param float $offerTotal
* #param float $productTotal
* #param null|int $partnerId
* #return mixed
*/
public function make($demandId, $offerTotal, $productTotal, $partnerId = null);
/**
* #return float
*/
public function getAcceptableLoss();
/**
* #return bool
*/
public function isAcceptable();
/**
* #return bool
*/
public function isUnacceptable();
/**
* #return null
*/
public function reject();
}
Now within the controller, I can inject the LossController as follows:
use LossControlInterface as LossControl;
class HomeController extends BaseController {
public function __construct(LossControl $lossControl)
{
$this->lossControl = $lossControl;
}
public function getLossThresholds()
{
$lossControl = $this->lossControl->make(985, 1000, null);
var_dump('Acceptable Loss: ' . $lossControl->getAcceptableLoss());
var_dump('Actual Loss: ' . $lossControl->calculateLoss());
var_dump('Acceptable? ' . $lossControl->isAcceptable());
}
}
However if I try to dependency inject the LossControlInterface from within a custom class called by a command:
[2014-09-02 13:09:52] development.ERROR: exception 'ErrorException' with message 'Argument 11 passed to Offer::__construct() must be an instance of LossControlInterface, none given, called in /home/vagrant/Code/.../ProcessOffer.php on line 44 and defined' in /home/vagrant/Code/.../Offer.php:79
It appears as though I am unable to dependency inject the interface into a custom class, but I can when dependency injecting into a controller.
Any thoughts on what Im doing wrong or have omitted to get the automatic resolution working?
The IoC is automatic within controllers, and you don't see the injection because Laravel handles the construction of controllers for you. When creating any other custom class by using the new keyword, you will still need to send in all of the parameters needed to it's constructor:
$myClass = new ClassWithDependency( app()->make('Dependency') );
You can hide this, to a degree, by funneling creation of your custom class through a service provider:
// Your service provider
public function register()
{
$this->app->bind('ClassWithDependency', function($app) {
return new ClassWithDependency( $app->make('Dependency') );
});
}
Then just have the IoC make it whenever you need it:
$myClass = app()->make('ClassWithDepenency');
In your case, you can change your code to look like this:
private function setOffer(Offer $offer = null) {
$this->processOffer = $offer ?:
new Offer( app()->make('LossControlInterface') );
}
A perhaps cleaner approach could be to create a service provider and an OfferFactory which gets injected into your controller. The controller can then request the factory to create the offer whenever it needs one:
// Controller
public function __construct(OfferFactory $offerFactory)
{
$this->offerFactory = $offerFactory;
}
public function setOffer(Offer $offer = null)
{
$this->processOffer = $offer ?: $this->offerFactory->createOffer();
}
// OfferFactory
class OfferFactory
{
public function createOffer()
{
return app()->make('Offer');
}
}
This has the benefit of completely decoupling your controller from the logic behind the creation of the offer, yet allowing you to have a spot to add any amount of complexity necessary to the process of creating offers.
In Laravel 5.2 the simplest solution for your particular problem would be to replace
new Offer();
with
App::make('Offer');
or even shorter
app('Offer');
which will use Laravel Container to take care of dependencies.
If however you want to pass additional parameters to the Offer constructor it is necessary to bind it in your service provider
App::bind('Offer', function($app, $args) {
return new Offer($app->make('LossControl'), $args);
});
And voila, now you can write
app('Offer', [123, 456]);
In laravel 5.4 (https://github.com/laravel/framework/pull/18271) you need to use the new makeWith method of the IoC container.
App::makeWith( 'App\MyNameSpace\MyClass', [ $id ] );
if you still use 5.3 or below, the above answers will work.

How can I replace the PhpRenderer in a ZF2 application

I have extended the PhpRenderer class in my ZF2 application like this:
namespace MyLib\View\Renderer;
class PhpRenderer extends \Zend\View\Renderer\PhpRenderer
{
}
I don't want to add a new rendering strategy, I just extend the PhpRenderer to add some #method phpdoc for my viewhelpers.
How can I replace the standard PhpRenderer with my extended PhpRenderer so it will be used to render my viewscripts?
The php renderer is a service inside the service manager. You can override this service directly or do it via the view manager (which instantiates and configures the renderer).
Override the service
In your module you define an onBootstrap() method. The "old" php renderer is already registered, you have to redefine it.
public function onBootstrap($e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$old = $sm->get('ViewRenderer');
$new = new MyCustomViewRenderer;
$new->setHelperPluginManager($old->getHelperPluginManager());
$new->setResolver($old->getResolver());
$sm->setAllowOverride(true);
$sm->setService('ViewRenderer', $new);
$sm->setAllowOverride(false);
}
Override the view manager
There is an alternative where you can redefine the view manager where the php renderer is instantiated. You have to redefine the view manager's factory for this:
In your application.config.php (note it is the application config, as the module config will not work here!)
service_manager => array(
'factories' => array(
'HttpViewManager' => 'MyModule\Service\HttpViewManagerFactory',
),
);
Then create your MyModule\Service\HttpViewManagerFactory:
use MyModule\View\Http\ViewManager as HttpViewManager;
class HttpViewManagerFactory implements FactoryInterface
{
/**
* Create and return a view manager for the HTTP environment
*
* #param ServiceLocatorInterface $serviceLocator
* #return HttpViewManager
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new HttpViewManager();
}
}
And then you can finally update the factory of the php renderer itself:
use Zend\Mvc\View\Http\ViewManager as BaseViewManager;
class ViewManager extends BaseViewManager
{
public function getRenderer()
{
if ($this->renderer) {
return $this->renderer;
}
$this->renderer = new MyCustomViewPhpRenderer;
$this->renderer->setHelperPluginManager($this->getHelperManager());
$this->renderer->setResolver($this->getResolver());
$model = $this->getViewModel();
$modelHelper = $this->renderer->plugin('view_model');
$modelHelper->setRoot($model);
$this->services->setService('ViewRenderer', $this->renderer);
$this->services->setAlias('Zend\View\Renderer\PhpRenderer', 'ViewRenderer');
$this->services->setAlias('Zend\View\Renderer\RendererInterface', 'ViewRenderer');
return $this->renderer;
}
}
Conclusion
The first method instantiates the normal php renderer already, so you instantiate two of them and replace the default with your own.
An alternative is to circumvent the instantiation of the default Zend's php renderer, but you have to do this inside the view manager class. The problem here is you have to redefine the factory for the view manager as well. This sounds as a detour, but it is the only way to get this done.
If all your custom class contains is #method declarations then you don't need to replace the php renderer class. Just make sure to use the #var docblock and your IDE will know what to do:
Document the type for the $this variable in your view files:
<!-- in a view file -->
<?php /* #var $this MyLib\View\Renderer\PhpRenderer */ ?>
<?= $this->myCustomViewHelper() ?>
Document individual variables or properties for view helpers, classes, etc:
class SomeHelper extends AbstractHelper
{
/** #var \MyLib\View\Renderer\PhpRenderer */
protected $view;
public function __invoke()
{
$this->view->myCustomViewHelper();
}
}

Categories