I want to use dependency injection to pass an instance of Plates to my controllers with PHP-DI that is integrated with my routing system Simple Router.
I've tried to inject an instance of Plates, but I get this error:
<?php
namespace Controllers;
use \League\Plates\Engine;
use \League\Plates\Template\Template;
use \League\Plates\Extension\Asset;
class Controller {
public function __construct(\League\Plates\Engine $templates)
{
$this->templates = $templates;
}
?>
Uncaught LogicException: The template name "home" is not valid. The default directory has not been defined
How I can solve this issue? I need also to pass the assets path with the asset() method. Any help will be appreciated.
UPDATE
Thanks to the help of jcHache I've managed the injection of a Plates instance inside my base controller with this DI code:
<?php
// config.php
return [
League\Plates\Engine::class => DI\create()
->constructor(TEMPLATE_ROOT)
->method('loadExtension', DI\get('League\Plates\Extension\Asset')),
League\Plates\Extension\Asset::class => DI\create()
->constructor(APP_ROOT),
];
index.php file
<?php
use Pecee\SimpleRouter\SimpleRouter;
use DI\ContainerBuilder;
$container = (new \DI\ContainerBuilder())
->useAutowiring(true)
->addDefinitions('config.php')
->build();
SimpleRouter::enableDependencyInjection($container);
This is great but I'm facing a problem and I can't find a fix for it.
I get this error that is relative to the assets loader of plates, it seems that it's instantiated more than once. I've extended my controllers with my base controller where the asset loader is instantiated, but I don't think is this the problem? Is there a fix?
Uncaught Pecee\SimpleRouter\Exceptions\NotFoundHttpException: The template function name "asset" is already registered
Plates engine factory require a view folder parameter (see Plates doc):
so you have to add this creation in your PHP-DI configuration file:
For Plates V4:
// config.php
return [
// ...
\League\Plates\Engine::class => function(){
return League\Plates\Engine::create('/path/to/templates', 'phtml');
},
];
For Plates V3, I'll try:
// config.php
return [
// ...
\League\Plates\Engine::class => function(){
return new League\Plates\Engine('/path/to/templates');
},
];
or
// config.php
return [
// ...
\League\Plates\Engine::class => DI\create()
->constructor('/path/to/templates')
,
];
Design Note:
Personally, I won't use dependency injection for a template engine, I think it would be better to instantiate Plates engine in a base controller class.
namespace controllers;
use League\Plates\Engine;
abstract class BaseController
{
/**
* #var \League\Plates\Engine
*/
protected $templates;
public function __construct()
{
$this->templates=new Engine(\TEMPLATE_ROOT);
$this->templates->loadExtension(new \League\Plates\Extension\Asset(\APP_ROOT));
}
protected function renderView(string $viewname, array $variables=[])
{
return $this->templates->render($viewname,$variables);
}
}
For a child controller using Plates:
namespace controllers;
class MyController extends BaseController
{
public function index()
{
return $this->renderView('home');
}
}
Related
I want to define some methods which can be used in multiple place or multiple controllers. Basically these methods will be like libraries which will perform multiple queries.
My main aim is to avoid writing common logic multiple times by creating some libraries.
Please help me with it.
Thanks in advance :)
Depends what are you trying to do. Here are some options:
By default all your controllers extend App\Http\Controllers\Controller class. Just put all the shared logic between controllers there.
For complex queries to the database you can create a Repository and and inject in the controllers.
class UserRepository {
public function getActiveUsers() {
return Users::with('role')
->where('...')
->someQueryScopes()
->anotherQueryScope()
->yetAnotherScope();
}
}
class SomeController extends Controller {
public function index(UserRepository $repository) {
$users = $repository->getActiveUsers();
return view('users.index')->withUsers($users);
}
}
Yet another option is to create a Service classes for business logic and inject them in the constructor or relevant methods
class UserCreatorService {
public function create($email, $password){
$user = User::create(['email' => $email, 'password' => $password]);
$user->addRole('Subscriber');
Event::fire(new UserWasCreated($user));
return $user;
}
}
class RegisterController extends Controller {
public function store(Request $request, UserCreatorService $service) {
$user = $service->create($request->input('email'), $request->input('password'));
return view('home')->withUser($user);
}
}
it's simple, build your own library in your app folder then create new file MyLibrary.php
namespace App;
Class MyLibrary {
public static function sum($a, $b) {
return $a + $b;
}
}
then create alias in your config/app.php
'MyLibrary' => App\MyLibrary::class,
and finally you can call it in anywhere your controller
$result = MyLibrary::sum(4, 5); // your $result now have value of 9
You can make a folder named lib and inside that a functions.php file and in composer.json
...
"autoload": {
"files": [
"app/lib/functions.php"
],
...
and run composer dump-autoload
Use helper classes and create common functions in them.
Create a folder named helper in app folder and create a helper class in it. And then use that helper class's function in multiple controller or views as you want.
I'm building an application, now i'm created a helper
class Students{
public static function return_student_names()
{
$_only_student_first_name = array('a','b','c');
return $_only_student_first_name;
}
}
now i'm unable to do something like this in controller
namespace App\Http\Controllers;
class WelcomeController extends Controller
{
public function index()
{
return view('student/homepage');
}
public function StudentData($first_name = null)
{
/* ********** unable to perform this action *********/
$students = Student::return_student_names();
/* ********** unable to perform this action *********/
}
}
this is my helper service provider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class HelperServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
foreach(glob(app_path().'/Helpers/*.php') as $filename){
require_once($filename);
}
}
}
i event added it as an alias in config/app.php file
'Student' => App\Helpers\Students::class,
Try putting use App\Helpers\Student; at the top of your controller beneath the namespace delcaration:
namespace App\Http\Controllers;
use App\Helpers\Student;
class WelcomeController extends Controller
{
// ...
Look more into PHP namespaces and how they are used, I believe you may have a deficient understanding about them. Their only purpose is to make so you can name and use two classes with the same name (e.g. App\Helpers\Student vs maybe App\Models\Student). If you needed to use both of those classes inside of the same source file, you can alias one of them like this:
use App\Helpers\Student;
use App\Models\Student as StudentModel;
// Will create an instance of App\Helpers\Student
$student = new Student();
// Will create an instance of App\Models\Student
$student2 = new StudentModel();
You do not need to have a service provider for this, just the normal language features. What you would need a service provider for is if you wanted to defer the construction of your Student object to the IoC:
public function register()
{
$app->bind('App\Helpers\Student', function() {
return new \App\Helpers\Student;
});
}
// ...
$student = app()->make('App\Helpers\Student');
You should never have to include or require a class file in laravel because that is one of the functions that composer provides.
You do not need a service provider to make it works. Just lets the Students class as you did:
class Students{
public static function return_student_names()
{
$_only_student_first_name = array('a','b','c');
return $_only_student_first_name;
}
}
all its methods should be static
You added the Facade correctly:
'Student' => App\Helpers\Students::class,
Finally, looks like your problem is caused by forgetting a backslash at facade name. Uses \Students instead of Students:
public function StudentData($first_name = null)
{
$students = \Student::return_student_names();
}
When using a facade, it is not necessary makes nay include, the facades were made to avoid complex includes in everywhere.
I used the following tutorial to get an idea about interfaces:
http://vegibit.com/what-is-a-laravel-interface/
But I wanted to change the directory of where I am putting my interfaces to "App/Models/Interfaces". And so I did. But now I cannot get it to work anymore. Here is my code:
Routes.php
App::bind('CarInterface', 'Subaru');
Route::get('subaru', function()
{
$car = App::make('CarInterface');
$car->start();
$car->gas();
$car->brake();
});
Model Subaru.php
<?php
use App\Models\Interfaces\CarInterface;
class Subaru implements CarInterface {
..etc
Interface CarInterface
<?php namespace App\Models\Interfaces;
interface CarInterface {
public function start();
public function gas();
public function brake();
}
I added this in my composer.json:
"psr-0": {
"Interfaces": "app/models/interfaces"
}
And I even added this in my start/global.php file:
ClassLoader::addDirectories(array(
app_path().'/models/interfaces',
In my recent laravel 5 project, I'm used to prepare my logics as Repository method.
So here's my current directory structure. For example we have 'Car'.
So first I just create directory call it libs under app directory and loaded it to composer.json
"autoload": {
"classmap": [
"database",
"app/libs" //this is the new changes (remove this comment)
]
}
after that I create a subfolder call it Car . Under the Car folder create two file 'CarEloquent.php' for eloquent implementation and CarInterface.php as interface.
CarInterface
namespace App\libs\Car;
interface CarInterface {
public function getAll();
public function create(array $data);
public function delete($id);
public function getByID($id);
public function update($id,array $data);
}
CarEloquent
namespace App\lib\Car;
use App\lib\Car\CarInterface;
use App\Car; //car model
class CarEloquent implements CarInterface {
protected $car;
function __construct(Car $a) {
$this->car = $a;
}
public function getAll(){
return $this->car->all();
}
}
Then create Car Service Provider to bind ioc controller.
For create Car service provider you can also use php artisan command by laravel.
php artisan make:provider CarServiceProvider
ServiceProvider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class CarServiceProvider extends ServiceProvider {
public function register() {
$this->app->bind('App\lib\Car\CarInterface', 'App\lib\Car\CarEloquent');
}
}
And final step would be add these service provider to config/app.php provider array.
'providers' => [
'App\Providers\CatServiceProvider',
]
And finally we are ready to use our repository method in our controller.
Example Controller
namespace App\Http\Controllers;
use App\lib\Car\CarInterface as Car;
class CarController extends Controller {
protected $carObject;
public function __construct(Car $c) {
$this->carObject = $c;
}
public function getIndex(){
$cars = $this->carObject->getAll();
return view('cars.index')->with('cars',$cars);
}
}
Main purpose to achieve here call repository method to controller, however you need use them as per your requirement.
Update
CarEloqent basically help us to improve database implementation, for example in future if you want to implement same functionality for other database like redis you just add another class CarRedis and change implementation file path from server provider.
Update 1: Good Resource
http://programmingarehard.com/2014/03/12/what-to-return-from-repositories.html
[book] From Apprentice to Artisan by Taylor Otwell
Very good explanation about repository method and software design principle commonly called separation of concerns. You should read this book.
If you still have any confusion to achieve these behaviors let me know and however I will keep eye on this question to update this answer, if I find some things to change or update or as per requirement.
I'm creating an abstract class that will grab the contents of a view using Laravel's View class. But I'm getting the following error when trying to run a method from a class that extends it:
Illuminate \ Container \ BindingResolutionException
Target [Illuminate\View\Engines\EngineInterface] is not instantiable.
Here's my code:
PdfReport.php
use Illuminate\View\View as View;
abstract class PdfReport {
private $view;
function __construct(View $view)
{
$this->view = $view;
}
public function render($reportView, $report)
{
$this->view->make('report.pdf.' . $reportView, ['report' => $report])->render();
}
}
EslReport.php
<?php namespace Reports\PdfReports;
class EslPdfReport extends PdfReport {
public function renderReport($report)
{
return $this->render('esl', $report);
}
}
Then I'm running my code in routes.php for testing purposes as follows:
use Reports\PdfReports\EslPdfReport;
Route::get('pdftest', array(
'as' => 'pdftest',
function(){
$eslReport = App::make('Reports\PdfReports\EslPdfReport');
$eslReport->renderReport(EslReport::find(1));
}
));
I'm not quite understanding if I'm doing something wrong with the dependency injection for the view in the abstract class, it's all pretty new concepts to me, so any help would be most appreciated.
Also I asked this question on laracasts forum if it helps: https://laracasts.com/discuss/channels/general-discussion/confusion-about-constructors-in-abstract-classes
Instead of Illuminate\View\View you need to inject Illuminate\View\Factory:
use Illuminate\View\Factory as View;
Here's a reference of facade classes and there actual underlying class you need to use when working with DI
I need to write a view helper that gets a service and do something with it. I successfully implemented the view helper to have access to the service locator. The problem is that the service I want to get is not being found through the service locator when the __invoke method is called.
The view helper code:
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper,
Zend\ServiceManager\ServiceLocatorAwareInterface,
Application\Model;
class LoggedCustomer extends AbstractHelper implements ServiceLocatorAwareInterface
{
use \Zend\ServiceManager\ServiceLocatorAwareTrait;
public function __invoke()
{
$model = new Model\Customer($this->getServiceLocator());
return $model->getCurrent();
}
}
A snippet of the model code:
namespace Application\Model;
use Application\Entity,
Andreatta\Model\Base as Base;
class Customer extends Base
{
/**
*
* #return Zend\Authentication\AuthenticationService
*/
public function getAuthService()
{
$serviceLocator = $this->getServiceLocator();
return $serviceLocator->get('Application\Auth');
}
/**
*
* #return Zend\Authentication\Adapter\AdapterInterface
*/
protected function getAuthAdapter()
{
return $this->getAuthService()->getAdapter();
}
public function getCurrent()
{
$authService = $this->getAuthService();
if ($authService->hasIdentity())
return $authService->getIdentity();
return null;
}
The snippet from module.config.php:
'service_manager' => array
(
'factories' => array
(
'Application\Auth' => function($sm)
{
$authService = $sm->get('doctrine.authenticationservice.application');
$authService->setStorage( new \Zend\Authentication\Storage\Session('Application\Auth'));
return $authService;
},
),
),
'view_helpers' => array
(
'invokables' => array
(
'loggedCustomer' => 'Application\View\Helper\LoggedCustomer',
),
),
When calling the view helper from any view I get the following:
Zend\View\HelperPluginManager::get was unable to fetch or create an instance for Application\Auth
The weird is that the application is functioning correctly (i.e. this service is being normally used by other parts of the application).
EDIT:
I did some research and I think the only services that I can access through the service manager inside the view helper are the ones registered inside the 'view_manager' section of module.config.php. Does anyone have an idea of how to access the other services?
$this->getServiceLocator() in view helper can only get u other view helpers you need to use $this->getServiceLocator()->getServiceLocator() to get the application services
#rafaame: I find a simple way to access service locator in view Helper
We just use:
$this->getView()->getHelperPluginManager()->getServiceLocator();
to get a service locator
A sample view Helper:
namespace Tmcore\View\Helper;
use Zend\View\Helper\AbstractHelper;
class Resource extends AbstractHelper
{
public function adminResource()
{
$sm = $this->getView()->getHelperPluginManager()->getServiceLocator();
$adminConfig = $sm->get('ModuleManager')->loadModule('admin')->getConfig();
return $adminConfig;
}
}
I guess you are retrieving the Zend\View\HelperPluginManager instead of the correct ServiceManager.
Probably you are not injecting it as you should.
That makes sense if thats your complete LoggedCustomer code, since you are not saving the SM. As far as I know, if you implement the ServiceLocatorAwareInterface the SM will be injected, but you have to handle it.
UPDATE:
sorry, i didnt realize you had ServiceLocatorAwareTrait; thats the same.
But, reading http://framework.zend.com/manual/2.0/en/modules/zend.service-manager.quick-start.html
i see
By default, the Zend Framework MVC registers an initializer that will inject the ServiceManager instance, which is an implementation of
Zend\ServiceManager\ServiceLocatorInterface, into any class
implementing Zend\ServiceManager\ServiceLocatorAwareInterface. A
simple implementation looks like the following.
So, the service manager is only being injected ... if you implement ServiceLocatorAwareInterface in a controller.
So, you should manually inject the service manager.
for that, what i use to do is to create a factory in Module.php, instead of creating the invokable in the config. for that you implement this function:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'loggedCustomer' => function($sm) {
$vh = new View\Helper\LoggedCustomer();
$vh->setServiceLocator($sm->getServiceLocator());
return $vh;
}
);
}
Also, i wont have the view helper implementing ServiceLocatorAwareInterface, so nothing else is automaticaly injected.
And with this it will work
It appears that the service manager that is injected into the view helper has only the services that are registered within the section 'view_manager' of module configs.
It is possible to inject the "main" service manager by registering the view helper as a factory like this:
'view_helpers' =>
[
'factories' =>
[
'loggedCustomer' => function($pluginManager)
{
$serviceLocator = $pluginManager->getServiceLocator();
$viewHelper = new View\Helper\LoggedCustomer();
$viewHelper->setServiceLocator($serviceLocator);
return $viewHelper;
},
]
],
But you have to make sure that you treat it in setServiceLocator method in the view helper. Otherwise the "limited" service manager will be injected into the view helper later on. Like this:
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
if($this->serviceLocator !== null)
return $this;
$this->serviceLocator = $serviceLocator;
return $this;
}
It fixes the problem, but it appears to be a tremendous hack to me.
In view helpers, if you want to access application services then use
$this->getServiceLocator()->getServiceLocator()