Global service objects in Laravel - php

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!

Related

Laravel change binding with Request params

I am using Laravel Framework 5.8.21. I am trying to bind an interface to a class based on Request params inside AppServiceProvider's register method like this.
public function register()
{
$this->app->bind('App\Contracts\SomeInterface', 'App\Logic\ClassName');
if(Request::has('notification_type') && Request::get('notification_type') == 'email') {
$this->app->bind('App\Contracts\SomeInterface', 'App\Logic\SomeotherClassName');
}
}
and later injecting the interface to Controllers __construct() method.
with tests, it always bound to ClassName. I tried to get accessed URL inside AppServiceProvider and while running unit tests, it always returning / with $this->app->request->getRequestUri(); and method as GET even though from the test I am posting to URL like this.
$this->post('/notification', [
'notification_type' => 'email',
])->assertJson(
'message-push-status' => true,
]);
While testing it with Postman, when I try to post http://localhost:8000/notification, it says, 419 | Page Expired.
This is possible. The register method of AppServiceProvider or any other service provider has access to request parameters.
This can be accessed as follows:
public function register()
{
$this->app->bind('SomeInterface', function($app) {
return new SomeClass(request()->some_parameter);
});
}
You will not be able to reliably use the current request information from within a service provider.
First, it is a general best practice to not depend on application logic within the register() method directly. You may cause a race condition where you have a dependency that hasn't been registered yet, or cause unnecessary overhead (e.g. establish a database connection even if you don't need any querying).
Second, Laravel's request lifecycle won't funnel the current request into the application until after all of the service provider registration and bootstrapping has been completed.
Without knowing exactly what business logic you're trying to accomplish, you have at least a couple options:
Use contextual binding to serve different implementations of the same interface depending on the requesting object (e.g. controller).
Use a factory or a similar facilitator-style object that you can inject in the controller, and can provide the proper dependency based on your preferred logic.

Using `__destruct` to implement default routing?

I'm updating a PHP framework I've written. It used to just use a default behavior for routing. For example consider a case where the request goes to domain.com/package/controller/method...
$url = ["package", "controller", "method"];
//Check if package exists...
//Check if controller exists in package...
//Check if method exists in controller...
This is all well and good, and works perfectly. However, I wanted to add some additional functionality to my router. That functionality being the ability to define custom routes, and pass an anonymous function which does whatever you want.
However, supposing that the request does not match any of the user-defined routes, I want to use the default functionality I have now to check if there are additional possible routes. That way I can update old projects with the new framework and not have them break, and additionally...I just like this default behavior because most of the time routes are not that complicated and defining routes feels like a violation of DRY to me.
The problem is that I don't want to pass the user-defined routes as an array to the object constructor. Rather, I want the user to call them as methods on the base application object similar to how laravel or express handles this.
The problem is that I want the default route checking to happen AFTER the user's defined routes have been checked not before. This quasi-code might help you understand what I mean...
class App
{
__construct
{
//Check Default Routing
}
private function get()
{
//Get Request
}
private function post()
{
//Post Request
}
private function put()
{
//Put Request
}
private function delete()
{
//Delete Request
}
}
app::get();
In the above case, the default routing would take place before the user-defined routes are called. I looked at the PHP consrtuctor/destructor page and learned about __destruct. However, after reading this question I'm a little bit unsure this would work.
PHP.net says...
The destructor method will be called as soon as there are no other
references to a particular object, or in any order during the shutdown
sequence.
The first part of that explanation sounds like exactly what I want. I.E. as soon as all of the methods have been called on the application object, we'll run the __destruct function which will check if the user-defined routes were fruitful, and if not, check if the default routing system yields any results.
The problem is that I'm not sure if this is bad practice, or simply won't work. Can I require a file, set my controller, and then call a method on that controller from within __destruct? Are there limitations that would effect the code within these controllers? Supposing that there is a problem using __destruct this way, what are my alternatives, keeping in mind I don't like either of these solutions...
Having the user call the default routing as a method at the end of their script.
Passing routes in as arrays to the constructor.
I think you're confused here. Take note of this from the PHP Manual
The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.
To put this a different way, there's two reasons to call a destructor
The class is being garbage collected. In other words, you've overwritten or unset all the references to the class instance. This means you can't directly call this class anymore
The PHP script has reached its end and the thread is shutting down.
In other words, there's nothing left for the class to do. But in your own statement you say this
The first part of that explanation sounds like exactly what I want. I.E. as soon as all of the methods have been called on the application object, we'll run the __destruct function which will check if the user-defined routes were fruitful, and if not, check if the default routing system yields any results.
There is no "and" here to work with. That's the point. In fact, there's very few places you would use this.
What you need is to think in layers. So you'd have a controller layer that you'd call to check the methods. In turn, that controller opens a new layer that checks user functions. That class or method should return something or throw an Exception if it fails. On failure it can then try to use default methods. This is how you need to structure your program. Trying to use a destructor to do this would likely only confuse people. Make the data flow explicit, not implicit (which is what magic methods do).

How can I efficiently structure this code in Laravel?

I have a class called Sharer, which accepts a Service interface in the construct method. Service can be FacebookService or TwitterService and so on. Each Service class has a post method, that posts whatever array data you pass to that method, using its own connection (either facebook, twitter in this example).
Now the Sharer class normalizes data before sending it to that service's post method. By normalizing, it checks whether the thing we are sending to that post method is just a simple array or a Model class. Model can be either Project model, or Image model, or Video model and so on.
If it is a model class, then it calls that specific model's transformer. Transformers are just helper classes, that accept the model instance and they have two methods - facebook and twitter in this case. And each method returns a formatted array, specific to the connection.
So for example, facebook method grabs the required fields (that are needed to post on facebook) from the model and sends that array back. And twitter method does the same for twitter's required fields.
Now the thing I am a bit stuck with, is calling that specific method on the transformer class. I want to do something like this:
if(we are sharing on facebook) {
then call the facebook method
}
if(we are sharing on twitter) {
then call the twitter method
}
but obviously I want to somehow make it dynamic and not have these if statements. What would be a better approach to this?
I solved it doing this:
$method = $this->resolveMethodToCall();
protected function resolveMethodToCall()
{
$reflection = new ReflectionClass($this->service); // service being either a FacebookService class or TwitterService
$shortName = $reflection->getShortName();
return strtolower(str_replace('Service', '', $shortName));
}
Probably not the best solution, but works well and after this I am checking if the resolved method actually exists on the transformer class and throw an exception if it doesn't.

Lumen IoC binding resolution spotty within phpunit tests

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
);

Creating Our Own Session Handler

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

Categories