Is there a way to send params to an observer? - laravel

Is there a way to send parameters to an Observer in Eloquent ORM?
Based on laravel's documentation:
User::observe(UserObserver::class);
observe method receive a class, not an instance of an object. So I cant do something like:
$observer = new MyComplexUserObserver($serviceA, $serviceB)
User::observe($observer);
So, in my code I can do something like:
class MyComplexUserObserver
{
private $serviceA;
private $serviceB;
public function __constructor($serviceA, $serviceB){
$this->serviceA = $serviceA;
$this->serviceB = $serviceB;
}
public function created(User $user)
{
//Use parameters and services here, for example:
$this->serviceA->sendEmail($user);
}
}
Is there a way to pass parameters or services to a model observer?
Im not using laravel directly, but i'm using eloquent (illuminate/database and illuminate/events)
Im not trying to send additional parameters to an explicit event like in: Laravel Observers - Any way to pass additional arguments?, i'm trying to construct an observer with additional parameters.
FULL SOLUTION:
Thank you to #martin-henriksen.
use Illuminate\Container\Container as IlluminateContainer;
$illuminateContainer = new IlluminateContainer();
$illuminateContainer->bind(UserObserver::class, function () use ($container) {
//$container is my project container
return new UserObserver($container->serviceA, $container->serviceB);
});
$dispatcher = new Dispatcher($illuminateContainer);
Model::setEventDispatcher($dispatcher); //Set eventDispatcher for all models (All models extends this base model)
User::observe(UserObserver::class);

In the Illuminate events there is the line, this indicates on event subscription it utilities the container. This mean we can use this to our advantage, i'm not super familiar with non Laravel bootstrapped applications. But where ever your app is defined, you will bind your class to your own class.
$container = new Container();
$container->bind(MyComplexUserObserver::class, function ($app) {
return new MyComplexUserObserver($serviceA, $serviceB, $theAnswerToLife);
});
$dispatcher = new Dispatcher($container);
This will result in, next time your application resolves your class, it will use this version of it and therefor you can setup your class as you intend.
Edit: an example how you can utilize the Laravel container, to utilize the bind functionality.

Related

Why won't this Laravel 5.4 service provider register?

I am trying to do a hello world service provider with the new Laravel 5.4.
I have created the following service provider file:
//File: app/TestProvider/TestServiceProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind('Test', function ($app) {
return new Test();
});
}
}
I have created a simple class under the same namespace:
//File: app/TestProvider/Test.php
namespace App\TestProvider;
class Test
{
/**
* Register bindings in the container.
*
* #return void
*/
public function helloWorld()
{
echo "hello world";
}
}
The problem is, this is not registering. The register method is executing as when I put a breaker before the 'bind' method, it executes:
public function register()
{
dd("BREAKER");
$this->app->bind('Test', function ($app) {
return new Test();
});
}
So this outputs "BREAKER" as expected. However if I put the breaker in the closure, nothing happens which suggests for some reason, that 'bind' method isn't being executed??
Any ideas?
EDIT:
Just some further info: I know that the Test class is registered and in the correct namespace as I can do:
dd(new Test());
in the registration method, and it outputs the resource id as expected.
Explanation
The closure provided only runs when the binding is being resolved. That's why it's a closure, it can be saved in the service container and resolved at any time while the program runs.
Solution
To see the resolved binding, create a controller and resolve the class in that controller:
// File: app/Http/Controllers/TestController.php
namespace App\Http\Controllers;
// This isn't the best way, but it works. See the best way below
class TestController extends Controller {
public function index()
{
return \App::make('Test')->helloWorld();
}
}
Of course, don't forget to register the route:
// File: routes/web.php
Route::get('/', 'TestController#index');
The binding will resolve when you hit the homepage.
However, as I said, it's not the best way, so here I prepared a better way. Change the way you register the binding:
// File: app/Providers/TestProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
use App\TestProvider\Test;
// Better way
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
// Note: we bind the exact complete class name!
$this->app->bind(Test::class, function ($app) {
return new Test();
});
}
}
After this change the controller so that it looks like this:
namespace App\Http\Controllers;
use App\TestProvider\Test;
class TestController extends Controller {
/**
* #var Test $test
*/
private $test;
// Let Laravel resolve the dependency on constructing the class
public function __construct(Test $test)
{
$this->test = $test;
}
public function index()
{
return $this->test->helloWorld();
}
}
You will see that the exact same thing happens, but it looks more elegant and avoids conflicts.
Details
Laravel gives only a high level overview of the service container, which doesn't help to learn how it works on the inside. The best way to see that is to go down the call stack.
When you do that, you find that Laravel registers every class in the project in the service container. That means that whether you create a service provider or not, the class will be in the container. How exactly?
When you run php artisan optimize, Laravel creates files that have array with all the classes of the project. When you run the app, after registering everything from the service providers, Laravel registers the rest of the classes from that file.
That means that in your case, if you don't specifically register the Test class, it will still be resolvable. Basically, you only need to register classes that need some specific instructions to be resolved.
So how does Laravel resolve the dependencies?
When you run \App::make(Test::class) or inject dependency via type hinting in the constructor (the "better way" from my solution), Laravel looks for that dependency among the bindings.
When it finds the dependency, it resolves either the closure associated to it or the constructor of the class directly.
When it resolves the constructor directly, it looks for type hints among the constructor parameters and recursively resolves all of them until there's nothing else to resolve.
After that it returns the resolved class.
Of course, bear in mind that for Laravel to analyze the contructor of a class, it needs to be resolved via the service container in the first place. You can't just call $test = new Test(); and expect Laravel to do all the magic :)
Conclusion
This is a rather quick overview of Laravel's service container. The best way for you to learn it is, of course, studying the sources for yourself. It's truly elegant and it uses PHP's functionality to the fullest.
I really hope this shed some light on the service container for you and can help you in the future :)
The closure passed to the bind() method is not executed until you actually attempt to resolve the alias you are binding.
So, if you dd('breaker') inside the closure, this won't actually get executed until Test is resolved (whatever your preferred resolution method is):
Service provider:
// bind the closure to the 'Test' alias
public function register()
{
$this->app->bind('Test', function ($app) {
dd("BREAKER");
return new Test();
});
}
Code that resolve Test alias:
// different ways of resolving the alias out of the container.
// any of these will execute the bound closure.
$test = resolve('Test');
$test = app('Test');
$test = app()->make('Test');
$test = \App::make('Test');
try:
$this->app->bind(Test::class, function ($app) {
return new Test();
});

How can I override Laravel Facade methods?

I want to override the Laravels' Mail's classes facade method send (just intercept it forcing some checks and then if it passes triggering parent::send())
What is the best way to do this?
A Facade doesn't work like that. It's essentially kind of like a wrapper class that calls the underlying class that it represents.
The Mail facade doesn't actually have a send method. When you do Mail::send(), under the hood, the "facade accessor" is used to reference an instance of the Illuminate\Mail\Mailer class bound in the IoC container. It's on that object the send method is called.
The way in which you can achieve what you're after is actually a little bit trickier than it seems. What you can do is:
Write your own implementation of Mailer, extending Illuminate\Mail\Mailer, in which you can override the send method, implement your checks and call parent::send().
Write your own service provider (Extending Illuminate\Mail\MailServiceProvider), in particular re-implement the register method. It should create an instance of your own Mailer in place of Laravel's own. (You can copy most of the code from Laravel's register method).
Now, in your config/app.php file, in the providers array, replace Illuminate\Mail\MailServiceProvider::class, with your own provider.
That should let you hook into Laravel's Mail functionality.
For more information, you can take a look at the following question/answer which achieves a similar thing. It extends the Mail functionality to add a new transport driver, but it takes a similar approach in that it provides its own Mailer implementation and service provider.
Add a new transport driver to Laravel's Mailer
app/MyMailer/Mailer.php
<?php
namespace App\MyMailer;
class Mailer extends \Illuminate\Mail\Mailer
{
public function send($view, array $data = [], $callback = null)
{
// Do your checks
return parent::send($view, $data, $callback);
}
}
app/MyMailer/MailServiceProvider.php (Most of the code copied from Laravel's MailServiceProvider class)
<?php
namespace App\MyMailer;
class MailServiceProvider extends \Illuminate\Mail\MailServiceProvider
{
public function register()
{
$this->registerSwiftMailer();
$this->app->singleton('mailer', function ($app) {
// This is YOUR mailer - notice there are no `use`s at the top which
// Looks for a Mailer class in this namespace
$mailer = new Mailer(
$app['view'], $app['swift.mailer'], $app['events']
);
$this->setMailerDependencies($mailer, $app);
$from = $app['config']['mail.from'];
if (is_array($from) && isset($from['address'])) {
$mailer->alwaysFrom($from['address'], $from['name']);
}
$to = $app['config']['mail.to'];
if (is_array($to) && isset($to['address'])) {
$mailer->alwaysTo($to['address'], $to['name']);
}
return $mailer;
});
}
}
config/app.php (In the providers array)
//...
// Illuminate\Mail\MailServiceProvider::class,
App\MyMailer\MailServiceProvider::class,
//...

Laravel inject sentry user into model

I keen to make my code decouple and ready for testing.
I have an Eloquent model getBudgetConvertedAttribute is depend on sentry user attribute.
public function getBudgetConvertedAttribute()
{
return Sentry::getUser()->currency * $this->budget;
}
This throw error while testing because Sentry::getUser is return null.
My question is, How shall I code to inject user into model from controller or service provider binding or testing?
Inject a $sentry object as a dependency in the constructor instead of using the Sentry Facade.
Example
use Path\To\Sentry;
class ClassName
{
protected $sentry
public function __construct(Sentry $sentry)
{
$this->sentry = $sentry;
}
public function methodName()
{
$this->sentry->sentryMethod();
}
}
Why not just create a method on the model, then takes a Sentry user object as a parameter?
public function getBudgetConverted(SentryUser $user)
{
return $user->currency * $this->budget;
}
You’ll need to change the type-hint (SentryUser) to the actual name of your user class.
If this is to aid testing, you could go one step furhter and type-hint on an interface (which you should be any way), that way you could test your method with a mock user object rather than one that may have a load of other dependencies like a database connection, which Eloquent models do.

How to get a reference to the service manager inside the Module's init method (ZF2)?

I need that some code be executed before any MvcEvent::EVENT_BOOTSTRAP listener get execute. Evidently Module::onBootstrap is no an option. I end with the following code:
class Module
{
function init(\Zend\ModuleManager\ModuleManager $moduleManager)
{
$moduleManager->getEventManager()->attach(
MvcEvent::EVENT_BOOTSTRAP, array(ClassX, 'StaticMethodOfClassX'), 20000);
}
}
I don't want have hard code the array(ClassX, 'StaticMethodOfClassX') reference but get it from the service manager. My problem is that I don't know how to get an service manager reference inside the module's init method. Any help? or this is impossible in ZF2 right now? Whatever variant to this schema or opinion will be appreciate too ;)
EDIT:
I will clarify "Evidently Module::onBootstrap is no an option", cos may be is not so trivial ;)
Modules Module::onBootstrap methods are executed when the event MvcEvent::EVENT_BOOTSTRAP is triggered, but the attachment of each module's Module::onBootstrap method to that event depend of the order in which modules were loaded. Due to, the order in which a specific Module::onBootstrap method will be executed depend on what other modules exist and how other modules affect the order in which that specific module will be loaded. Beside, whatever listener attached to the MvcEvent::EVENT_BOOTSTRAP event with priority greater than 1 will be execute before any module Module::onBootstrap method, example the ViewManager::onBootstrap listener. So, to achieve what I want
I need that some code be executed before any
MvcEvent::EVENT_BOOTSTRAP listener get execute
modules obBootstrap methods are not an option.
This is a very old post but since no answer has been accepted and I recently needed to achieve the same thing, I thought I'd share my solution.
The reason I needed to access the ServiceManager before the Bootstrap event is triggered, was so I could manipulate the merged configuration with values retrieved from the database.
Problem:
The example found in the Zend documentation shows how to manipulate the merged configuration, but at that particular time the Service manager is empty, making it impossible to retrieve things like database adapters etc.
Solution:
In your module class, implement the interface InitProviderInterface and add the appropriate method.
public function init(ModuleManagerInterface $moduleManager)
{
$eventManager = $moduleManager->getEventManager();
$eventManager->attach(ModuleEvent::EVENT_LOAD_MODULES_POST, [$this, 'onLoadModulesPost']);
}
The EVENT_LOAD_MODULES_POST event will get invoked after the EVENT_MERGE_CONFIG event but before the EVENT_BOOTSTRAP event is triggered. Also at this particular time the ServiceManager will contain all the factories, invokable classes you're wanting to access.
Your callback method may look something like.
public function onLoadModulesPost(ModuleEvent $event)
{
/* #var $serviceManager \Zend\ServiceManager\ServiceManager */
$serviceManager = $event->getParam('ServiceManager');
$configListener = $event->getConfigListener();
$configuration = $configListener->getMergedConfig(false);
$someService = $serviceManager->get('Your/Custom/Service');
$information = $someService->fetchSomeInformation();
$configuration = array_merge($configuration, $information);
$configListener->setMergedConfig($configuration);
$event->setConfigListener($configListener);
$serviceManager->setAllowOverride(true);
$serviceManager->setService('Config', $configuration);
$serviceManager->setAllowOverride(false);
}
You can get it off the MvcEvent
$locator = $event->getTarget()->getServiceLocator()->get('YourObject')
If you don't have access to the event, you can set the event as a property on the Module class on bootstrap, and then use it in your init method whenever.
public function onBootstrap($event) {
$this->setMvcEvent($event);
}
function init(\Zend\ModuleManager\ModuleManager $moduleManager)
{
$locator = $this->mvc_event->getTarget()->getServiceLocator()->get('YourClass');
$moduleManager->getEventManager()->attach(
MvcEvent::EVENT_BOOTSTRAP, array(ClassX, 'StaticMethodOfClassX'), 20000);
}
Are you using ZfcBase in your application? The AbstractModule has a boostrap method (not onBootstrap) which is executed by this event handler in the init method
$sharedManager->attach('Zend\Mvc\Application', 'bootstrap', function($e) use ($instance, $moduleManager) {
$app = $e->getParam('application');
...
$instance->bootstrap($moduleManager, $app);
});
Of course you can use this approach without ZfcBase.
Then you can implement Zend\ServiceManager\ServiceLocatorAwareInterface:
public function bootstrap(\Zend\ModuleManager\ModuleManager $moduleManager, \Zend\Mvc\ApplicationInterface $app){
$this->setServiceLocator($app->getServiceManager());
parent::bootstrap($moduleManager, $app);
}
public function setServiceLocator(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator){
$this->_serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator(){
return $this->_serviceLocator;
}
Maybe a bit later but hope it will help somebody else. At the init point there is no much services at the Service Manager but you can access it:
public function init(ModuleManager $moduleManager)
{
$sm = $moduleManager->getEvent()->getParam('ServiceManager');
$applicationConfig = $sm->get('applicationconfig');
var_dump($applicationConfig['modules']);
}
In this case we are retrieving the module names.
This is better idea.
class module
public function onBootstrap(MvcEvent $e)
{
$sm = $app->getServiceManager();
$config = $sm->get('config');
and this is all.

how to achieve MVC in my Zend Framework

currently i am doing a project in zend the way i am doing is working perfectly but i am sure its not the way i am suppose to do i mean i am not following MVC and i want to apply MVC in my zend app.
i am pasting code of one simple module which will describe what i am doing .kindly correct me where i am making faults.
my controller
class ContactsController extends Zend_Controller_Action{
public function contactsAction(){
if(!Zend_Auth::getInstance()->hasIdentity()){
$this->_redirect('login/login');
}
else{
$request = $this->getRequest();
$user = new Zend_Session_Namespace('user');
$phone_service_id = $user->p_id;
$instance = new Contacts();
$select = $instance->Get_Contacts($p_id);
$adapter = new Zend_Paginator_Adapter_DbSelect($select);
$paginator = new Zend_Paginator($adapter);
.
.
//more code
}
plz note this 2 line in my controller
$instance = new Contacts();
$select = $instance->Get_Contacts($pid);
this is my contacts class in models
class Contacts extends Zend_Db_Table{
function Get_Contacts($p_id){
$DB = Zend_Db_Table_Abstract::getDefaultAdapter();
$select = $DB->select()
->from('contact', array('contact_id','contact_first_name','contact_mobile_no','contact_home_no','contact_email','contact_office_no'))
->where('pid = ?', $p_id)
->order('date_created DESC');
return $select;
}
}
after this i simple assign my result to my view.
note please
as its working but there is not private data members in my class,my class is not a blue print.there are no SETTERS AND GETTERS .how can i make my code that best suits MVC and OOP??
The most simple answer: you are already almost MVC. You use a Zend_Controller_Action to grab some data and pass this on to a view layer where you render the html. The only missing part is your model, which is mixed up between the controller and your data gateway (where you implemented a table data gateway pattern, that Zend_Db_Table thing).
I gave a pretty thorough explanation in an answer to another question how I'd properly set up the relations between Controller and Model. I also combined this with a Form, to handle data input, filtering and validation. Then to bundle some common functions, I introduced a Service layer between the Model and Controller.
With the controller, you perform some actions (list all my contacts, create a new contact, modify a contact) and the model is purely containing the data (id, name, phone, address). The service helps to group some functions (findContactByName, findContactById, updateContactWithForm).
If you know how to split Controller, Mode, Form and Service, your controller can become something like this:
class ContactsController extends Zend_Controller_Action
{
public function indexAction ()
{
if (!$this->hasIdentity()) {
$this->_redirect('login/login');
}
$service = new Application_Service_Contacts;
$contacts = $service->getContacts();
$paginator = $service->getPaginator($contacts);
$this->view->paginator = $paginator;
}
protected function hasIdentity ()
{
return Zend_Auth::getInstance->hasIdentity();
}
}
It is your personal taste what you want to do in your controller: I'd say you put as less as possible in your controllers, but you need to keep the control. So: a call to get data happens in the controller, retrieving this data happens somewhere else. Also: a call to convert a dataset into something else happens in the controller, the conversion happens somewhere else.
This way you can change the outcome in controllers extremely fast if you provided enough methods to your service classes to fetch the data. (Note I took the Zend_Auth to another function: if you have other actions, you can use this same function. Also, if you want to change something in your authentication, you have one place where this is located instead of every action in the controller)
keep one thing in mind when u learn new technology so first read thier own documentation. No one can explain better than them. Its hard to understand firstly but when you study it you will usedto and than u will love it like me Zend Offical Site

Resources