I'm familiar with how to use the Session in ZF2, e.g.
$user_session = new Container('user');
$user_session->username = 'JohnDoe';
This is fine, but if I'm trying to persist session data in one of my business logic services I'd strongly prefer to inject a session management object/service into my service's constructor, like in this pseudocode:
class BusinessSvc{
protected $sessionSvc;
function __construct($sessionSvc){
$this->sessionSvc = $sessionSvc;
}
public function doBusinessLayerStuff(){
... do stuff ...
$this->sessionSvc->store('lastOrderNumber', '1234');
... do stuff ...
}
}
I would think the framework would provide this functionality, but I can't find it anywhere. I could always write my own, but didn't want to reinvent the wheel.
The answer was a lot simpler than I realized. Once instantiated, a Container instance itself can be injected into the business service and provide it with access to the session. If using phpunit to later test the service, the object could be mocked with an array or an instance of ArrayObject.
In Module.php's getServiceConfig method:
'MyModule\Service\BusinessService' => function($sm) {
// Container doesn't need to use this name but it seems sensible.
$container = new Container('MyModule\Service\BusinessService');
return new Service\BusinessService($container);
},
Related
I have a command class with a dependency type-hinted in the constructor:
class RunAnalyticsCommand extends Command
{
public function __construct(Analytics $analytics)
{
//
}
}
The constructor for the Analytics class looks like this:
class Analytics
{
public function __construct(Odoo $odoo, ChannelInterface $channel)
{
//
}
}
In a service provider, I've instructed the application what to instantiate for the Odoo class. If I create an instance of Analytics like this, it works fine. It gets the Odoo instance from the container and uses the channel that I pass in.
$analytics = app(Analytics::class, ['channel' => new ChannelA]);
Now, I'm trying to use contextual binding to accomplish all of this "behind the scenes". I write the following in my service provider's register method:
$this->app->when(RunAnalyticsCommand::class)
->needs(Analytics::class)
->give(function () {
return app(Analytics::class, ['channel' => new ChannelA]);
});
However, now when I run the RunAnalyticsCommand, I get an error that Maximum function nesting level of '256' reached, aborting!
I'm assuming this happens because the give callback is trying to resolve the same Analytics class, and the container treats that call as if it was also coming from the RunAnalyticsCommand class, so it just keeps trying to resolve the same thing over and over.
Is this the expected behavior or a bug with contextual binding? Shouldn't the call to resolve the class from within the give callback not behave as if it were originating from the RunAnalyticsCommand? Is there some other way to tell the container to resolve without using the contextual binding?
I'm using Instagram API library to connect user to Instagram profile and then do smth with it. So, as Instagram API wiki says:
Once you have initialized the InstagramAPI class, you must login to an account.
$ig = new \InstagramAPI\Instagram();
$ig->login($username, $password); // Will resume if a previous session exists.
I have initialized InstagramAPI class and then I called $ig->login('username', 'password');. But I have to call it in every function where I need to work with Instagram.
So how could I save this object $ig for using it in the future in other controllers without calling login() any more? Can I save $ig object into the session or cookie file?
P.S. I think saving into the session is not safe way to solve the issue.
UPD: I was trying to save $ig object into the session, however the size is large and session become stop working as well.
Regarding the register method you asked in the comments section, all you need to create a new service provider class in your app\providers directory and declare the register method in there for example:
namespace App\Providers;
use InstagramAPI\Instagram;
use Illuminate\Support\ServiceProvider;
class InstagramServiceProvider extends ServiceProvider
{
public function register()
{
// Use singleton because, always you need the same instance
$this->app->singleton(Instagram::class, function ($app) {
return new Instagram();
});
}
}
Then, add your newly created InstagramServiceProvider class in providers array inside the config/app.php file for example:
'providers' => [
// Other ...
App\Providers\InstagramServiceProvider::class,
]
Now on, in any controller class, whenever you need the Instagram instance, all you need to call the App::make('InstagramAPI\Instagram') or simply call the global function app('InstagramAPI\Instagram') or even you can typehint the class in any method/constructor etc. Some examples:
$ig = App::make('InstagramAPI\Instagram');
$ig = App::make(Instagram::class); // if has use statement at the top fo the class
$ig = app('...');
In a class method as a dependency:
public function someMethod(Instagram $ig)
{
// You can use $ig here
}
Hope this helps but read the documentation properly, there will get everything documented.
I am confused as to how to use the cache in Laravel. I can either use the Cache facade and do something like …
use Cache;
public function someMethod()
{
Cache::remember('users', 60 , function() {
return DB::table('users')->get();
});
}
Or I could use something like this …
use Illuminate\Contracts\Cache\Repository;
protected $cache;
public function __construct(Repository $repository)
{
$this->cache = $repository;
}
public function someMethod()
{
$this->cache->remember('users', 60 , function() {
return DB::table('users')->get();
});
}
Both will implement the same method remember from vendor/laravel/framework/src/Illuminate/Cache/Repository.php
Repository class which implements vendor/laravel/framework/src/Illuminate/Contracts/Cache/Repository.php which I have included in the second method.
As per Laravel docs:
The Illuminate\Contracts\Cache\Factory and Illuminate\Contracts\Cache\Repository contracts provide access to Laravel's cache services. The Factory contract provides access to all cache drivers defined for your application. The Repository contract is typically an implementation of the default cache driver for your application as specified by your cache configuration file.
However, you may also use the Cache facade, which is what we will use throughout this documentation. The Cache facade provides convenient, terse access to the underlying implementations of the Laravel cache contracts.
So can I conclude that both the approach are same. The Cache Facade provides me a cleaner implementation, that's it.
You would get the same result in your application. It's the same but not the same.
In your second approach you are using dependency injection. Which makes it easier to write tests for your class. This way you get a better maintainable application.
Have a deeper look at dependency injection. Here is a nice article: Dependency Injection with Laravel’s IoC.
I need a way to load objects via IoC provider depending on a request parameter. Right now I'm loading my objects directly with App::make(xy, [$urlParamter] which I want to refactor, so that I can use dependency injection the way it is supposed to. To describe my current architecture I need to show you quiet some information and in the end you find my concrete questions I have about it.
I'm building up a general CMS framework which provides an import architecture that is extendable with a custom import implementation.
Now I'm struggling with properly loading the concrete classes via IoC container, because they always depend on the selected import.
To dig into my problem, here is my entry point in routes.php
Route::get('/import', ['as' => 'overview', 'uses' => '\CMSFramework\Http\Controllers\Import\ImportController#index']);
This generates a view where the user selects a concrete import to be triggered. After selecting a concrete import, the user should get individual views to prepare the appropriate import (i.e. Upload a CSV file, select an area to import, etc.)
In my concept an import implementation consist of:
A controller class, to implement specific (peraration-) tasks like uploading a CSV file. It inherits from a base controller of the cms framework
An import "business" or "service" class, that implements how the data is getting imported (and may further delegate to queued jobs etc.)
The CMS framework part consists of:
A base controller class for all common/shared import tasks like (start the prepared import, clean all working data, etc.)
A base service class ImportBase where all implementations inherit from. It provides an interface to receive a progress for any import and implements shared operations like cleaning up working data, etc.)
An ImportStatus class which is part of the ImportBase-Class via $ImportBase->status() to handle all runtime status informations (like "is the job still running, what is the progress). This class also provides a containter for a so called "payload" that allows any conrete import implementation to push and fetch custom status informations (ie. any sub-process has been finished)
So back to my IoC architecture. After the user selected a concrete import, the following route delegates the action to the custom import implementation's controller. If it's a framework supported standard-action like via URL /import/<importkey>/clean, the inherited BaseController of the cms framework takes over and handles the request
Route::get('/import/{key}/{method}', ['uses' => function($key, $method) {
return App::make('\\MadeleinePim\\Http\\Controllers\\Import\\'.ucfirst(camel_case($key)).'Controller')->$method($key);
}]);
I know that this direct binding via a naming convention can be improved (maybe via a custom configuration file), but for now this works for me.
Now I need to show an example of how I tried to implement a concrete import target in my controller via /import/<importkey>/seedCsvDataToDatabase:
public function seedCsvDataToDatabase($key)
{
// The IoC binding is shown in next code snippet. I did not found a good way to use method injection because
// of the route-specific parameters that control the responsible import implementation
$import = \App::make(Import::class, [$key]);
// Now trigger the import service operation of that concrete import implementation (probably bad design here)
$import->seed();
// Now, that this preparation task is done, I use the ImportStatus object which is part of the Import to store
// status informations. With this I can then decided in which step the user is (Think of it like a wizard to
// prepare any import)
$import->status()
->set(ConcreteImport::STATUS_SEEDED, true)
->set(ConcreteImport::STATUS_SEEDED_DURATION_SECONDS, (microtime(true) - $time_start) / 60);
// Back to controller method that determines in which status the import is to delegate/redirect to different
// views.
return redirect('/import/<importkey>');
}
My IoC binding for the Import class:
$this->app->singleton(Import::class, function ($app, array $parameters) {
$importKey = head($parameters);
// There is a config file that provides the class names of the concrete import implementations
$importClassName = config()->get('import.' . $importKey);
if (!$importClassName) {
throw new ImportNotFoundException($importKey, "Import with key '{$importKey}' is not setup properly'");
}
$importReflectionClass = new \ReflectionClass($importClassName);
return $importReflectionClass->newInstance($importKey);
});
And finally, the lazy loading of the import status, which is encapsulated in the ImportStatus object looks like this
public function status()
{
if (!$this->status) {
$this->status = \App::make(ImportStatus::class, [$this->key()]);
}
return $this->status;
}
I hope that demonstrates the way I try to resolve my import objects from the IoC container.
My learning so far is, that this is not the right way to inject my objects.
Is the assumption right, that I should not pass the $importKey at runtime to the App::make() and rather should try to make this independ?
My failed attempt on this was to make the IoC binding smarter and let it access the Request to properly inject my concrete import object with the required $importKey, like (pseudo code!):
$this->app->bind(ImportStatus::class, function(Container $app) {
// Did not find a good way to access the {key}-part of my route /import/{key}/{method}
$key = $app->make(Request::class)->get('key'); // Does not work like this
return new \Scoop\Import\ImportStatus($key);
});
Does this approach can work like this?
Can I somehow pass through the $importKey from my route to the ServiceProvider (or better pull it from there?)
Is there a better solution to initialize my concrete import implementations?
----------
UPDATE 1
For my lattest idea to access the Route in my IoC Binding, I got this way working:
$this->app->singleton(Import::class, function (Container $app) {
$importKey = \Route::current()->getParameter('key');
$importClassName = config()->get('import.' . $importKey);
$importReflectionClass = new \ReflectionClass($importClassName);
return $importReflectionClass->newInstance($importKey);
});
Nevertheless the idea of #Sandyandi N. dela Cruz to use a router binding prevents the direct coupling between the Binding and the Request which still doesn't feel right. Using router-binding to couple a request parameter to an implementation, sounds more appropriate.
I think you've dwelt to much on the IoC container there. Why not implement the Factory pattern and do a route binding instead of creating multiple controllers to handle different Imports? Crude example as follows:
Create a route binder - edit your app/Provider/RouteServiceProvider.php's boot() method
public function boot(Router $router)
{
parent::boot($router);
// Add the statement below.
$router->bind('import', 'App\RouteBindings#import');
}
Create the App\RouteBindings class as app/RouteBindings.php
Create an import() method with the following:
public function import($importKey, $route)
{
switch ($importKey) {
case 'import_abc':
return new ImportAbc;
break; // break; for good measure. ;)
case 'import_xyz':
return new ImportXyz;
break;
// and so on... you can add a `default` handler to throw an ImportNotFoundExeption.
}
}
Create a route for resolving an Import class.
Route::get('import/{import}/{method}', 'ImportController#handleImport');
Here, {import} will return the proper Import concrete class based on your URL.
In your ImportController's handleImport() you can do the following:
public function handleImport(Import $import, $method)
{
// $import is already a concrete class resolved in the route binding.
$import->$method();
}
So when you hit: http://example.com/import/import_abc/seed, the route binding will return a concrete class of ImportAbc and store it in $import on your handleImport() method, then your handleImport() method will execute: $import->seed();. Tip: you should probably move other controller logic such as $import->status()->set() into the Import class. Keep your controllers thin.
Just make sure your Import classes have the same signature.
It's kinda like Laravel's Route Model Binding except you create the logic for the bindings.
Again, this is just a crude example but I hope it helps.
Take a look to that code
<?php
namespace Sestante\SestanteBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sestante\SestanteBundle\Model\StrutturaManager;
class MainController extends Controller
{
public function indexAction(Request $request)
{
return $this->render('SestanteSestanteBundle:Main:index.html.twig');
}
public function showLodgingsAction(Request $request)
{
$repo = $this->getDoctrine()->getRepository('SestanteSestanteBundle:Struttura');
$usr = $this->get('security.context')->getToken()->getUser();
$usrId = $usr->getId();
$sm = new StrutturaManager($repo);
$lodgingList = $sm->retrieveLodgingsFromUser($usrId);
return $this->render('SestanteSestanteBundle:Main:showLodgings.html.twig',array('lodgingList' => $lodgingList));
}
}
This is a controller for an application that I've been writing.
Take a look to showLodgingsAction. I've tryied to place all business logic into a model (StrutturaManager) that with a repository (that I've passed directly from controller because, as far I know, they're available only here or via D.I.) query my db, do some elaboration and return a list that I'll render onto a template.
First question: Is this "code separation" good, or exists a better way for do what I'm trying to do?
Second question: suppose that, now, I want to use an object of StrutturaManager type into indexAction. Remember that mine object want a repository. So, have I to declare again, and again, and again, all my object for every controller's action where I want to use them? I suppose that must exist a smarter method but, at the moment, I don't understand which.
Define StrutturaManager as a service and inject the EntityManager into it. This way the manager will have access to repositories you need and controllers won't know about Doctrine nor repositories — which is a good practice.