I am using the Phalcon micro application as my REST web service. I want to add an event to the application and fire this event from different places like controllers.
For example; if a user registers, the controller should fire a userRegistered event, and userRegistered should do some stuff.
How can I implement this?
interface IUsers
{
function onUserRegistered();
}
Event class
class UsersActivities implements IUsers
{
function onUserRegistered()
{
// TODO: Implement onUserRegistered() method.
}
}
Just check docs. It's pretty simple, create manager, creat listener(UsersActivities in your case i guess) and fire events in manager.
https://docs.phalconphp.com/pl/latest/reference/events.html
Related
Pls I'm still new to laravel and I have used events in laravel a couple of times but I'm curious and would like to know if it's possible to execute an event in laravel asynchronously. Like for instance in the code below:
<?php
namespace mazee\Http\Controllers;
class maincontroller extends Controller
{
public function index(){
Event::fire(new newaccountcreated($user)) ;
//do something
}
Is it possible for the block of code in the event listener of the "newaccountcreated" event to be executed asynchronously after the event is fired ?
Yes of course this is possible. You should read about Laravel Queues. Every driver (only not sync driver) are async. The easiest to configure is the database driver, but you can also want to try RabbitMQ server , here is Laravel bundle for it.
You can also add to your EventListener: newaccountcreated trait Illuminate\Queue\InteractsWithQueue (you can read about him here) which will helps you to connect it with Laravel Queue.
Filip's answer covers it all. I will add a bit more to it. If you push an event it will goto the default queue. You can specify a queue name as well. Have the listener class implements ShouldQueue and just include the queue method in the listener class like below.
/**
* Push a new job onto the queue.
**/
public function queue($queue, $job, $data)
{
return $queue->pushOn('queue-name', $job, $data);
}
I am developing a web application in which I will be using third party API integrations (Payment gateways, SMS Vendors, Emailing services like mailchimp, other external APIs). My application will be having the repository pattern which will have repositories associated with each of my models and my controllers will be utilising the repository functions.
Now where does all my API integrations comes in? Should I be keeping all my API integrations in a single repository?
Example :
If I have a SMS provider Valuefirst, how does my Laravel repository design look like?
My SMS interface
interface ISmsProvider {
public function sendSms($mobileNo,$msg);
}
My ValueFirst (SMS Provider) repository
class ValueFirstRepository implements ISmsProvider {
public function sendSMS($mobieNo,$msg)
{
//API Call to ValueFirst SMS provider
// Any database insertions/updates to SMS model
// Any file logs
}
}
Assuming I have a service provider class where I have done the binding of the interface and repository my controller will look like
SMS Controller
class SmsController extends BaseController {
// ISmsProvider is the interface
public function __construct(ISmsProvider $sms)
{
$this->sms = $sms;
}
public function sendSMS()
{
$mobileNo = '9999999999';
$msg = 'Hello World!';
$users = $this->sms->sendSMS($mobileNo,$msg);
}
}
My questions
Is this the right approach to an API integration using repository pattern?
Does anything related to any database activities happens in the ValueFirst repository? Eg : If I want to update a record after the API response from ValueFirst, do I do it in the repository?
If it's right , can someone show me how the model interactions happens from the repository?
Now if I need to choose between ValueFirst or any other vendor how does that happens? How can I do it from the controller?
I will be using this project as a skeleton for my application
l5-Repository
Please help me with this design, I am fairly new to Laravel / repository pattern. Trying to learn stuff. TIA :)
Your approach seems pretty good to me but there is something i would improve
First of all I would keep the ValueFirstRepository class with the only resposability of managing your SMS API, and inject a specific repository class ( SmsRepository ) to interact with the DB through eloquent ( or only a model if you don't need a repository )
The important point here is to keep the resposability of managing your API in one class and the responsability of interacting with the DB in another:
ValueFirstRepository
class ValueFirstRepository implements ISmsProvider
{
//INJECT REPOSITORY CLASS TO INTERACT WITH DB
public function __construct(SmsRepository $smsRepo )
{
$this->smsRepo = $smsRepo;
}
//this method will be called from your controller
public function sendSMS($mobieNo,$msg)
{
//API CALL
$info = this->sendSMSAPI($mobieNo,$msg);
//DB CALL
$this->smsRepo->save( $info );
// Any file logs
}
//this will actually interact with the API
protected function sendSMSAPI($mobieNo,$msg)
{
//API Call to ValueFirst SMS provider
}
}
A litte variant to this solution could be using events, and fire events in your ValueFirstRepository class when a sms is sent, and respond to that event implementing some listeners that will do other operations related to the event
Another alternative solution could be to handle the steps directly in your controller:
SMS Controller
//INJECT THE DEPENDECIES IN YOUR CONTROLLER
public function __construct(ISmsProvider $sms, SmsRepository $smsRepo )
{
$this->sms = $sms;
$this->smsRepo = $smsRepo;
}
public function sendSMS()
{
//send SMS
$mobileNo = '9999999999';
$msg = 'Hello World!';
$info= $this->sms->sendSMS($mobileNo,$msg);
//use the model to save data
this->$smsRepo->save($info);
}
This time the dependency of SmsRepository would be injected in the controller, instead of the ValueFirstRepository class, and the controller's methods would be a litte more big, but it's up to you to decide the best way for you
For the last question: if you want to change your vendor provider, you could use Laravel's capability to bind interfaces to implementation through the bind method:
App::bind( App\ISmsProvider::class , App\ValueFirstRepository::class );
This will tell laravel wich class inject when requesting a specific interface. So, in this case, when a ISmsProvider interface is requested, Laravel will automatically inject a ValueFirstRepository concrete instance.
If you want to change the vendor you should only change the line to:
App::bind( App\ISmsProvider::class , App\AnotherSMSVendorRepository::class );
and the AnotherSMSVendorRepository class will be injected instead of ValueFirstRepository
I do not understand why the following code does not work. Perhaps I do not understand something with the EventManger/SharedEventManager ind Zend Framework 2.
For now I could not find anything about this on the internet.
Could it be that the instance of the IndexContoller is already destroyed at EVENT_RENDER and not constructed at EVENT_ROUTE? Perhaps this is the case or am I missing something here?
class IndexController extends AbstractActionController
{
public function routeEventOccured() {
echo 'test';
}
public function renderEventOccured() {
echo 'test';
}
public function __construct()
{
$this->getEventManager()->attach(MvcEvent::EVENT_ROUTE, array($this,
'routeEventOccured'));
$this->getEventManager()->attach(MvcEvent::EVENT_RENDER, array($this,
'renderEventOccured'));
}
}
Zend Framework 2 uses the concept of event. One class can trigger an event,
and other classes may listen to events. Technically, triggering an event means just calling another class' "callback" method. The event management is implemented inside of
the Zend\Mvc\EventManager component.
The application's "life" consists of several stages. Each application life stage is initiated by the application by triggering an event. Other classes (either belonging to Zend Framework or specific to your application) may listen
to events and react accordingly.
Below, the four main events (life stages) are presented:
Bootstrap. When this event is triggered by the application, a module has a chance to
register itself as a listener of further application events in its onBootstrap()
callback method.
Route. When this event is triggered, the request's URL is analyzed using a router class (typically, with
Zend\Mvc\Router\Http\TreeRouteStack class. If an exact match between the URL and a route
is found, the request is passed to the site-specific controller class assigned to the route.
Dispatch. The controller class "dispatches" the request using the corresponding action method
and produces the data that can be displayed on the web page.
Render. On this event, the data produced by the controller's action method are passed for rendering to
Zend\View\Renderer\PhpRenderer class. The renderer class uses a
view template file for producing an HTML page.
I would like to implement an Event system in my custom MVC framework, to allow decoupling
of classes that need to interact with each other. Basically, the ability for any class to trigger an event and any other class that listens for this event to be able to hook into it.
However, I cannot seem to find a correct implementation given the nature of php's share nothing architecture.
For instance, let's say that I have a User model that each time that it is updated, it triggers a userUpdate event. Now, this event is useful for class A (for instance) as it needs to apply its own logic when a user is updated.
However, class A is not loaded when a user is updated, so it cannot bind to any events triggered by the User object.
How can you get around such a scenario?
Am I approaching it wrongly?
Any ideas would be greatly appreciated
There must be an instance of class A before the event is triggered because you must register for that event. An exception would be if you'd register a static method.
Let's say you have an User class which should trigger an event. First you need an (abstract) event dispatcher class. This kind of event system works like ActionScript3:
abstract class Dispatcher
{
protected $_listeners = array();
public function addEventListener($type, callable $listener)
{
// fill $_listeners array
$this->_listeners[$type][] = $listener;
}
public function dispatchEvent(Event $event)
{
// call all listeners and send the event to the callable's
if ($this->hasEventListener($event->getType())) {
$listeners = $this->_listeners[$event->getType()];
foreach ($listeners as $callable) {
call_user_func($callable, $event);
}
}
}
public function hasEventListener($type)
{
return (isset($this->_listeners[$type]));
}
}
Your User class can now extend that Dispatcher:
class User extends Dispatcher
{
function update()
{
// do your update logic
// trigger the event
$this->dispatchEvent(new Event('User_update'));
}
}
And how to register for that event? Say you have class A with method update.
// non static method
$classA = new A();
$user = new User();
$user->addEventListener('User_update', array($classA, 'update'));
// the method update is static
$user = new User();
$user->addEventListener('User_update', array('A', 'update'));
If you have proper autoloading the static method can be called.
In both cases the Event will be send as parameter to the update method. If you like you can have an abstract Event class, too.
I made a very simple PHP Event Dispatcher / Event Hander for myself, it is testable and has been used on my websites.
If you need it, you can take a look.
I'm trying to use the Event System in CakePHP v2.1+
It appears to be quite powerful, but the documentation is somewhat vague. Triggering the event seems pretty straight-forward, but I'm not sure how to register the corresponding listener(s) to listen for the event. The relevant section is here and it offers the following example code:
App::uses('CakeEventListener', 'Event');
class UserStatistic implements CakeEventListener {
public function implementedEvents() {
return array(
'Model.Order.afterPlace' => 'updateBuyStatistic',
);
}
public function updateBuyStatistic($event) {
// Code to update statistics
}
}
// Attach the UserStatistic object to the Order's event manager
$statistics = new UserStatistic();
$this->Order->getEventManager()->attach($statistics);
But it does not say where this code should reside. Inside a specific controller? Inside the app controller?
In case it's relevant, the listener will be part of a plugin which I am writing.
Update:
It sounds like a popular way to do this is by placing the listener registration code in the plugin's bootstrap.php file. However, I can't figure out how to call getEventManager() from there because the app's controller classes, etc aren't available.
Update 2:
I'm also told that listeners can live inside Models.
Update 3:
Finally some traction! The following code will successfully log an event when inside of my MyPlugin/Config/bootstrap.php
App::uses('CakeEventManager', 'Event');
App::uses('CakeEventListener', 'Event');
class LegacyWsatListener implements CakeEventListener {
public function implementedEvents() {
return array(
'Controller.Attempt.complete' => 'handleLegacyWsat',
);
}
public static function handleLegacyWsat($event) { //method must be static if used by global EventManager
// Code to update statistics
error_log('event from bootstrap');
}
}
CakeEventManager::instance()->attach(array('LegacyWsatListener', 'handleLegacyWsat'), 'Controller.Attempt.complete');
I'm not sure why, but I can't get errors when I try to combine the two App::uses() into a single line.
Events
Events are callbacks that are associated to a string. An object, like a Model will trigger an event using a string even if nothing is listening for that event.
CakePHP comes pre-built with internal events for things like Models. You can attach an event listener to a Model and respond to a Model.beforeSave event.
The EventManager
Every Model in Cake has it's own EventManager, plus there is a gobal singleton EventManager. These are not all the same instance of EventManager, and they work slightly differently.
When a Model fires an event it does so using the EventManager reference it has. This means, you can attach an event listener to a specific Model. The advantages are that your listener will only receive events from that Model.
Global listeners are ones attached to the singleton instance of EventManager. Which can be accessed anywhere in your code. When you attach a listener there it's called for every event that happens no matter who triggers it.
When you attach event listener in the bootstrap.php of an app or plugin, then you can use the global manager, else you have to get a reference to the Model you need using ClassRegistry.
What EventManager To Use?
If the event you want to handle is for a specific Model, then attach the listener to that Model's EventManager. To get a reference of the model you can call the ClassRegistry::init(...).
If the event you want to handle could be triggered anywhere, then attach the listener to the global EventManager.
Only you know how your listener should be used.
Inside A Listener
Generally, you put your business logic into models. You shouldn't need to access a Controller from an event listener. Model's are much easier to access and use in Cake.
Here is a template for creating a CakeEventListener. The listener is responsible for monitoring when something happens, and then passing that information along to another Model. You should place your business logic for processing the event in Models.
<?php
App::uses('CakeEventListener', 'Event');
class MyListener implements CakeEventListener
{
/**
*
* #var Document The model.
*/
protected $Document;
/**
* Constructor
*/
public function __construct()
{
// get a reference to a Model that we'll use
$this->Document = ClassRegistry::init('Agg.Document');
}
/**
* Register the handlers.
*
* #see CakeEventListener::implementedEvents()
*/
public function implementedEvents()
{
return array(
'Model.User.afterSave'=>'UserChanged'
);
}
/**
* Use the Event to dispatch the work to a Model.
*
* #param CakeEvent $event
* The event object and data.
*/
public function UserChanged(CakeEvent $event)
{
$data = $event->data;
$subject = $event->subject();
$this->Document->SomethingImportantHappened($data,$subject);
}
}
What I like to do is place all my Events into the Lib folder. This makes it very easy to access from anywhere in the source code. The above code would go into App/Lib/Event/MyListener.php.
Attaching The EventListeners
Again, it depends on what events you need to listen for. The first thing you have to understand is that an object must be created in order to fire the event.
For example;
It's not possible for the Document model to fire Model.beforeSave event when the Calendar controller is displaying an index, because the Calendar controller never uses the Document model. Do you need to add a listener to Document in the bootstrap.php to catch when it saves? No, if Document model is only used from the Documents controller, then you only need to attach the listener there.
On the other hand, the User model is used by the Auth component almost every. If you want to handle a User being deleted. You might have to attach an event listener in the bootstrap.php to ensure no deletes sneak by you.
In the above example we can attach directly to the User model like so.
App::uses('MyListener','Lib');
$user = ClassRegistry::init('App.User');
$user->getEventManager()->attach(new MyListener());
This line will import your listener class.
App::uses('MyListener','Lib');
This line will get an instance of the User Model.
$user = ClassRegistry::init('App.User');
This line creates a listener, and attaches it to the User model.
$user->getEventManager()->attach(new MyListener());
If the User Model is used in many different places. You might have to do this in the bootstrap.php, but if it's only used by one controller. You can place that code in the beforeFilter or at the top of the PHP file.
What About Global EventManager?
Assuming we need to listen for general events. Like when ever any thing is saved. We would want to attach to the global EventManager. It would go something like this, and be placed in the bootstrap.php.
App::uses('MyListener','Lib');
CakeEventManager::instance()->attach(new MyListener());
If you want to attach an event listener inside bootstrap.php file of your plugin, everything should work fine using the hints posted in the answers. Here is my code (which works properly):
MyPlugin/Config/bootstrap.php:
App::uses('CakeEventManager', 'Event');
App::uses('MyEventListener', 'MyPlugin.Lib/Event');
CakeEventManager::instance()->attach(new MyEventListener());
MyPlugin/Lib/Event/MyEventListener.php:
App::uses('CakeEventListener', 'Event');
class MyEventListener implements CakeEventListener {
...
}
Event listeners related to MyPlugin are being registered only when the plugin is loaded. If I don't want to use the plugin, event listeners are not attached. I think this is a clean solution when you want to add some functionality in various places in your app using a plugin.
Its' not important, where the code resides. Just make sure its being executed and your events are properly registered & attached.
We're using a single file where all events are attached and include it from bootstrap.php, this ensures that all events are available from all locations in the app.
The magic happens when you dispatch an event, like from an controller action.
$event = new CakeEvent('Model.Order.afterPlace', $this, array('some'=>'data') ));
$this->getEventManager()->dispatch($event);
However, you can dispatch events from anywhere you can reach the EventManager (in Models, Controller and Views by default)