|
| 1 | +# Available Controllers |
| 2 | + |
| 3 | +Controllers in the MVC layer simply need to be objects implementing |
| 4 | +`Zend\Stdlib\DispatchableInterface`. That interface describes a single method: |
| 5 | + |
| 6 | +```php |
| 7 | +use Zend\Stdlib\DispatchableInterface; |
| 8 | +use Zend\Stdlib\RequestInterface as Request; |
| 9 | +use Zend\Stdlib\ResponseInterface as Response; |
| 10 | + |
| 11 | +class Foo implements DispatchableInterface |
| 12 | +{ |
| 13 | + public function dispatch(Request $request, Response $response = null) |
| 14 | + { |
| 15 | + // ... do something, and preferably return a Response ... |
| 16 | + } |
| 17 | +} |
| 18 | +``` |
| 19 | + |
| 20 | +While this pattern is simple enough, chances are you don't want to implement custom dispatch logic |
| 21 | +for every controller (particularly as it's not unusual or uncommon for a single controller to handle |
| 22 | +several related types of requests). |
| 23 | + |
| 24 | +The MVC also defines several interfaces that, when implemented, can provide controllers with |
| 25 | +additional capabilities. |
| 26 | + |
| 27 | +## Common Interfaces Used With Controllers |
| 28 | + |
| 29 | +### InjectApplicationEvent |
| 30 | + |
| 31 | +The `Zend\Mvc\InjectApplicationEventInterface` hints to the `Application` instance that it should |
| 32 | +inject its `MvcEvent` into the controller itself. Why would this be useful? |
| 33 | + |
| 34 | +Recall that the `MvcEvent` composes a number of objects: the `Request` and `Response`, naturally, |
| 35 | +but also the router, the route matches (a `RouteMatch` instance), and potentially the "result" of |
| 36 | +dispatching. |
| 37 | + |
| 38 | +A controller that has the `MvcEvent` injected, then, can retrieve or inject these. As an example: |
| 39 | + |
| 40 | +```php |
| 41 | +$matches = $this->getEvent()->getRouteMatch(); |
| 42 | +$id = $matches->getParam('id', false); |
| 43 | +if (!$id) { |
| 44 | + $response = $this->getResponse(); |
| 45 | + $response->setStatusCode(500); |
| 46 | + $this->getEvent()->setResult('Invalid identifier; cannot complete request'); |
| 47 | + return; |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +The `InjectApplicationEventInterface` defines simply two methods: |
| 52 | + |
| 53 | +```php |
| 54 | +public function setEvent(Zend\EventManager\EventInterface $event); |
| 55 | +public function getEvent(); |
| 56 | +``` |
| 57 | + |
| 58 | +### ServiceLocatorAware |
| 59 | + |
| 60 | +In most cases, you should define your controllers such that dependencies are injected by the |
| 61 | +application's `ServiceManager`, via either constructor arguments or setter methods. |
| 62 | + |
| 63 | +However, occasionally you may have objects you wish to use in your controller that are only valid |
| 64 | +for certain code paths. Examples include forms, paginators, navigation, etc. In these cases, you may |
| 65 | +decide that it doesn't make sense to inject those objects every time the controller is used. |
| 66 | + |
| 67 | +The `ServiceLocatorAwareInterface` interface hints to the `ServiceManager` that it should inject |
| 68 | +itself into the controller. It defines two simple methods: |
| 69 | + |
| 70 | +```php |
| 71 | +use Zend\ServiceManager\ServiceLocatorInterface; |
| 72 | +use Zend\ServiceManager\ServiceLocatorAwareInterface; |
| 73 | + |
| 74 | +public function setServiceLocator(ServiceLocatorInterface $serviceLocator); |
| 75 | +public function getServiceLocator(); |
| 76 | +``` |
| 77 | + |
| 78 | +### EventManagerAware |
| 79 | + |
| 80 | +Typically, it's nice to be able to tie into a controller's workflow without needing to extend it or |
| 81 | +hardcode behavior into it. The solution for this at the framework level is to use the |
| 82 | +`EventManager`. |
| 83 | + |
| 84 | +You can hint to the `ServiceManager` that you want an `EventManager` injected by implementing the |
| 85 | +interface `EventManagerAwareInterface`, which tells the `ServiceManager` to inject an |
| 86 | +`EventManager`. |
| 87 | + |
| 88 | +You define two methods. The first, a setter, should also set any `EventManager` identifiers you want |
| 89 | +to listen on, and the second, a getter, should simply return the composed `EventManager` instance. |
| 90 | + |
| 91 | +```php |
| 92 | +use Zend\EventManager\EventManagerAwareInterface; |
| 93 | +use Zend\EventManager\EventManagerInterface; |
| 94 | + |
| 95 | +public function setEventManager(EventManagerInterface $events); |
| 96 | +public function getEventManager(); |
| 97 | +``` |
| 98 | + |
| 99 | +### Controller Plugins |
| 100 | + |
| 101 | +Code re-use is a common goal for developers. Another common goal is convenience. However, this is |
| 102 | +often difficult to achieve cleanly in abstract, general systems. |
| 103 | + |
| 104 | +Within your controllers, you'll often find yourself repeating tasks from one controller to another. |
| 105 | +Some common examples: |
| 106 | + |
| 107 | +- Generating URLs |
| 108 | +- Redirecting |
| 109 | +- Setting and retrieving flash messages (self-expiring session messages) |
| 110 | +- Invoking and dispatching additional controllers |
| 111 | + |
| 112 | +To facilitate these actions while also making them available to alternate controller |
| 113 | +implementations, we've created a `PluginManager` implementation for the controller layer, |
| 114 | +`Zend\Mvc\Controller\PluginManager`, building on the `Zend\ServiceManager\AbstractPluginManager` |
| 115 | +functionality. To utilize it, you simply need to implement the `setPluginManager(PluginManager |
| 116 | +$plugins)` method, and set up your code to use the controller-specific implementation by default: |
| 117 | + |
| 118 | +```php |
| 119 | +use Zend\Mvc\Controller\PluginManager; |
| 120 | + |
| 121 | +public function setPluginManager(PluginManager $plugins) |
| 122 | +{ |
| 123 | + $this->plugins = $plugins; |
| 124 | + $this->plugins->setController($this); |
| 125 | + |
| 126 | + return $this; |
| 127 | +} |
| 128 | + |
| 129 | +public function getPluginManager() |
| 130 | +{ |
| 131 | + if (!$this->plugins) { |
| 132 | + $this->setPluginManager(new PluginManager()); |
| 133 | + } |
| 134 | + |
| 135 | + return $this->plugins; |
| 136 | +} |
| 137 | + |
| 138 | +public function plugin($name, array $options = null) |
| 139 | +{ |
| 140 | + return $this->getPluginManager()->get($name, $options); |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +## The AbstractActionController |
| 145 | + |
| 146 | +Implementing each of the above interfaces is a lesson in redundancy; you won't often want to do it. |
| 147 | +As such, we've developed two abstract, base controllers you can extend to get started. |
| 148 | + |
| 149 | +The first is `Zend\Mvc\Controller\AbstractActionController`. This controller implements each of the |
| 150 | +above interfaces, and uses the following assumptions: |
| 151 | + |
| 152 | +- An "action" parameter is expected in the `RouteMatch` object composed in the attached `MvcEvent`. |
| 153 | +If none is found, a `notFoundAction()` is invoked. |
| 154 | +- The "action" parameter is converted to a camelCased format and appended with the word "Action" to |
| 155 | +create a method name. As examples: "foo" maps to "fooAction", "foo-bar" or "foo.bar" or "foo\_bar" |
| 156 | +to "fooBarAction". The controller then checks to see if that method exists. If not, the |
| 157 | +`notFoundAction()` method is invoked; otherwise, the discovered method is called. |
| 158 | +- The results of executing the given action method are injected into the `MvcEvent`'s "result" |
| 159 | +property (via `setResult()`, and accessible via `getResult()`). |
| 160 | + |
| 161 | +Essentially, a route mapping to an `AbstractActionController` needs to return both "controller" and |
| 162 | +"action" keys in its matches. |
| 163 | + |
| 164 | +Creation of action controllers is then reasonably trivial: |
| 165 | + |
| 166 | +```php |
| 167 | +namespace Foo\Controller; |
| 168 | + |
| 169 | +use Zend\Mvc\Controller\AbstractActionController; |
| 170 | + |
| 171 | +class BarController extends AbstractActionController |
| 172 | +{ |
| 173 | + public function bazAction() |
| 174 | + { |
| 175 | + return array('title' => __METHOD__); |
| 176 | + } |
| 177 | + |
| 178 | + public function batAction() |
| 179 | + { |
| 180 | + return array('title' => __METHOD__); |
| 181 | + } |
| 182 | +} |
| 183 | +``` |
| 184 | + |
| 185 | +### Interfaces and Collaborators |
| 186 | + |
| 187 | +`AbstractActionController` implements each of the following interfaces: |
| 188 | + |
| 189 | +- `Zend\Stdlib\DispatchableInterface` |
| 190 | +- `Zend\Mvc\InjectApplicationEventInterface` |
| 191 | +- `Zend\ServiceManager\ServiceLocatorAwareInterface` |
| 192 | +- `Zend\EventManager\EventManagerAwareInterface` |
| 193 | + |
| 194 | +The composed `EventManager` will be configured to listen on the following contexts: |
| 195 | + |
| 196 | +- `Zend\Stdlib\DispatchableInterface` |
| 197 | +- `Zend\Mvc\Controller\AbstractActionController` |
| 198 | +- `Zend\Mvc\Controller\AbstractController` |
| 199 | + |
| 200 | +Additionally, if you extend the class, it will listen on the extending class's name. |
| 201 | + |
| 202 | +## The AbstractRestfulController |
| 203 | + |
| 204 | +The second abstract controller ZF2 provides is `Zend\Mvc\Controller\AbstractRestfulController`. This |
| 205 | +controller provides a native RESTful implementation that simply maps HTTP request methods to |
| 206 | +controller methods, using the following matrix: |
| 207 | + |
| 208 | +- **GET** maps to either `get()` or `getList()`, depending on whether or not an "id" parameter is |
| 209 | +found in the route matches. If one is, it is passed as an argument to `get()`; if not, `getList()` |
| 210 | +is invoked. In the former case, you should provide a representation of the given entity with that |
| 211 | +identification; in the latter, you should provide a list of entities. |
| 212 | +- **POST** maps to `create()`. That method expects a `$data` argument, usually the `$_POST` |
| 213 | +superglobal array. The data should be used to create a new entity, and the response should typically |
| 214 | +be an HTTP 201 response with the Location header indicating the URI of the newly created entity and |
| 215 | +the response body providing the representation. |
| 216 | +- **PUT** maps to `update()`, and requires that an "id" parameter exists in the route matches; that |
| 217 | +value is passed as an argument to the method. It should attempt to update the given entity, and, if |
| 218 | +successful, return either a 200 or 202 response status, as well as the representation of the entity. |
| 219 | +- **DELETE** maps to `delete()`, and requires that an "id" parameter exists in the route matches; |
| 220 | +that value is passed as an argument to the method. It should attempt to delete the given entity, |
| 221 | +and, if successful, return either a 200 or 204 response status. |
| 222 | + |
| 223 | +Additionally, you can map "action" methods to the `AbstractRestfulController`, just as you would in |
| 224 | +the `AbstractActionController`; these methods will be suffixed with "Action", differentiating them |
| 225 | +from the RESTful methods listed above. This allows you to perform such actions as providing forms |
| 226 | +used to submit to the various RESTful methods, or to add RPC methods to your RESTful API. |
| 227 | + |
| 228 | +### Interfaces and Collaborators |
| 229 | + |
| 230 | +`AbstractRestfulController` implements each of the following interfaces: |
| 231 | + |
| 232 | +- `Zend\Stdlib\DispatchableInterface` |
| 233 | +- `Zend\Mvc\InjectApplicationEventInterface` |
| 234 | +- `Zend\ServiceManager\ServiceLocatorAwareInterface` |
| 235 | +- `Zend\EventManager\EventManagerAwareInterface` |
| 236 | + |
| 237 | +The composed `EventManager` will be configured to listen on the following contexts: |
| 238 | + |
| 239 | +- `Zend\Stdlib\DispatchableInterface` |
| 240 | +- `Zend\Mvc\Controller\AbstractRestfulController` |
| 241 | +- `Zend\Mvc\Controller\AbstractController` |
| 242 | + |
| 243 | +Additionally, if you extend the class, it will listen on the extending class's name. |
0 commit comments