I'm trying to get familiar with unit testing in PHP with a small API in Lumen.
Writing the first few tests was pretty nice with the help of some tutorials but now I encountered a point where I have to mock/ stub a dependency.
My controller depends on a specific custom interface type hinted in the constructor.
Of course, I defined this interface/implementation-binding within a ServiceProvider.
public function __construct(CustomValidatorContract $validator)
{
// App\Contracts\CustomValidatorContract
$this->validator = $validator;
}
public function resize(Request $request)
{
// Illuminate\Contracts\Validation\Validator
$validation = $this->validator->validate($request->all());
if ($validation->fails()) {
$response = array_merge(
$validation
->errors() // Illuminate\Support\MessageBag
->toArray(),
['error' => 'Invalid request data.']
);
// response is global helper
return response()->json($response, 400, ['Content-Type' => 'application/json']);
}
}
As you can see, my CustomValidatorContract has a method validate() which returns an instance of Illuminate\Contracts\Validation\Validator (the validation result). This in turn returns an instance of Illuminate\Support\MessageBag when errors() is called. MessageBag then has a toArray()-method.
Now I want to test the behavior of my controller in case the validation fails.
/** #test */
public function failing_validation_returns_400()
{
$EmptyErrorMessageBag = $this->createMock(MessageBag::class);
$EmptyErrorMessageBag
->expects($this->any())
->method('toArray')
->willReturn(array());
/** #var ValidationResult&\PHPUnit\Framework\MockObject\MockObject $AlwaysFailsTrueValidationResult */
$AlwaysFailsTrueValidationResult = $this->createStub(ValidationResult::class);
$AlwaysFailsTrueValidationResult
->expects($this->atLeastOnce())
->method('fails')
->willReturn(true);
$AlwaysFailsTrueValidationResult
->expects($this->atLeastOnce())
->method('errors')
->willReturn($EmptyErrorMessageBag);
/** #var Validator&\PHPUnit\Framework\MockObject\MockObject $CustomValidatorAlwaysFailsTrue */
$CustomValidatorAlwaysFailsTrue = $this->createStub(Validator::class);
$CustomValidatorAlwaysFailsTrue
->expects($this->once())
->method('validate')
->willReturn($AlwaysFailsTrueValidationResult);
$controller = new ImageResizeController($CustomValidatorAlwaysFailsTrue);
$response = $controller->resize(new Request);
$this->assertEquals(400, $response->status());
$this->assertEquals(
'application/json',
$response->headers->get('Content-Type')
);
$this->assertJson($response->getContent());
$response = json_decode($response->getContent(), true);
$this->assertArrayHasKey('error', $response);
}
This is a test that runs ok - but can someone please tell me if there is a better way to write this? It doesn't feel right.
Is this big stack of moc-objects needed because of the fact that I'm using a framework in the background? Or is there something wrong with my architecture so that this feels so "overengineered"?
Thanks
What you are doing is not unit testing because you are not testing a single unit of your application. This is an integration test, performed with unit testing framework, and this is the reason it looks intuitively wrong.
Unit testing and integration testing happen at different times, at different places and require different approaches and tools - the former tests every single class and function of your code, while latter couldn't care less about those, they just request APIs and validate responses. Also, IT doesn't imply mocking anything because it's goal is to test how well your units integrate with each other.
You'll have hard time supporting tests like that because every time you change CustomValidatorContract you'll have to fix all the tests involving it. This is how UT improves code design by requiring it to be as loosely coupled as possible (so you could pick a single unit and use it without the need to boot entire app), respecting SRP & OCP, etc.
You don't need to test 3rd party code, pick an already tested one instead. You don't need to test side effects either, because environment is just like 3rd party service, it should be tested separately (return response() is a side effect). Also it seriously slows down the testing.
All that leads to the idea that you only want to test your CustomValidatorContract in isolation. You don't even need to mock anything there, just instantiate the validator, give it few sets of input data and check how it goes.
This is a test that runs ok - but can someone please tell me if there is a better way to write this? It doesn't feel right. Is this big stack of moc-objects needed because of the fact that I'm using a framework in the background? Or is there something wrong with my architecture so that this feels so "overengineered"?
The big stack of mock objects indicates that your test subject is tightly coupled to many different things.
If you want to support simpler tests, then you need to make the design simpler.
In other words, instead of Controller.resize being one enormous monolithic thing that knows all of the details about everything, think about a design where resize only knows about the surface of things, and how to delegate work to other (more easily tested) pieces.
This is normal, in the sense that TDD is a lot about choosing designs that support better testing.
Related
I am working on an extension (app) of nextcloud (which is based on Symfony). I have a helper class to extract data from the request that is passed by the HTTP server to PHP. A much-reduced one could be something like this (to get the point here):
<?php
namespace OCA\Cookbook\Helpers;
class RequestHelper {
public function getJson(){
if($_SERVER['Request_Method' === 'PUT'){ // Notice the typos, should be REQUEST_METHOD
$raw = file_get_content('php://input');
return json_decode($raw, true);
} else { /* ... */ }
}
}
Now I want to test this code. Of course, I can do some unit testing and mock the $_SERVER variable. Potentially I would have to extarct the file_get_content into its own method and do a partial mock of that class. I get that. The question is: How much is this test worth?
If I just mimick the behavior of that class (white box testing) in my test cases I might even copy and paste the typo I intentionally included here. As this code is an MWE, real code might get more complex and should be compatible with different HTTP servers (like apache, nginx, lighttpd etc).
So, ideally, I would like to do some automated testing in my CI process that uses a real HTTP server with different versions/programs to see if the integration is working correctly. Welcome to integration testing.
I could now run the nextcloud server with my extension included in a test environment and test some real API endpoints. This is more like functional testing as everything is tested (server, NC core, my code and the DB):
phpunit <---> HTTP server <---> nextcloud core <---> extension code <---> DB
^
|
+--> RequestHelper
Apart from speed, I have to carefully take into account to test all possible paths through the class RequestHelper (device under test, DUT). This seems a bit brittle to me in the long run.
All I could think of is adding a simple endpoint only for testing the functionality of the DUT, something like a pure echo endpoint or so. For the production use, I do not feel comfortable having something like this laying around.
I am therefore looking for an integration test with a partial mock of the app (mocking the business logic + DB) to test the route between the HTTP server and my DUT. In other words, I want to test the integration of the HTTP server, nextcloud core, my controller, and the DUT above without any business logic of my app.
How can I realize such test cases?
Edit 1
As I found from the comments the problem statement was not so obviously clear, I try to explain a bit at the cost of the simplicity of the use-case.
There is the nextcloud core that can be seen as a framework from the perspective of the app. So, there can be controller classes that can be used as targets for URL/API endpoints. So for example /apps/cookbook/recipe/15 with a GET method will fetch the recipe with id 15. Similarly, with PUT there can be a JSON uploaded to update that recipe.
So, inside the corresponding controller the structure is like
class RecipeController extends Controller {
/* Here the PUT /apps/cookbook/recipe/{id} endpoint will be routed */
public function update($id){
$json = $this->requestHelper->getJson(); // Call to helper
// Here comes the business logic
// aka calls to other classes that will save and update the state
// and perform the DB operation
$this->service->doSomething($json);
// Return an answer if the operation terminated successfully
return JsonResponse(['state'=>'ok'], 200);
}
}
I want to test the getJson() method against different servers. Here I want to mock at least the $this->service->doSomething($json) to be a no-op. Ideally, I would like to spy into the resulting $json variable to test that exactly.
No doubt, in my test class it would be something like
class TestResponseHandler extends TestCase {
public function setUp() { /* Set up the http deamon as system service */}
public testGetJson() {
// Creat Guzzle client
$client = new Client([
'base_uri' => 'http://localhost:8080/apps/cookbook',
]);
// Run the API call
$headers = ...;
$body = ...;
$response = $client->put('recipe/15', 'PUT', $headers, $body);
// Check the response body
// ....
}
}
Now, I have two code interpreters running: Once, there is the one (A) that runs phpunit (and makes the HTTP request). Second, there is the one (B) associated with the HTTP server listening on localhost:8080.
As the code above with the call to getJson() is running inside a PHP interpreter (B) outside the phpunit instance I cannot mock directly as far as I understand. I would have to change the main app's code if I am not mistaken.
Of course, I could provide (more or less) useful data in the test function and let the service->doSomething() method do its job but then I am no longer testing only a subset of functions but I am doing functional or system testing. Also, this makes it harder to generate well-aimed test cases if all these side-effects need to be taken into account.
I am testing the endpoints/API of a web application. I have multiple small tests that depend on the return values of the preceding tests. Some of the tests even depend on side effects that are made by the preceding tests. Here's an example of how it goes (the numbered list items represent individual test cases):
Make a request to an endpoint and assert the http code is 200, return the response
Parse the response body and do some assertions on it, return the parsed response body
Do some assertions on the debug values of the parsed response body
Make a new request to another endpoint and assert the http code is 200, return the response
Parse the response body and assert that the side effects from test 1 actually took place
As you can see the tests sort of propagate from test 1, and all the other tests depend on its return value or side effects.
Now I want to execute these tests with data from a data provider to test the behavior with multiple users from our application. According to the phpunit documentation, this is not possible. From the docs:
When a test depends on a test that uses data providers, the depending test will be executed when the test it depends upon is successful for at least one data set. The result of a test that uses data providers cannot be injected into a depending test.
Just to be clear, what I want is for test 1 to execute x number of times with y values, and have all the other tests propagate its return value or check its side effects each time.
After some googling, the only solution that comes to mind is to put all the tests into one single test to remove all dependencies. However I have multiple test suites with this behavior, and some of the tests would get really big and unwieldy.
So, how can I keep the dependencies between small test cases while using data providers? I'm using php 5.5 along with Silex 1.3 and phpunit 4.8
EDIT: I should mention that my tests are extending Silex' WebTestCase, though I'm not sure if it makes a difference.
Here's an example in case I was unclear:
public function testValidResponse()
{
$client = $this->createClient();
$client->request('POST', '/foo', $this->params);
$this->assertEquals(200, $client->getResponse()->getStatusCode());
return $client->getResponse();
}
/**
* #depends testValidResponse
*/
public function testStatusIsOk(Response $response)
{
$json = json_decode($response->getContent(), true);
$this->assertTrue($json['status']);
return $json;
}
/**
* #depends testStatusIsOk
*/
public function testExecutionTime($json)
{
$this->assertLessThan($this->maxExecutionTime, $json['debug']['executionTimeSec']);
}
/**
* #depends testValidResponse
*/
public function testAnotherEndpointValidResponse()
{
$client = $this->createClient();
$client->request('GET', '/bar');
$this->assertEquals(200, $client->getResponse()->getStatusCode());
return $client->getResponse();
}
/**
* #depends testAnotherEndpointValidResponse
*/
public function testSideEffectsFromFirstTest(Response $response)
{
// ...
}
I think the main problem is that the tests are too complex, and should not depend that much on each other. The solution was to reduce some of the complexity by refactoring the tests. Here's a rough outline of what I did:
Moved some of the more complex test cases from Integration testing
to Unit testing. So I'm not testing the endpoint any more, but the methods that are executed when you go to the endpoint.
Added a generic test case that operates on data from a data provider which includes, not exclusively, the urls to the different endpoints. This test case simply tests that the endpoints are returning the expected http code and some other small things. This allowed me to put all of my Integration testing into one simple test case.
I'm pretty happy with the solution, I deleted 7-8 test classes, and the complex testing that got moved to Unit testing is also simplified.
I am trying to test some of my controllers through Unit Testing. But there is something strange happening. With the following code in my testcase:
public function test_username_registration_too_short()
{
$result = $this->action('POST', 'App\\Controllers\\API\\UserController#store', null, [
'username' => 'foo'
]);
$this->assertEquals('not_saved', $result->getContent());
// $result = $this->action('POST', 'App\\Controllers\\API\\UserController#store', null, [
// 'username' => 'foo'
// ]);
// $this->assertEquals('not_saved', $result->getContent());
}
public function test_username_registration_too_short_run_2()
{
$result = $this->action('POST', 'App\\Controllers\\API\\UserController#store', null, [
'username' => 'foo'
]);
$this->assertEquals('not_saved', $result->getContent());
}
When I run this, the initial too_short test passes, but the exact same code on run 2 does not pass (it even manages to save the user). But if I put that same code twice in the same method (what is commented out now) it works perfectly? I have nothing in my setUp or tearDown methods. And I am a bit lost here.
The code in the controller is the following:
$user = new User(Input::all());
if($user->save())
{
return 'saved';
}
return 'not_saved';
I'm not going to stop repeating myself over this question. There's a similar answer to a (somewhat) similar question. TL;DR: don't use unit testing framework for functional / integration testing.
This is area of functional testing and there is a fabulous framework
called Behat. You should do your own research, but essentially, while
PHPUnit is great at testing more or less independent blocks of
functionality it sucks at testing bigger things like full request
execution. Later you will start experiencing issues with session
errors, misconfigured environment, etc., all because each request is
supposed to be executed in it's own separate space and you force it
into doing the opposite. Behat on the other hand works in a very
different way, where for each scenario (post robot, view non-existing
page), it sends a fresh request to the server and checks the result.
It is mostly used for final testing of everything working together by
making assertions on the final result (response object / html / json).
If you want to test your code the proper way consider using the right tools for that. Once you know your way around with Behat you'll fall in love with it + you can use PHPUnit from within the Behat, to make individual assertions.
I have been starting unit testing recently and am wondering, should I be writing unit tests for 100% code coverage?
This seems futile when I end up writing more unit testing code than production code.
I am writing a PHP Codeigniter project and sometimes it seems I write so much code just to test one small function.
For Example this Unit test
public function testLogin(){
//setup
$this->CI->load->library("form_validation");
$this->realFormValidation=new $this->CI->form_validation;
$this->CI->form_validation=$this->getMock("CI_Form_validation");
$this->realAuth=new $this->CI->auth;
$this->CI->auth=$this->getMock("Auth",array("logIn"));
$this->CI->auth->expects($this->once())
->method("logIn")
->will($this->returnValue(TRUE));
//test
$this->CI->form_validation->expects($this->once())
->method("run")
->will($this->returnValue(TRUE));
$_POST["login"]=TRUE;
$this->CI->login();
$out = $this->CI->output->get_headers();
//check new header ends with dashboard
$this->assertStringEndsWith("dashboard",$out[0][0]);
//tear down
$this->CI->form_validation=$this->realFormValidation;
$this->CI->auth=$this->realAuth;
}
public function badLoginProvider(){
return array(
array(FALSE,FALSE),
array(TRUE,FALSE)
);
}
/**
* #dataProvider badLoginProvider
*/
public function testBadLogin($formSubmitted,$validationResult){
//setup
$this->CI->load->library("form_validation");
$this->realFormValidation=new $this->CI->form_validation;
$this->CI->form_validation=$this->getMock("CI_Form_validation");
//test
$this->CI->form_validation->expects($this->any())
->method("run")
->will($this->returnValue($validationResult));
$_POST["login"]=$formSubmitted;
$this->CI->login();
//check it went to the login page
$out = output();
$this->assertGreaterThan(0, preg_match('/Login/i', $out));
//tear down
$this->CI->form_validation=$this->realFormValidation;
}
For this production code
public function login(){
if($this->input->post("login")){
$this->load->library('form_validation');
$username=$this->input->post('username');
$this->form_validation->set_rules('username', 'Username', 'required');
$this->form_validation->set_rules('password', 'Password', "required|callback_userPassCheck[$username]");
if ($this->form_validation->run()===FALSE) {
$this->load->helper("form");
$this->load->view('dashboard/login');
}
else{
$this->load->model('auth');
echo "valid";
$this->auth->logIn($this->input->post('username'),$this->input->post('password'),$this->input->post('remember_me'));
$this->load->helper('url');
redirect('dashboard');
}
}
else{
$this->load->helper("form");
$this->load->view('dashboard/login');
}
}
Where am I going so wrong?
In my opinion, it's normal for test code to be more than production code. But test code tends to be straightforward, once you get the hang of it, it's like a no brainer task to write tests.
Having said that, if you discover your test code is too complicated to write/to cover all the execution paths in your production code, that's a good indicator for some refactoring: your method may be too long, or attempts to do several things, or has so many external dependencies, etc...
Another point is that it's good to have high test coverage, but does not need to be 100% or some very high number. Sometimes there are code that has no logic, like code that simply delegates tasks to others. In that case you can skip testing them and use #codeCoverageIgnore annotation to ignore them in your code coverage.
In my opinion its logical that test are much more code because you have to test multiple scenarios, must provide test data and you have to check that data for every case.
Typically a test-coverage of 80% is a good value. In most cases its not necessary to test 100% of the code because you should not test for example setters and getter. Dont test just for the statistics ;)
The answer is it depends, but generally no. If you are publishing a library then lots of tests are important and can even help create the examples for the docs.
Internal projects you would probably want to focus your code around complex functions and things which would be bad if they were to go wrong. For each test thing what is the value in having the test here rather than in the parent function?
What you would want to avoid is testing too much is anything that relies on implementation details, or say private method/functions, otherwise if you change the structure you'll find you'll have to repeatedly rewrite the entire suite of tests.
It is better to test at a higher level, the public functions or anything which is at the boundary between modules, a few tests at the highest level you can should yield reasonable converge and ensure the code works in the way that your code is actually called. This is not to say lower level functions shouldn't have tests but at that level it's more to check edge cases than to have a test for the typical case against every function.
Instead of creating tests to increase coverage, create tests to cover bugs as you find and fix them, create tests against new functionality when you would have to manually test it anyway. Create tests to guard against bad things which must not happen. Fragile tests which easily break during refactors should be removed or changed to be less dependant on the implementation of the function.
I've seen a lot of people saying that Symfony2, Zend Framework 2 and others are event-driven.
On the desktop world, by event-driven programming I understand that the application will notify its observers whenever its state changes.
Since PHP applications are state-less there's no way to do such thing. I.E. Having observers tied to the view observing changes while the user uses the interface. Instead it needs a new request process in order to update the view. So, it's not an event but a whole new request.
On the other hand there's a similar concept: Event-driven architecture.
Here you can read both:
http://en.wikipedia.org/wiki/Event-driven_programming
http://en.wikipedia.org/wiki/Event-driven_architecture
And here the other one:
http://en.wikipedia.org/wiki/Signal_programming
A signal is a notification to a process that an event occurred.
Signals are sometimes described as software interrupts. Signals are
analogous to hardware interrupts in that they interrupt the normal
flow of execution of a program; in most cases, it is not possible to
predict exactly when a signal will arrive.
Stackoverflow [singals] tag description
Moreover, what I used to call event-driven seems to be more related to the Signals and Slots Pattern introduced by Qt (observer pattern implementation)
As an example, there's the Prado Framework which claims to be event-driven:
http://www.pradosoft.com/demos/quickstart/?page=Fundamentals.Applications (Application Lifecycles section)
http://www.pradosoft.com/docs/manual/System/TApplication.html#methodonEndRequest
IIRC, this isn't an event-driven application, but instead just plug-in hooks (signals and slots) used by classes that implements the observable Interface. I mean, considering the way desktop applications use events and the way state-less applications use events (as plugi-ns): The first use events for the whole application, including views, the last just for server-side operations.
One is more related to Aspect-oriented programming (with signals and slots) and the other is not specifically tied to cross-cutting concerns/AOP. In other words, it's more related to the application state.
So, what is in fact the relation between these terms and how they differ from each other?
Event-driven programming
Event-driven architecture
Signals and Slots Pattern
Are these terms just generic patterns? Hence, everything that implements the observer pattern can be considered event-driven?
UPDATE
Zend Framework 2
The article about AOP I've linked above
( http://mwop.net/blog/251-Aspects,-Filters,-and-Signals,-Oh,-My!.html )
was written by Matthew Weier O'Phinney (ZF Leader). IIRC, it doesn't
have mentions about "event-driven", just signal and slots.
Symfony 2
The Symfony2 EventDispatcher component description doesn't have mentions about
being for "event-driven" applications:
http://symfony.com/doc/current/components/event_dispatcher/introduction.html
It only contains references to "Events" (which, indeed, are handled by Signal and Slots).
Both frameworks seem to use the Intercepting Filter Pattern within the Signal and Slots in order to handle synchronous events during the request process.
Disclaimer: It's a long answer, but I think it's worth the read with
all its references. And IMHO it leads to a conclusive answer.
I've been struggling upon this subjects the last couple of days and, if I've read all correctly, the answer is:
Event-driven !== Request-driven
"[...] I find this the most interesting difference in event
collaboration, to paraphrase Jon Udell: request driven software speaks
when spoken to, event driven software speaks when it has something to say.
A consequence of this is that the responsibility of managing state
shifts. In request collaboration you strive to ensure that every piece
of data has one home, and you look it up from that home if you want
it. This home is responsible for the structure of data, how long it's
stored, how to access it. In the event collaboration scenario the
source of new data is welcome to forget the data the second it's
passed to its Message Endpoint."
Martin Fowler - Event Collaboration (Queries section)
Based on that assertion, IIRC, modern PHP frameworks implements the Observer Pattern + Intercepting Filters + Singal and Slots, in order to trigger some events during the request cycle.
But, despite the fact that it adopts some ideas of event-driven architectures, it doesn't seems to support that the whole framework is event-driven (i.e. Symfony2 is an event-driven framewrok).
We are used to dividing programs into multiple components that
collaborate together. (I'm using the vague 'component' word here
deliberately since in this context I mean many things: including
objects within a program and multiple processes communicating across a
network.) The most common way of making them collaborate is a
request/response style. If a customer object wants some data from a
salesman object, it invokes a method on the salesman object to ask it
for that data.
Another style of collaboration is Event Collaboration. In this style
you never have one component asking another to do anything, instead
each component signals an event when anything changes. Other
components listen to that event and react however they wish to. The
well-known observer pattern is an example of Event Collaboration.
Martin Fowler - Focus on events (section: Using events to
collaborate)
I think that PHP applications are more closely to be event-driven than request-driven only when the focus are on events. If those applications/frameworks are only using events for cross-cutting concerns (AOP), then it's not event-driven. In the same way you would not call it test-driven or domain-driven just because you have some domain objects and unit tests.
Real world examples
I've picked some examples to show why those frameworks are not fully event-driven. Despite AOP events, everything is request-driven:
Note: Although, it can be adapted to be event-driven
Zend Framework 2
Let's examine the \Zend\Mvc\Application component:
It implements the \Zend\EventManager\EventManagerAwareInterface and relies on the \Zend\Mvc\MvcEvent which describes the possible events:
class MvcEvent extends Event
{
/**##+
* Mvc events triggered by eventmanager
*/
const EVENT_BOOTSTRAP = 'bootstrap';
const EVENT_DISPATCH = 'dispatch';
const EVENT_DISPATCH_ERROR = 'dispatch.error';
const EVENT_FINISH = 'finish';
const EVENT_RENDER = 'render';
const EVENT_ROUTE = 'route';
// [...]
}
The \Zend\Mvc\Application component itself is event-driven because it doesn't communicates directly with the other components, but instead, it just triggers events:
/**
* Run the application
*
* #triggers route(MvcEvent)
* Routes the request, and sets the RouteMatch object in the event.
* #triggers dispatch(MvcEvent)
* Dispatches a request, using the discovered RouteMatch and
* provided request.
* #triggers dispatch.error(MvcEvent)
* On errors (controller not found, action not supported, etc.),
* populates the event with information about the error type,
* discovered controller, and controller class (if known).
* Typically, a handler should return a populated Response object
* that can be returned immediately.
* #return ResponseInterface
*/
public function run()
{
$events = $this->getEventManager();
$event = $this->getMvcEvent();
// Define callback used to determine whether or not to short-circuit
$shortCircuit = function ($r) use ($event) {
if ($r instanceof ResponseInterface) {
return true;
}
if ($event->getError()) {
return true;
}
return false;
};
// Trigger route event
$result = $events->trigger(MvcEvent::EVENT_ROUTE, $event, $shortCircuit);
if ($result->stopped()) {
$response = $result->last();
if ($response instanceof ResponseInterface) {
$event->setTarget($this);
$events->trigger(MvcEvent::EVENT_FINISH, $event);
return $response;
}
if ($event->getError()) {
return $this->completeRequest($event);
}
return $event->getResponse();
}
if ($event->getError()) {
return $this->completeRequest($event);
}
// Trigger dispatch event
$result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit);
// Complete response
$response = $result->last();
if ($response instanceof ResponseInterface) {
$event->setTarget($this);
$events->trigger(MvcEvent::EVENT_FINISH, $event);
return $response;
}
$response = $this->getResponse();
$event->setResponse($response);
return $this->completeRequest($event);
}
That's event-driven: You have no clue looking at the code of which router, dispatcher and view renderer will be used, you only know that those events will be triggered. You could hook almost any compatible component to listen and process the events. There's no direct communication between components.
But, there's one important thing to note: This is the presentation layer (Controller + View). The domain layer indeed can be event-driven, but that's not the case of almost all applications you see out there. **There's a mix between event-driven and request-driven:
// albums controller
public function indexAction()
{
return new ViewModel(array(
'albums' => $this->albumsService->getAlbumsFromArtist('Joy Division'),
));
}
The controller component is not event-driven. It communicates directly with the service component. Instead the services should subscribe to events risen by controllers, which is part of the presentation layer. (I'll point out the references about what would be an event-driven domain model at the end of this answer).
Symfony 2
Now let's examine the same on the Symfony2 Application/FrontController: \Symfony\Component\HttpKernel\HttpKernel
It indeed has the main events during the request: Symfony\Component\HttpKernel\KernelEvents
/**
* Handles a request to convert it to a response.
*
* Exceptions are not caught.
*
* #param Request $request A Request instance
* #param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
*
* #return Response A Response instance
*
* #throws \LogicException If one of the listener does not behave as expected
* #throws NotFoundHttpException When controller cannot be found
*/
private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
// request
$event = new GetResponseEvent($this, $request, $type);
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
if ($event->hasResponse()) {
return $this->filterResponse($event->getResponse(), $request, $type);
}
// load controller
if (false === $controller = $this->resolver->getController($request)) {
throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". Maybe you forgot to add the matching route in your routing configuration?', $request->getPathInfo()));
}
$event = new FilterControllerEvent($this, $controller, $request, $type);
$this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);
$controller = $event->getController();
// controller arguments
$arguments = $this->resolver->getArguments($request, $controller);
// call controller
$response = call_user_func_array($controller, $arguments);
// view
if (!$response instanceof Response) {
$event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
$this->dispatcher->dispatch(KernelEvents::VIEW, $event);
if ($event->hasResponse()) {
$response = $event->getResponse();
}
if (!$response instanceof Response) {
$msg = sprintf('The controller must return a response (%s given).', $this->varToString($response));
// the user may have forgotten to return something
if (null === $response) {
$msg .= ' Did you forget to add a return statement somewhere in your controller?';
}
throw new \LogicException($msg);
}
}
return $this->filterResponse($response, $request, $type);
}
But besides of being "event-capable" it communicates directly with the ControllerResolver component, hence it's not fully event-driven since the beginning of the request process, though it triggers some events and allows some components to be pluggable (which is not the case of the ControllerResolver that's injected as a constructor parameter).
Instead, to be a fully event-driven component it should be as in ZF2 Application component:
// Trigger dispatch event
$result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit);
Prado
I haven't enough time to investigate the source code, but at first it doesn't seems to be built in a SOLID way. Either way, what's a controller on MVC-alike frameworks, Prado calls it a TPage (Not sure yet):
http://www.pradosoft.com/demos/blog-tutorial/?page=Day3.CreateNewUser
And it indeed communicates directly with components:
class NewUser extends TPage
{
/**
* Checks whether the username exists in the database.
* This method responds to the OnServerValidate event of username's custom validator.
* #param mixed event sender
* #param mixed event parameter
*/
public function checkUsername($sender,$param)
{
// valid if the username is not found in the database
$param->IsValid=UserRecord::finder()->findByPk($this->Username->Text)===null;
}
[...]
}
I understand that the TPage is an event listener and can be pluggable. But it doesn't make your domain model event-driven. So I think that, in some extent, it's more close to the ZF2 proposal.
Event-driven examples
To finish this long answer, here's what a full-blown event-driven application would have:
Decoupling applications with Domains Events
http://www.whitewashing.de/2012/08/25/decoupling_applications_with_domain_events.html
Event sourcing
http://martinfowler.com/eaaDev/EventSourcing.html
Domain Event Pattern
http://martinfowler.com/eaaDev/DomainEvent.html
Event Collaboration
http://martinfowler.com/eaaDev/EventCollaboration.html
Event Interception
http://martinfowler.com/bliki/EventInterception.html
Message Endpoint
http://www.enterpriseintegrationpatterns.com/MessageEndpoint.html
... and so on
PHP is not stateless, HTTP is. To state it simply, we have essentially built a layer on top of a stateless technology upon which we can implement stateful designs. Taken together, PHP and your datastore of choice have all the tools they need to build an application design based on event driven patterns via the tokenization of sessions.
In a highly generalized way, you can think of HTTP as being for the web what BIOS has been for desktop computing. In fact, take this just a little bit further, and you can easily see the implicit event-driven nature of the web. You said "it's not an event but a whole new request", and I return with, "a whole new request is an event", and I mean that in the design pattern sense of the word. It has concrete semantic meaning relating to your user's interaction with your application.
Essentially, through patterns like MVC and Front Controller (and by the mechanism of HTTP cookies and PHP sessions), we simply restore session state, and then respond to the event, modifying that state accordingly.
I like to contemplate the essence of REST: Representational State Transfer... but I would add that we should not forget the implicit implication that state is only transferred when a UI event has occurred. So we maintain the contracts we have with HTTP that we "speak" only in "Representational States" of our model (i.e. a document, JSON, etc), but that is only our dialect. Other systems choose to speak in canvas coordinates, signal db, etc.
edit/more thoughts
So I've been pondering it for a while and I think there is a concept which illustrates a little of the ambiguity when discussing these patterns in the realm of PHP via HTTP: determinism. Specifically, once a request is received, the path of PHP execution is deterministic, and this is exactly why it's remarkably difficult to consider an "event-driven" architecture in PHP. My notion is that we should consider one level higher than PHP, to the larger "session" of interaction with the user.
In desktop computing, we use runloops and a state-ful context to "wait" for events. However, I will argue that the web is actually an improvement over this architecture (in most cases) but ultimately the same pattern. Instead of a runloop and infinite-duration state, we bootstrap our state when an event occurs and then process that event. And instead of just keeping that state in memory and waiting for the next event, we archive that state and close the resources. It could be considered less efficient in one sense (that we need to load state at every "event"), but it could also be called more efficient in that there is never idle state in memory for no reason. We only load state which is actually being consumed/manipulated
So in this way, think of PHP via HTTP as being event driven at the macro level, while resting assured that any given execution is indeed deterministic and not actually event driven at all. However, we implement a front-controller and MVC pattern so that we can provide application developers with the familiar structure of even driven hooks. When you are working with a decent framework, you simply say "I want to know when a user registers, and the user should be available to me to modify at that time". This is event-driven development. It should not concern you that the framework has bootstrapped the environment for (almost) the sole purpose of calling your hook (vs. the more traditional notion that the environment was already there and you were simply notified of an event). This is what it means to develop PHP in an event-driven way. The controllers determine (based on the request) which event is occurring, and uses whichever mechanism it is designed to use (i.e. observer pattern, hook architecture, etc) to allow your code to handle the event, respond to the event, or whatever nomenclature is most appropriate for your particular framework's semantics.