I'm trying to create my own session handler. I've found this resource. The problem with that, is that he make changes to vendor directory which is something I don't want to.
The reason for that is, I'm working with others, it's a collaborative project using version control system (pushing the main application ignore the vendor folder ) and besides that I presume with each composer install all changes will be lost (although I'm not sure).
It crossed my mind to change the DatabaseSessionHandler, after all , all I wanted was to change the field names of the table that Laravel uses for storing the session (I'm working on a pre-existing database), but it would be the same as I mentioned above.
Is there any way I could create my own session handler and use it in my application?
A service provider or something even better?
Links will be appreciated.
Update
Something I want to clarify, I want to be able to use Laravels API.
Another resource I've found is Laravels doc how to write a session extension, but I think a lot of stuff is missing. It says I have to create a service provider cause sessions are started very early in the request-lifecycle. Any directions from there?
It says I have to create a service provider cause sessions are started very early in the request-lifecycle. Any directions from there?
What that actually means is that you have to register a Session Service Provider in the IoC-Container very early in the request-lifecycle.
Since the bindings in your app/config/app.php will be registered very early during the bootstrapping process of laravel, it is the perfect place to bind your custom SessionHandler-Extension.
Basically you need the following things to tackle your problem:
A Service Provider that binds a new instance of a SessionHandlerInterface (your concrete Custom Session Handler) to the IoC Container
And edit in your app/config/app.php that adds your new Service Provider
Your Custom Session Handler Class
Let's get started:
Bootstrapping the Service Provider:
The Docs tell us to add our Custom Service Provider below the Illuminate\Session\SessionServiceProvider so in your app/config/app.php add the following line below laravel SessionServiceProvider:
'MyProject\Extension\CustomSessionServiceProvider',
Now during laravels bootstrapping process out CustomSessionServiceProvider will be loaded right after laravels. In our custom provider we will do the actual binding.
Creating the Service Provider:
Now that you made sure the Service Provider is being loaded we will implement it.
Within the service provider we can overwrite the binding of laravels DatabaseSessionHandler which we will do.
<?php namespace MyProject\Extension;
use Illuminate\Support\ServiceProvider;
use Session;
class CustomSessionServiceProvider extends ServiceProvider {
public function register()
{
$connection = $this->app['config']['session.connection'];
$table = $this->app['config']['session.table'];
$this->app['session']->extend('database', function($app) use ($connection, $table){
return new \MyProject\Extension\CustomDatabaseSessionHandler(
$this->app['db']->connection($connection),
$table
);
});
}
}
First we grab the connection type that we use to store our session and then the table where the sessions will be stored.
Since you only want to change column names we don't need to implement a whole new Database Session Handler. Instead let's extend Laravels Illuminate\Session\DatabaseSessionHandler and overwrite the necessary methods.
Laravels DatabaseSessionHandler has two dependencies. An implementation of the ConnectionInterface and a table name. Both are injected into our CustomDatabaseSessionHandler which you can see above.
Then we just return our CustomDatabaseSessionHandler in the closure.
Creating the actual CustomDatabaseSessionHandler
Now that we're ready to fire off the new CustomDatabaseSessionHandler let's create it.
There is not much to do. Only four methods use the hardcoded columns. We will just extend the Illuminate\Session\DatabaseSessionHandler class and overwrite those four.
<?php namespace MyProject\Extension;
use Illuminate\Session\DatabaseSessionHandler;
class CustomDatabaseSessionHandler extends DatabaseSessionHandler {
public function read($sessionId)
{
// Reading the session
}
public function write($sessionId, $data)
{
// Writing the session
}
public function destroy($sessionId)
{
// Destryoing the Session
}
public function gc($lifetime)
{
// Cleaning up expired sessions
}
}
Since you want to only change column names you can even copy the method bodies from the parent class and just change what you want.
That's it. Happy coding and have fun with Laravel!
When implementing, make sure you are locking the session as this is often forgotten and leads to unreliable session modifications. For more information read:
http://www.leaseweblabs.com/2013/10/symfony2-memcache-session-locking/
You are able to use SessionHandlerInterface
class MySessionHandler implements SessionHandlerInterface {
// implememntation
}
$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();
References
http://www.php.net/manual/en/class.sessionhandlerinterface.php
Related
I have a service file that processes data and stores it in private fields. Many queries are executed, hence I'd like to instantiate the service file only once and access data from the fields when necessary in later stages of the request's lifecycle.
I've tried registering the service file in Laravel's service provider using the following snippet, but it did not seem to work as I expected.
$this->app->singleton('App\Services\UserService', function ($app) {
return new App\Services\UserService();
});
In my case, the first time I called the service methods was in my middleware class. The particular methods I called should've set many private fields in place which I could later use. Service file was loaded in using the "injection" method.
public function __construct(UserService $user_service) {
$this->user_service = $user_service;
}
However, once the request finally proceeded to the controller method, the fields in the service object had been nulled and I had to call the "heavy" methods once again.
Within the the controller constructor method, I resolved the service file using the resolve() helper method, however I don't think that would've made a difference.
Is there something I've missed or misunderstood?
Any help or pointers are appriciated!
I'm trying to build a good building block for my application to avoid vendor locking. It's Laravel 5.4, and here is the scenario I need help with:
I want to implement SMS sending but I don't want to be locked to a single vendor.
So conceptually in code, I would do something like this:
use App/SMS;
// controller
public function send_sms()
{
SMS::send('Thanks for saving', $number);
}
However SMS is just an abstraction, which would be calling a real implementation's class functions, which I may have many of, and switch out in a config file for example.
class SMSGlobal implements SMSProvider
{
public function send($msg, $to)
{
// Whatever the logic here is for SMSGlobal to send an SMS.
}
}
class Nexmo implements SMSProvider
{
public function send($msg, $to)
{
// Whatever the logic here is for Nexmo to send an SMS.
}
}
// Somewhere else, I would set SMS to a particular provider (in config)?
'SMSProvider' => 'SMSGlobal'
I hope this makes sense. I'm just not sure how to implement this type of thing - conceptually it makes sense (in my head).
I would have MANY of these abstractions, so the system is not vendor locked.
It would be similar to the way Cache works, where in config you set 'redis' and then calling Cache::get() uses the redis provider but as a programmer there is no difference whether the backend is database/file or redis.
Cheers!
FYI - Also asked here https://laracasts.com/discuss/channels/laravel/abstracting-underlying-logic
Not sure of the size of the community there though.
It sounds like you're describing creating and passing around an interface which is bound to implementing classes. In Laravel you can bind an interface with implementing classes as described in the docs on the service container.
I've run into an issue with Lumen v5.0.10 that has me at my wits end. I'm designing an application largely using TDD with the bundled phpunit extensions. I'm basically getting a BindingResolutionException for "App\Contracts\SubscriberInteractionInterface". This is an interface in the directory App\Contracts which has an implementation in App\Services which is registered in the AppServiceProvider like so:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
// Owner manager
$this->app->singleton(
'App\Contracts\OwnerInteractionInterface',
'App\Services\OwnerManager'
);
// Subscriber manager
$this->app->singleton(
'App\Contracts\SubscriberInteractionInterface',
'App\Services\SubscriberManager'
);
// dd($this->app->bound('App\Contracts\SubscriberInteractionInterface'));
}
}
My frustration is that if I uncomment that last line in the function then it shows that App\Contracts\SubscriberInteractionInterface has been bound (and thus may be resolved).
I then have a controller which effectively looks like this
class MyController extends Controller {
public function __construct(LoggerInterface $log)
{
$this->log = $log;
}
public function index(Request $request)
{
if (/* Seems to come from owner */)
{
$owners = App::make('App\Contracts\OwnerInteractionInterface');
return $owners->process($request);
}
if (/* Seems to come from subscriber */)
{
$subscribers = App::make('App\Contracts\SubscriberInteractionInterface');
return $subscribers->process($request);
}
}
}
I use them in this way because I only want the relevant one instantiated (not both as would happen if I type-hinted them) and they also each have type hinted dependencies in their constructors.
The issue is that the route of the tests that needs OwnerInteractionInterface runs just fine but the one that needs SubscriberInteractionInterface does not. The implementations and interfaces are largely similar and as I showed before, they are both registered at the same time and I can confirm that SubscriberInteractionInterface is bound. In fact, if I put the line:
dd(App::bound('App\Contracts\SubscriberInteractionInterface'));
at the top of index() it returns true. The tests happen to be ordered such that the path that uses OwnerInteractionInterface runs first and it resolves fine and then the other test fails with a BindingResolutionException. However, if I omit other tests and run just that one, then everything goes smoothly. The tests are in different files and the only setup I do is to bind a mock for a third party API in place of an entirely different binding from those shown and none of that code touches these classes. This is done within a setUp() function and I make sure to call parent::setUp() within it.
What's going on here? Could it be that binding one concrete instance wipes non-concrete bindings from the IoC? Or is it that the default setup allows some influence to transfer over from one test to another?
I know I sorta have a workaround but the constraint of never running the full test-suite is annoying. Its starting to seem that testing would be easier if I just use the instance directly instead of resolving it from its interface.
Also, does anyone know a way to inspect the IoC for resolvable bindings?
After further attempts at debugging, I've found that if you use app(...) in place of App::make(...) then the issue does not come up. I put in a eval(\Psy\sh()) call in the tearDown of the TestCase class and found that after a few tests you get the following result:
>>> app()->bound('App\Contracts\OwnerInteractionInterface')
=> true
>>> App::bound('App\Contracts\OwnerInteractionInterface')
=> false
>>> App::getFacadeRoot() == app()
=> false
This is to say that somehow, the Laravel\Lumen\Application instance that the App facade uses to resolve your objects is not the same as the current instance that is created by the setUp() method. I think that this instance is the old one from which all bindings have been cleared by a $this->app->flush() call in the tearDown() method so that it can't resolve any custom bindings in any tests that follow the first tearDown() call.
I've tried to hunt down the issue but for now I have to conclude this project with this workaround. I'll update this answer should I find the actual cause.
Instead of use bind, you can use bindIf method. Container will check whether the abstract has been bound or not. If not, it will bind your abstract and vice versa. You can read the api here.
So if you use singleton, you may use bindIf like.
// Owner manager
$this->app->bindIf(
'App\Contracts\OwnerInteractionInterface',
'App\Services\OwnerManager',
true
);
// Subscriber manager
$this->app->bindIf(
'App\Contracts\SubscriberInteractionInterface',
'App\Services\SubscriberManager',
true
);
I extended my Laravel4 Project with another Project Model. Now I have two Models (like Model1 and Model2). My goal is to set a custom config var, telling the installation which Project model to use.
Is there a way to set this in the config and then use Project1 as Project in my Controllers?
If I call $p = new Project; I want it to be an instance of either Project1 or Project2, depending on the configuration.
I hope you understand my question, thank you
You can kind of achieve this by using the IoC container.
First, instead of using new Project, use App::make('Project') - that won't change anything yet, but will allow you to now 'redefine' what 'Project' points to.
So the next step is, in your startup script (or a service provider if you're using service providers) code similar to the following:
if (Config::get('model', 'Project') != 'Project') {
App::alias('Project', Config::get('model', 'Project'));
}
This line will bind whatever classname you set in 'model' in the main config to be what the code retrieves when you use App::make('Project'). The reason for the if is just so we don't end up aliasing Project to Project. Feel free to test it without, but it may create an infinite loop/recursion in the IoC.
The code may not be perfect as it's untested, but at the very least it should give you a path to explore.
Points to note
You may find App::bind('project', Config::get('model')); works better, especially if you start using the IoC container's automatic injection
As mentioned above, the if surrounding the magic line may not be required. The IoC container may well be clever enough to not keep following an alias to itself.
A note on static access
I mentioned before to change all instances of new Project to App::make('Project'), but I didn't mention how to change your code when using something like $projects = Project::all();. Well you can still do a similar thing, but you just have to get an instance: App::make('Project')->all();.
When you start going down this route, you find that you're having to App::make('Project') more and more. When this is the case, using Laravel's automatic dependency injection will help a lot. To do this, you typehint the class in your controller's constructor:
class ProjectController extends \Controller
{
protected $project;
public function __construct(Project $project)
{
$this->project = $project;
}
public function index()
{
$projects = $this->projects->whereVisible(1)->orderBy('date', 'desc')->get();
return View::make('projects.index', compact('projects'));
}
}
This automatic injection happens in a few classes. I don't know the full list, but certainly controllers, event listeners, view creators and view composers. If you have a class of your own you wish to take advantage of the automatic injection, you can App::make() it and it too will get its dependencies injected automatically.
I've been using registry pattern for a very long time. Basically, I load all the classes using a main object (even if they're not required by the controller itself) and controllers can reach them.
It loads like 20 classes currently and I want to change my approach.
I want to define dependencies for my controllers. For example, my register controller only depends on database class, recaptcha class and filter class.
So, I want to create a solution like this:
//dependencies
$registerDependencies = array(new Database(), new Recatpcha(), new Filter());
//load register controller
$this->loadController->('register', $this->loadDependencies($registerDependencies));
Is it called DI/DI Container?
Is this a better approach than my current system?
I would probably use this approach:
$this->loadController->register('database.main', 'Database')
->register('database.user', 'Database')
->register('recaptcha', 'Racatpcha');
And the register function would look like this
public function register($serviceName, $serviceClass)
{
// you can inject options to your class via a config array or a conf file
$this->registry[$serviceName] = new $serviceClass();
}
If you give an alias to your service, you could have multiple services that share the same class but with different parameters.
The service 'database.main' could connect to a DB and 'database.user' to another DB.
Symfony2 uses dependency injection and you can find documentation about the component on their website.