Events in Yii looks great, but several questions still wakes me at night:
If I raise an event and create several PHP event handler classes in chain, can I pass different data between them (like return value)?
Is the event designed for this goal? As far as I see, the event seems to be one-direction way of notification and passing data back is not a common practice, is that correct?
Lets say:
I have 3 handlers : Handler1, Handler2, Handler3 executed in this order. Each Handler concatenates some string data.
Can I pass the concatenated sting between handlers and are the handlers assumed to do this?
In a event chain, is throwing an exception in an event handler a good practice?
You're correct that the event system was primarily designed (or at least: documented) as a read-only notification system. However, it is possible to do what you want by creating your own subclassed Event that defines a public property for the data you want to pass around.
For example, start with a custom event class:
class MyEvent extends \yii\base\Event
{
public $data;
}
Trigger this event:
$event = new MyEvent([
'data' => 'hello world'
]);
$this->trigger('myEvent', $event);
echo "After passing through the entire event chain, data is now: " . $event->data;
And add behaviors (or handlers) that listen to it:
public function onMyEvent($event)
{
$event->data .= ', goodbye world';
}
If all went well, this should end up echo'ing hello world, goodbye world
Related
I'm fairly new to domain driven design concepts and I've run into a problem with returning proper responses in an API while using a command bus with commands and command handlers for the domain logic.
Let's say we’re building an application with a domain driven design approach. We have a back end and front end portion. The back end has all of our domain logic with an exposed API. The front end uses the API to make requests to the application.
We're building our domain logic with commands and command handlers mapped to a command bus. Under our Domain directory we have a command for creating a post resource called CreatePostCommand. It's mapped to its handler CreatePostCommandHandler via the command bus.
final class CreatePostCommand
{
private $title;
private $content;
public function __construct(string $title, string $content)
{
$this->title = $title;
$this->content= $content;
}
public function getTitle() : string
{
return $this->title;
}
public function getContent() : string
{
return $this->content;
}
}
final class CreatePostCommandHandler
{
private $postRepository;
public function __construct(PostRepository $postRepository)
{
$this->postRepository = $postRepository;
}
public function handle(Command $command)
{
$post = new Post($command->getTitle(), $command->getContent());
$this->postRepository->save($post);
}
}
In our API we have an endpoint for creating a post. This is routed the createPost method in a PostController under our Application directory.
final class PostController
{
private $commandBus;
public function __construct(CommandBus $commandBus)
{
$this->commandBus = $commandBus;
}
public function createPost($req, $resp)
{
$command = new CreatePostCommand($command->getTitle(), $command->getContent());
$this->commandBus->handle($command);
// How do we get the data of our newly created post to the response here?
return $resp;
}
}
Now in our createPost method we want to return the data of our newly created post in our response object so our front end application can know about the newly created resource. This is troublesome since we know that by definition the command bus should not return any data. So now we're stuck in a confusing position where we don't know how to add our new post to the response object.
I'm not sure how to proceed with this problem from here, several questions come to mind:
Is there an elegant way to return the post's data in the response?
Am I incorrectly implementing the Command/CommandHandler/CommandBus pattern?
Is this simply just the wrong use case for the Command/CommandHandler/CommandBus pattern?
First, notice that if we wire the controller directly to the command handler, we face a similar problem:
public function createPost($req, $resp)
{
$command = new CreatePostCommand($command->getTitle(), $command->getContent());
$this->createPostCommandHandler->handle($command);
// How do we get the data of our newly created post to the response here?
return $resp;
}
The bus is introducing a layer of indirection, allowing you to decouple the controller from the event handler, but the problem you are running into is more fundamental.
I'm not sure how to proceed with this problem from here
TL;DR - tell the domain what identifiers to use, rather than asking the domain what identifier was used.
public function createPost($req, $resp)
{
// TADA
$command = new CreatePostCommand($req->getPostId()
, $command->getTitle(), $command->getContent());
$this->createPostCommandHandler->handle($command);
// happy path: redirect the client to the correct url
$this->redirectTo($resp, $postId)
}
In short, the client, rather than the domain model or the persistence layer, owns the responsibility of generating the id of the new entity. The application component can read the identifier in the command itself, and use that to coordinate the next state transition.
The application, in this implementation, is simply translating the message from the DTO representation to the domain representation.
An alternative implementation uses the command identifier, and derives from that command the identities that will be used
$command = new CreatePostCommand(
$this->createPostId($req->getMessageId())
, $command->getTitle(), $command->getContent());
Named UUIDs are a common choice in the latter case; they are deterministic, and have small collision probabilities.
Now, that answer is something of a cheat -- we've really only demonstrated that we don't need a result from the command handler in this case.
In general, we would prefer to have one; Post/Redirect/Get is a good idiom to use for updating the domain model, but when the client gets the resource, we want to make sure they are getting a version that includes the edits they just made.
If your reads and writes are using the same book of record, this isn't a problem -- whatever you read is always the most recent version available.
However, cqrs is a common architectural pattern in domain driven design, in which case the write model (handling the post) will redirect to the read model -- which is usually publishing stale data. So you may want to include a minimum version in the get request, so that the handler knows to refresh its stale cache.
Is there an elegant way to return the post's data in the response?
There's an example in the code sample you provided with your question:
public function createPost($req, $resp)
Think about it: $req is a representation of the http request message, which is roughly analogous to your command, and $resp is essentially a handle to a data structure that you can write your result into.
In other words, pass a callback or a result handle with your command, and let the command handler fill in the details.
Of course, that depends on your bus supporting callbacks; not guaranteed.
Another possibility, which doesn't require changing the signature of your command handler, is to arrange that the controller subscribes to events published by the command handler. You coordinate a correlation id between the command and the event, and use that to pull up the result event that you need.
The specifics don't matter very much -- the event generated when processing the command could be written to a message bus, or copied into a mailbox, or....
I am using this approach and I am returning command results. However, this is a solution which works only if the command handlers are part of the same process. Basically, I'm using a mediator, the controller and the command handler get an instance of it (usually as a constructor dependency).
Pseudo code controller
var cmd= new MyCommand();
var listener=mediator.GetListener(cmd.Id);
bus.Send(cmd);
//wait until we get a result or timeout
var result=listener.Wait();
return result;
Pseudo code command handler function
var result= new CommandResult();
add some data here
mediator.Add(result,cmd.Id);
That's how you get immediate feedback. However, this shouldn't be used to implement a business process.
Btw, this has nothing to do with DDD, it's basically a message driven CQS approach which can be and it is used in a DDD app.
I have a custom handler that performs deserialization of my object:
public function deserialize( JsonDeserializationVisitor $visitor,
$data,
array $type,
DeserializationContext $context)
I don't want to take all deserialization job on my own, I only want to do some of that. For example, I have an Album (id, name, description, photos) and I want to deserialize "description" by myself, and left all other job to the bundle.
if (!empty($data['id']))
$album = $albumManager->createWithId($data['id']);
else
$album = $albumManager->create();
$album->setDescription($albumDescriptionParser->parse($data['description']));
// and now I want to delegate other deserialization job to JMSSerializer
// ....
return $album;
I know this is done using context, visitor and navigator, but I cant figure how
Okay, I found the way to implement that. It seems it cannot be easy handled in the deserialization handler.
Instead, I used my custom ObjectConstructorInterface interface implementation and serializer.post_deserialize event.
Creation process can be handled in the custom ObjectConstructorInterface implementation and all "extra settings" can be set after deserialization actually done - when serializer.post_deserialize event occur.
I know php and nodejs too,in javascript we have asynchronize programming ,so I understand meaning of event in it.but I saw Event in Yii and Zend 2 and use them too,but I can't understand the meaning of it,how it works in php and what exactly does in it?
First of all, there are no events in PHP
An event is an abstraction for callback functions with their name.
Typically, we'd define them as $eventName => $listener, where $listener is a callback function for the $eventName
What is the difference between events and regular callback functions?
Again - the core point to remember, is that events are callback functions. Nothing more.
The only difference between them, is how we do invoke them.
An event is defined on bootstrap step with its required arguments, but invoked on demand without arguments. While the callback function is invoked with arguments and only
Consider this example,
<?php
$eventManager = new EventManager();
$eventManager->attach('my_event', function(){
print_r(func_get_args());
}, array('foo', 'bar'));
As we have just defined an event, we'd invoke like,
$eventManager->trigger('my_event');
This will output: Array([0] => [foo], [1] => [bar]
That's related to JavaScript!
Since most of us are familiar with JavaScript even-driven architecture, its worth nothing to mention an example of its common usage:
var a = document.getElementsByTagName('a')[0];
a.onclick = function(event) { // <-- We define an event with the event argument
event.preventDefault();
alert('A element was clicked');
}
a.click(); // <-- but we invoke it without arguments
// or If you want a Jquery
$("a").click(function(event){
event.preventDefault();
alert('A element was clicked');
});
$("a").click();
Since in PHP we don't have such event-driven nature, we can replace it with our own class that manage events and takes a full advantage of it.
Why use them?
While events confuse so many people, they are extremely useful.
Imagine you have a Content Management System (CMS), where your users can decide how to handle 404 errors. Say, they can handle with
1) Show a blank page
2) Redirect to /
3) Show a custom message
Without events you would have to do it, like
if ($router->isMatched($request)){
//do dispatch etc
} else {
// Here you start handling 404 errors
switch($config->read('404_way_handle')){
case 'show_blank':
die();
break;
case 'show_msg':
echo 'Some custom message';
break;
case 'redirect':
// do redirect
break;
}
}
With an event you can simplify the readability and keep the code more maintainable:
if ($router->isMatched($request)){
// do dispatch
} else {
$eventManager->trigger('404_handler');
}
while 404_handler itself looks like
$eventManager->attach('404_handler', function(){
switch($config->read('404_way_handle')){
case 'show_blank':
die();
break;
case 'show_msg':
echo 'Some custom message';
break;
case 'redirect':
// do redirect
break;
}
}, $config);
So let's break it down
1) Events improve readability, which is great for future
2) Events do adhere to the Single-Responsibility Principle, because you can simply inject $eventManager to your classes that need it, while callback functions could break it or could introduce a global state too (Which is bad for unit-testings).
3) There are distinct types of logic - template logic, business logic, error handler logic, data access logic etc etc. Events do simplify your application logic by decoupling business (or another kind) logic from its configuration logic, so that you end up with clear application logic.
You can watch this lecture if you want to know how they do work in Zend Framework 2 (watch it even if you're not familiar with Zend Framework 2)
Events in MVC-related architectures
Since you've been talking about frameworks, its worth nothing to mention, that there could be events in MVC-related architectures too. And since events are callback functions you can abstract common boostrap events in your MVC-like architecture, like this.
$mvcEvent->on(MVC_EVENT::ROUTE_MATCH, function(){
$mvcEvent->on(MVC_EVENT::DISTPATCH, function($content){
echo $mvcEvent->trigger(MVC_EVENT::RENDER, $content);
});
});
Note : In pure MVC theory, there are no events at all. They do acts as helpers, but again - in frameworks you can abstract them and call them "events".
This article helped me understrand EventManager in ZF2:
The Event Manager is the component of the framework which allows you to hook in to named events in your application.
There are an existing set of named events in the framework, such as the dispatch event in controllers. You can also create your own as suits your application’s purpose. That’s step one.
Then, you attach (or listen) to those events. When they fire – or are triggered – your code interrogates the context of the event and responds if needed.
reference: http://www.maltblue.com/tutorial/zend-framework-2-event-manager-a-gentle-introduction
I am impressed by the flexibility of Yii events. I am new to Yii and I want to know how to pass parameters to Yii event handlers?
//i have onLogin event defined in the login model
public function onLogin($event)
{
$this->raiseEvent("onLogin", $event);
}
I have a login handler defined in the handler class. This event handler method takes a parameter:
function loginHandler($param1)
{
...
}
But here, I am confused as to how to pass a parameter to the login event handler:
//i attach login handler like this.
$loginModel->onLogin = array("handlers", "loginHandler");
$e = new CEvent($this);
$loginModel->onLogin($e);
Am I doing something wrong? Is there another approach for this?
Now I have an answer for my own question. CEvent class has a public property called params where we can add additional data while passing it to event handler.
//while creating new instance of an event I could do this
$params = array("name" => "My Name");
$e = new CEvent($this, $params);
$loginModel->onLogin($e);
//adding second parameter will allow me to add data which can be accessed in
// event handler function. loginHandler in this case.
function loginHandler($event)// now I have an event parameter.
{
echo $event->params['name']; //will print : "My Name"
}
If you want to use onBeginRequest and onEndRequest you can do it by adding the next lines into your config file:
return array (
'onBeginRequest'=>array('Y', 'getStats'),
'onEndRequest'=>array('Y', 'writeStats'),
)
or you can do it inline
Yii::app()->onBeginRequest= array('Y', 'getStats');
Yii::app()->onEndRequest= array('Y', 'writeStats');`class Y {
public function getStats ($event) {
// Here you put all needed code to start stats collection
}
public function writeStats ($event) {
// Here you put all needed code to save collected stats
}
}`
So on every request both methods will run automatically. Of course you can think "why not simply overload onBeginRequest method?" but first of all events allow you to not extend class to run some repeated code and also they allow you to execute different methods of different classes declared in different places. So you can add
Yii::app()->onEndRequest= array('YClass', 'someMethod');
at any other part of your application along with previous event handlers and you will get run both Y->writeStats and YClass->someMethod after request processing. This with behaviors allows you create extension components of almost any complexity without changing source code and without extension of base classes of Yii.
I have a logger class (in PHP, but that does not matter) which spits out log messages. The logger is a custom implementation and works well. However, I would like to extend it to provide a event ID with each type of logger message. Such that "User logged in messages" are event ID 1, "Form validation failed" is event ID 2, for example.
The purpose of this event ID is to filter unimportant events when a long list of logs is viewed. I believe it would be quicker to categorize logs by event ID and faster to query than doing substring searches in a database.
I have the following ideas, but welcome viable alternatives. I used PHP in this example, but something that is generic enough that it could be applied to most languages such as PHP/Java/C#/etc would be useful.
1) Include an ID implicitly in the log function call:
abstract class EventId {
const LOGIN = 1;
const VALIDATION_FAILURE = 2;
// etc
}
Logger::messageDebug(EventID::LOGIN, $username . " logged in");
Logger::messageWarning(EventID::VALIDATION_FAILURE, "Form failed to validate, etc.");
The advantages of this are simplicity, but I fear it could get a little messy.
2) Pass variable parameters to log messages as separate arguments, then hash the first string
Logger::messageDebug("%s logged in", $username);
Logger::messageWarning("The %s form failed to validate", $form);
The advantages are simplicity, the disadvantages are the event ID depends upon hashing (or some other function) of the first string to differentiate the types of log messages.
3) Backtrace and do some nastyness
function messageDebug($message) {
$trace = obtainTraceOfCallToMessageDebug();
$callToMessageDebug = $trace[0];
$eventId = "";
$eventId .= $eventId->file;
$eventId .= $eventId->line;
$eventId = sha1($eventId);
messageImpl($eventId, $message);
}
This is pretty nasty, but it does have an advantage in that existing log messages do not need to be changed, additionally it is less fragile when writing new log messages. In PHP, obtaining a backtrace is easy and sort of cheap, in other languages you may have to throw an Exception which is super nasty. Anyway, it's an idea.
Looking forward to your opinions. My main requirement is maintainability rather than speed. I am currently looking towards number 1 - keep it simple.
I'd go with the first one with a bit of modification:
Assuming that you called your logger with statements like :
Logger::messageWarning($message)
I'd replace that with
Logger::messageWarning($message, EventId:someconstant)
and defining your EventId class as
abstract class EventId {
const UNSPECIFIED = 1;
const LOGIN = 2;
const VALIDATION_FAILURE = 3;
// etc
}
and you logger function would accept a second parameter, but use a default value of EventId:UNSPECIFIED for that.
class Logger {
...
function messageWarning($message, $message_type = EventId:UNSPECIFIED) {
...
}
}
This way you don't break existing code, don't do the hack outlined in #3, and can replace your log calls where and when you need by adding the event type.