1: <?php namespace Laravel\Routing;
2:
3: use Laravel\IoC;
4: use Laravel\Str;
5: use Laravel\View;
6: use Laravel\Event;
7: use Laravel\Bundle;
8: use Laravel\Request;
9: use Laravel\Redirect;
10: use Laravel\Response;
11: use FilesystemIterator as fIterator;
12:
13: abstract class Controller {
14:
15: /**
16: * The layout being used by the controller.
17: *
18: * @var string
19: */
20: public $layout;
21:
22: /**
23: * The bundle the controller belongs to.
24: *
25: * @var string
26: */
27: public $bundle;
28:
29: /**
30: * Indicates if the controller uses RESTful routing.
31: *
32: * @var bool
33: */
34: public $restful = false;
35:
36: /**
37: * The filters assigned to the controller.
38: *
39: * @var array
40: */
41: protected $filters = array();
42:
43: /**
44: * The event name for the Laravel controller factory.
45: *
46: * @var string
47: */
48: const factory = 'laravel.controller.factory';
49:
50: /**
51: * Create a new Controller instance.
52: *
53: * @return void
54: */
55: public function __construct()
56: {
57: // If the controller has specified a layout to be used when rendering
58: // views, we will instantiate the layout instance and set it to the
59: // layout property, replacing the string layout name.
60: if ( ! is_null($this->layout))
61: {
62: $this->layout = $this->layout();
63: }
64: }
65:
66: /**
67: * Detect all of the controllers for a given bundle.
68: *
69: * @param string $bundle
70: * @param string $directory
71: * @return array
72: */
73: public static function detect($bundle = DEFAULT_BUNDLE, $directory = null)
74: {
75: if (is_null($directory))
76: {
77: $directory = Bundle::path($bundle).'controllers';
78: }
79:
80: // First we'll get the root path to the directory housing all of
81: // the bundle's controllers. This will be used later to figure
82: // out the identifiers needed for the found controllers.
83: $root = Bundle::path($bundle).'controllers'.DS;
84:
85: $controllers = array();
86:
87: $items = new fIterator($directory, fIterator::SKIP_DOTS);
88:
89: foreach ($items as $item)
90: {
91: // If the item is a directory, we will recurse back into the function
92: // to detect all of the nested controllers and we will keep adding
93: // them into the array of controllers for the bundle.
94: if ($item->isDir())
95: {
96: $nested = static::detect($bundle, $item->getRealPath());
97:
98: $controllers = array_merge($controllers, $nested);
99: }
100:
101: // If the item is a file, we'll assume it is a controller and we
102: // will build the identifier string for the controller that we
103: // can pass into the route's controller method.
104: else
105: {
106: $controller = str_replace(array($root, EXT), '', $item->getRealPath());
107:
108: $controller = str_replace(DS, '.', $controller);
109:
110: $controllers[] = Bundle::identifier($bundle, $controller);
111: }
112: }
113:
114: return $controllers;
115: }
116:
117: /**
118: * Call an action method on a controller.
119: *
120: * <code>
121: * // Call the "show" method on the "user" controller
122: * $response = Controller::call('user@show');
123: *
124: * // Call the "user/admin" controller and pass parameters
125: * $response = Controller::call('user.admin@profile', array($username));
126: * </code>
127: *
128: * @param string $destination
129: * @param array $parameters
130: * @return Response
131: */
132: public static function call($destination, $parameters = array())
133: {
134: static::references($destination, $parameters);
135:
136: list($bundle, $destination) = Bundle::parse($destination);
137:
138: // We will always start the bundle, just in case the developer is pointing
139: // a route to another bundle. This allows us to lazy load the bundle and
140: // improve speed since the bundle is not loaded on every request.
141: Bundle::start($bundle);
142:
143: list($name, $method) = explode('@', $destination);
144:
145: $controller = static::resolve($bundle, $name);
146:
147: // For convenience we will set the current controller and action on the
148: // Request's route instance so they can be easily accessed from the
149: // application. This is sometimes useful for dynamic situations.
150: if ( ! is_null($route = Request::route()))
151: {
152: $route->controller = $name;
153:
154: $route->controller_action = $method;
155: }
156:
157: // If the controller could not be resolved, we're out of options and
158: // will return the 404 error response. If we found the controller,
159: // we can execute the requested method on the instance.
160: if (is_null($controller))
161: {
162: return Event::first('404');
163: }
164:
165: return $controller->execute($method, $parameters);
166: }
167:
168: /**
169: * Replace all back-references on the given destination.
170: *
171: * @param string $destination
172: * @param array $parameters
173: * @return array
174: */
175: protected static function references(&$destination, &$parameters)
176: {
177: // Controller delegates may use back-references to the action parameters,
178: // which allows the developer to setup more flexible routes to various
179: // controllers with much less code than would be usual.
180: foreach ($parameters as $key => $value)
181: {
182: if ( ! is_string($value)) continue;
183:
184: $search = '(:'.($key + 1).')';
185:
186: $destination = str_replace($search, $value, $destination, $count);
187:
188: if ($count > 0) unset($parameters[$key]);
189: }
190:
191: return array($destination, $parameters);
192: }
193:
194: /**
195: * Resolve a bundle and controller name to a controller instance.
196: *
197: * @param string $bundle
198: * @param string $controller
199: * @return Controller
200: */
201: public static function resolve($bundle, $controller)
202: {
203: if ( ! static::load($bundle, $controller)) return;
204:
205: $identifier = Bundle::identifier($bundle, $controller);
206:
207: // If the controller is registered in the IoC container, we will resolve
208: // it out of the container. Using constructor injection on controllers
209: // via the container allows more flexible applications.
210: $resolver = 'controller: '.$identifier;
211:
212: if (IoC::registered($resolver))
213: {
214: return IoC::resolve($resolver);
215: }
216:
217: $controller = static::format($bundle, $controller);
218:
219: // If we couldn't resolve the controller out of the IoC container we'll
220: // format the controller name into its proper class name and load it
221: // by convention out of the bundle's controller directory.
222: if (Event::listeners(static::factory))
223: {
224: return Event::first(static::factory, $controller);
225: }
226: else
227: {
228: return new $controller;
229: }
230: }
231:
232: /**
233: * Load the file for a given controller.
234: *
235: * @param string $bundle
236: * @param string $controller
237: * @return bool
238: */
239: protected static function load($bundle, $controller)
240: {
241: $controller = strtolower(str_replace('.', '/', $controller));
242:
243: if (file_exists($path = Bundle::path($bundle).'controllers/'.$controller.EXT))
244: {
245: require_once $path;
246:
247: return true;
248: }
249:
250: return false;
251: }
252:
253: /**
254: * Format a bundle and controller identifier into the controller's class name.
255: *
256: * @param string $bundle
257: * @param string $controller
258: * @return string
259: */
260: protected static function format($bundle, $controller)
261: {
262: return Bundle::class_prefix($bundle).Str::classify($controller).'_Controller';
263: }
264:
265: /**
266: * Execute a controller method with the given parameters.
267: *
268: * @param string $method
269: * @param array $parameters
270: * @return Response
271: */
272: public function execute($method, $parameters = array())
273: {
274: $filters = $this->filters('before', $method);
275:
276: // Again, as was the case with route closures, if the controller "before"
277: // filters return a response, it will be considered the response to the
278: // request and the controller method will not be used.
279: $response = Filter::run($filters, $parameters, true);
280:
281: if (is_null($response))
282: {
283: $this->before();
284:
285: $response = $this->response($method, $parameters);
286: }
287:
288: $response = Response::prepare($response);
289:
290: // The "after" function on the controller is simply a convenient hook
291: // so the developer can work on the response before it's returned to
292: // the browser. This is useful for templating, etc.
293: $this->after($response);
294:
295: Filter::run($this->filters('after', $method), array($response));
296:
297: return $response;
298: }
299:
300: /**
301: * Execute a controller action and return the response.
302: *
303: * Unlike the "execute" method, no filters will be run and the response
304: * from the controller action will not be changed in any way before it
305: * is returned to the consumer.
306: *
307: * @param string $method
308: * @param array $parameters
309: * @return mixed
310: */
311: public function response($method, $parameters = array())
312: {
313: // The developer may mark the controller as being "RESTful" which
314: // indicates that the controller actions are prefixed with the
315: // HTTP verb they respond to rather than the word "action".
316: if ($this->restful)
317: {
318: $action = strtolower(Request::method()).'_'.$method;
319: }
320: else
321: {
322: $action = "action_{$method}";
323: }
324:
325: $response = call_user_func_array(array($this, $action), $parameters);
326:
327: // If the controller has specified a layout view the response
328: // returned by the controller method will be bound to that
329: // view and the layout will be considered the response.
330: if (is_null($response) and ! is_null($this->layout))
331: {
332: $response = $this->layout;
333: }
334:
335: return $response;
336: }
337:
338: /**
339: * Register filters on the controller's methods.
340: *
341: * <code>
342: * // Set a "foo" after filter on the controller
343: * $this->filter('before', 'foo');
344: *
345: * // Set several filters on an explicit group of methods
346: * $this->filter('after', 'foo|bar')->only(array('user', 'profile'));
347: * </code>
348: *
349: * @param string $event
350: * @param string|array $filters
351: * @param mixed $parameters
352: * @return Filter_Collection
353: */
354: protected function filter($event, $filters, $parameters = null)
355: {
356: $this->filters[$event][] = new Filter_Collection($filters, $parameters);
357:
358: return $this->filters[$event][count($this->filters[$event]) - 1];
359: }
360:
361: /**
362: * Get an array of filter names defined for the destination.
363: *
364: * @param string $event
365: * @param string $method
366: * @return array
367: */
368: protected function filters($event, $method)
369: {
370: if ( ! isset($this->filters[$event])) return array();
371:
372: $filters = array();
373:
374: foreach ($this->filters[$event] as $collection)
375: {
376: if ($collection->applies($method))
377: {
378: $filters[] = $collection;
379: }
380: }
381:
382: return $filters;
383: }
384:
385: /**
386: * Create the layout that is assigned to the controller.
387: *
388: * @return View
389: */
390: public function layout()
391: {
392: if (starts_with($this->layout, 'name: '))
393: {
394: return View::of(substr($this->layout, 6));
395: }
396:
397: return View::make($this->layout);
398: }
399:
400: /**
401: * This function is called before the action is executed.
402: *
403: * @return void
404: */
405: public function before() {}
406:
407: /**
408: * This function is called after the action is executed.
409: *
410: * @param Response $response
411: * @return void
412: */
413: public function after($response) {}
414:
415: /**
416: * Magic Method to handle calls to undefined controller functions.
417: */
418: public function __call($method, $parameters)
419: {
420: return Response::error('404');
421: }
422:
423: /**
424: * Dynamically resolve items from the application IoC container.
425: *
426: * <code>
427: * // Retrieve an object registered in the container
428: * $mailer = $this->mailer;
429: *
430: * // Equivalent call using the IoC container instance
431: * $mailer = IoC::resolve('mailer');
432: * </code>
433: */
434: public function __get($key)
435: {
436: if (IoC::registered($key))
437: {
438: return IoC::resolve($key);
439: }
440: }
441:
442: }
443: