I tried to get slim with PHP-DI and Autowiring working, without success. Maybe I have a wrong understanding of autowiring.
I setup a new project and created the following Index.php:
require __DIR__ . '/../vendor/autoload.php';
use userservice\core\services\UserServiceInterface;
use userservice\infrastructure\services\UserService;
use userservice\webservice\controller\UsersController;
use DI\Container;
$app = \DI\Bridge\Slim\Bridge::create();
$container = $app->getContainer();
$container->set(UserServiceInterface::class, UserService::class);
$container->set(UsersController::class, UsersController::class);
$app->get('/user', [UsersController::class, 'get']);
$app->run();
The UsersController
namespace userservice\webservice\controller;
use userservice\core\services\UserServiceInterface;
class UsersController{
/**
*
* #var UserServiceInterface
*/
private $userService;
public function __construct(UserServiceInterface $userService) {
$this->userService = $userService;
}
public function get(\Psr\Http\Message\ResponseInterface $response, \Psr\Http\Message\RequestInterface $request){
//$user = $this->userService->get();
$response->getBody()->write("Test");
return $response;
}
}
In the example above I get the following error-message:
Non-static method userservice\webservice\controller\UsersController::get() should not be called statically in
I found a working solution how I can use PHP-DI autowiring with mapping to interfaces:
require __DIR__ . '/../vendor/autoload.php';
use userservice\core\services\UserServiceInterface;
use userservice\infrastructure\services\UserService;
use userservice\webservice\controller\UsersController;
use userservice\core\repositories\UserRepositoryInterface;
use userservice\infrastructure\repositories\UserRepository;
/**
* Mapping of classes to interfaces
*/
$definitions = [
//REPOSITORIES
UserRepositoryInterface::class => DI\get(UserRepository::class),
//SERVICES
UserServiceInterface::class => DI\get(UserService::class),
];
$builder = new DI\ContainerBuilder();
$builder->addDefinitions($definitions);
$container = $builder->build();
$app = \DI\Bridge\Slim\Bridge::create($container);
$app->get('/user', [UsersController::class, 'get']);
$app->run();
Related
There is a custom project in which I decided to use the DI container from Laravel, here is the code:
bootstrap:
<?php
use Illuminate\Container\Container;
use App\Providers\AppServiceProvider;
require_once __DIR__ . '/../vendor/autoload.php';
$container = new Container;
$appServiceProvider = new AppServiceProvider($container);
$appServiceProvider->register();
return $container;
AppServiceProvider:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(CallbackApplication::class);
$this->app->bind(SMSSubscriptionService::class);
$this->app->bind(HttpClient::class);
$this->app->bind(DriverMysqli::class);
$this->app->bind(Response::class);
$this->app->bind(DatabaseInterface::class, function ($app) {
$db = $app->make(DriverMysqli::class);
$db->setConnectionConfig(DatabaseConfig::PARAMS['local']);
$db->initialize();
return $db;
});
$this->app->instance(Request::class, Request::capture());
}
public function boot()
{
}
}
Project entry point:
$container = require __DIR__ . '/../Core/bootstrap2.php';
/** #var CallbackApplication $controller */
$controller = $container->get(CallbackApplication::class);
$controller->init();
$controller->process();
$controller->save();
$controller->response();
I used PHP DI before (https://php-di.org/)
But now I decided to switch to the Laravel container
The PHP DI documentation says:
PHP-DI always creates objects only when they are requested or injected somewhere.
Do laravel containers also use lazy object initialization?
If I don't need to use for example the Request object in my project entry point, then it won't be initialized in the container?
I searched everywhere, I did not find the answer, can I somehow check this?
I am trying to access the $container from my middleware, but i am not getting much luck.
In my index.php file I have
require '../../vendor/autoload.php';
include '../bootstrap.php';
use somename\Middleware\Authentication as Authentication;
$app = new \Slim\App();
$container = $app->getContainer();
$app->add(new Authentication());
And then I have a class Authentication.php like this
namespace somename\Middleware;
class Authentication {
public function __invoke($request, $response, $next) {
$this->logger->addInfo('Hi from Authentication middleware');
but i get an error
Undefined property: somename\Middleware\Authentication::$logger in ***
I have also tried adding the following constructor to the class but I also get no joy.
private $container;
public function __construct($container) {
$this->container = $container;
}
Could anyone help please?
Best Practice to Middleware Implementation is Something like this :
Place this code inside your dependency section :
$app = new \Slim\App();
$container = $app->getContainer();
/** Container will be passed to your function automatically **/
$container['MyAuthenticator'] = function($c) {
return new somename\Middleware\Authentication($c);
};
then inside your Authentication class create constructor function like you mentioned :
namespace somename\Middleware;
class Authentication {
protected $container;
public function __invoke($request, $response, $next)
{
$this->container->logger->addInfo('Hi from Authentication middleware');
}
public function __construct($container) {
$this->container = $container;
}
/** Optional : Add __get magic method to easily use container
dependencies
without using the container name in code
so this code :
$this->container->logger->addInfo('Hi from Authentication middleware');
will be this :
$this->logger->addInfo('Hi from Authentication middleware');
**/
public function __get($property)
{
if ($this->container->{$property}) {
return $this->container->{$property};
}
}
}
After inside your index.php add Middleware using name resolution like this:
$app->add('MyAuthenticator');
I disagree with Ali Kaviani's Answer. When adding this PHP __magic function (__get), the code will be a lot more difficult to test.
All the required dependencies should be specified on the constructor.
The benefit is, that you can easily see what dependencies a class has and therefore only need to mock these classes in unit-tests, otherwise you would've to create a container in every test. Also Keep It Simple Stupid
I'll show that on the logger example:
class Authentication {
private $logger;
public function __construct($logger) {
$this->logger = $logger;
}
public function __invoke($request, $response, $next) {
$this->logger->addInfo('Hi from Authentication middleware');
}
}
Then add the middleware with the logger parameter to the container:
$app = new \Slim\App();
$container = $app->getContainer();
$container['MyAuthenticator'] = function($c) {
return new somename\Middleware\Authentication($c['logger']);
};
Note: the above registration to the container could be done automatically with using PHP-DI Slim (but that should be also slower).
I have a problem with DI, it's new for me. I would like to give the Container as DI in my Project class, but i got an error :
Argument 1 passed to Project::__construct() must be an instance of Slim\Container, none given
I created a class Project :
use Slim\Container;
class Project {
protected $ci;
public function __construct(Container $ci) {
$this->ci = $ci;
}
}
Here my DIC configuration :
//dependencies.php
$container = $app->getContainer();
$container[Project::class] = function ($c) {
return new Project($c);
};
And here my code index.php to run the code
// Instantiate the app
$settings = require __DIR__ . '/../app/configs/settings.php';
$app = new \Slim\App($settings);
// Set up dependencies
require __DIR__ . '/../app/configs/dependencies.php';
// Register middleware
require __DIR__ . '/../app/configs/middleware.php';
// Register routes
require __DIR__ . '/../app/configs/routes.php';
require __DIR__ . '/../app/model/Project.php';
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = new Project();
return $response;
});
$app->run();
I dont know where i failed, i even tried to use slim-brige, but i got the same result. I also tried to give a string instead of the the Container, and i still get null
Argument 1 passed to Project::__construct() must be an instance of Slim\Container, none given
Well, no argument given to the constructor indeed:
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = new Project(); // You should pass container instance here, but you don't
return $response;
});
Since you have registered your Project class in the container, you can get instance from the container:
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = $this->container->get('Project'); // Get Project instance from the container, you have already registered it
return $response;
});
I have the problem with doctrine console. I have a MailServiceFactory.php containing this code:
namespace Application\Service;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MailServiceFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$I_render = $serviceLocator->get('ViewRenderer');
$a_config = $serviceLocator->get('config');
return new MailService($I_render, $a_config);
}
}
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MailServiceFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$I_render = $serviceLocator->get('ViewRenderer');
$a_config = $serviceLocator->get('config');
return new MailService($I_render, $a_config);
}
}
and all functions works, but when i execute the vendor/bin/doctirne-module I get the error below:
Fatal error: Uncaught exception
'Zend\ServiceManager\Exception\ServiceNotFoundException' with message
'Zend\ServiceManager\ServiceManager::get was unable to fetch or create
an instance for ViewRenderer' in
/Users/Daniele/Apps/corradini.com/www/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:529
Why i'm getting this error?
You cannot get ViewRenderer directly from ServiceLocator on console requests because its not in stage since request is not an HttpRequest.
Instead of ViewRenderer, you can easily try to passing a PhpRenderer instance to your service and render a ViewModel using that. For example:
use Zend\View\Renderer\PhpRenderer;
class MailServiceFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$renderer = new PhpRenderer();
$a_config = $serviceLocator->get('config');
return new MailService($renderer, $a_config);
}
Now in your MailService:
use Zend\View\Renderer\RendererInterface;
use Zend\View\Model\ViewModel;
class MailService
{
protected $renderer;
public function __construct(RendererInterface $renderer, $config)
{
$this->renderer = $renderer;
// set your config etc..
}
public function fooMethod()
{
$model = new ViewModel();
$model->setTemplate('path/to/template.phtml');
$result = $this->renderer->render($model);
// The $result contains rendered html markup
}
Hope it helps.
Thanks foozy now doctrine console funciton! but the i can't read the tempalte for render mail.
My tempate is locatend in module/Application/view/email/template.phtml
In my module.config.php there is configuration:
'template_path_stack' => array(
'application' => __DIR__ . '/../view',
),
And error:
Zend\View\Renderer\PhpRenderer::render: Unable to render template "email/template.phtml"; resolver could not resolve to a file
where mistake ?
thanks
I had the same problem after I added a view_manager config to the console config. The solution is to just pull the renderer from the ViewManager:
$renderer = $serviceLocator->get('ViewManager')->getRenderer();
This will also register the ViewRenderer service, so any calls to get('ViewRenderer') after this should work too.
in ZF3 also a TemplateMapResolver can help you to fix some issues like:
"PhpRenderer::render: Unable to render template"
$resolver = new \Zend\View\Resolver\TemplateMapResolver();
$resolver->setMap([
'mailTemplate' => __DIR__ . '/../../view/email/testmail.phtml'
]);
$renderer->setResolver($resolver);
$viewContent = new \Zend\View\Model\ViewModel($contentArray);
$viewContent->setTemplate('mailTemplate');
Warning: This question is Laravel 4 specific.
I've been using Facades in my controllers before. Therefore I know the code is working. Now I need to introduce dependency injection for various reasons.
After refactoring the controller I get following error:
Illuminate \ Container \ BindingResolutionException
Unresolvable dependency resolving [Parameter #0 [ $name ]].
I can't figure out where the problem is. The Error message seems cryptic to me and I don't understand it. (I don't see any problem with my __constructor parameters since I've registered the binding for the HelpersInterface)
Here are the important parts of my code:
File: app/start/global.php
<?php
// ...
App::bind('Acme\Interfaces\HelpersInterface', 'Acme\Services\Helpers');
File: composer.json
// ...
"autoload": {
// ...
"psr-0": {
"Acme": "app/"
}
},
// ...
File: app/Acme/Controllers/BaseController.php
<?php namespace Acme\Controllers;
use Carbon\Carbon;
use Controller;
use Illuminate\Foundation\Application as App;
use Illuminate\View\Factory as View;
use Acme\Interfaces\HelpersInterface as Helpers;
use Illuminate\Http\Response;
class BaseController extends Controller {
/**
* #var \Illuminate\Foundation\Application
*/
private $app;
/**
* #var \Carbon\Carbon
*/
private $carbon;
/**
* #var \Illuminate\View\Factory
*/
private $view;
/**
* #var \Acme\Interfaces\HelpersInterface
*/
private $helpers;
function __construct(App $app, Carbon $carbon, View $view, Helpers $helpers)
{
$this->app = $app;
$this->carbon = $carbon;
$this->view = $view;
$this->helpers = $helpers;
$lang = $this->app->getLocale();
$now = $this->carbon->now();
$this->view->share('lang', $lang);
$this->view->share('now', $now);
}
/**
* Missing Method
*
* Abort the app and return a 404 response
*
* #param array $parameters
* #return Response
*/
public function missingMethod($parameters = array())
{
return $this->helpers->force404();
}
}
File: app/Acme/Services/Helpers.php
<?php namespace Acme\Services;
use Illuminate\Config\Repository as Config;
use Illuminate\Database\Connection as DB;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector as Redirect;
use Illuminate\Session\Store as Session;
use Illuminate\Support\Facades\Response;
use Illuminate\Translation\Translator as Lang;
use Illuminate\View\Factory as View;
use Acme\Interfaces\MockablyInterface;
use Monolog\Logger as Log;
class Helpers implements HelpersInterface {
// ...
public function __construct(
Config $config,
Lang $lang,
View $view,
MockablyInterface $mockably,
Log $log,
Request $request,
Session $session,
DB $db,
Redirect $redirect,
Response $response
) {
// ...
}
// ...
}
File: app/Acme/Providers/HelpersServiceProvider.php
<?php namespace Acme\Providers;
use Illuminate\Support\ServiceProvider;
use Acme\Services\Helpers;
class HelpersServiceProvider extends ServiceProvider {
private $db;
private $defaultDbConnection;
protected function init()
{
$this->db = $this->app['db'];
$this->defaultDbConnection = $this->db->getDefaultConnection();
}
public function register()
{
$this->init();
$this->app->bind('helpers', function ()
{
return new Helpers(
$this->app['config'],
$this->app['translator'],
$this->app['view'],
$this->app['mockably'],
$this->app->make('log')->getMonolog(),
$this->app['request'],
$this->app['session.store'],
$this->db->connection($this->defaultDbConnection),
$this->app['redirect'],
$this->app['Illuminate\Support\Facades\Response']
);
});
}
For me it was just a matter of running
php artisan optimize:clear
It seems your Acme\Services\Helpers constructor takes a $name parameter, but is not type hinted.
Laravel's IoC is not magic. If your don't provide a type hint for every parameter, the IoC container has no way of knowing what to pass in.
Make sure you use Illuminate\Http\Request; on top of the file instead of any other http import like this
use Illuminate\Http\Request;
THANK ME LATER!
Got it fixed. All the tutorials about dependency injection were referring to concrete implementations of interfaces so that I thought that's the way to go about it. Joseph Silber's answer got me on the right track.
The trick is to bind the Interface to the binding of the ServiceProvider like shown below. That way Laravel will know how to instantiate the Helpers service.
File: app/start/global.php
<?php
// ...
App::bind('Acme\Interfaces\HelpersInterface', 'helpers');