controller.php 10.8 KB
Newer Older
1 2 3
<?php namespace Laravel\Routing;

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 14 15

abstract class Controller {

	/**
16 17 18 19 20 21 22
	 * The layout being used by the controller.
	 *
	 * @var string
	 */
	public $layout;

	/**
23 24 25 26 27 28 29
	 * The bundle the controller belongs to.
	 *
	 * @var string
	 */
	public $bundle;

	/**
30 31 32 33 34 35 36
	 * Indicates if the controller uses RESTful routing.
	 *
	 * @var bool
	 */
	public $restful = false;

	/**
37
	 * The filters assigned to the controller.
38 39 40
	 *
	 * @var array
	 */
41
	protected $filters = array();
42 43

	/**
44 45 46 47 48 49 50
	 * The event name for the Laravel controller factory.
	 *
	 * @var string
	 */
	const factory = 'laravel.controller.factory';

	/**
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
	 * Create a new Controller instance.
	 *
	 * @return void
	 */
	public function __construct()
	{
		// If the controller has specified a layout to be used when rendering
		// views, we will instantiate the layout instance and set it to the
		// layout property, replacing the string layout name.
		if ( ! is_null($this->layout))
		{
			$this->layout = $this->layout();
		}
	}

	/**
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
	 * Detect all of the controllers for a given bundle.
	 *
	 * @param  string  $bundle
	 * @param  string  $directory
	 * @return array
	 */
	public static function detect($bundle = DEFAULT_BUNDLE, $directory = null)
	{
		if (is_null($directory))
		{
			$directory = Bundle::path($bundle).'controllers';
		}

		// First we'll get the root path to the directory housing all of
		// the bundle's controllers. This will be used later to figure
		// out the identifiers needed for the found controllers.
		$root = Bundle::path($bundle).'controllers'.DS;

		$controllers = array();

		$items = new fIterator($directory, fIterator::SKIP_DOTS);

		foreach ($items as $item)
		{
			// If the item is a directory, we will recurse back into the function
			// to detect all of the nested controllers and we will keep adding
			// them into the array of controllers for the bundle.
			if ($item->isDir())
			{
				$nested = static::detect($bundle, $item->getRealPath());

				$controllers = array_merge($controllers, $nested);
			}

			// If the item is a file, we'll assume it is a controller and we
			// will build the identifier string for the controller that we
			// can pass into the route's controller method.
			else
			{
				$controller = str_replace(array($root, EXT), '', $item->getRealPath());

				$controller = str_replace(DS, '.', $controller);

				$controllers[] = Bundle::identifier($bundle, $controller);
			}
		}

		return $controllers;
	}

	/**
118
	 * Call an action method on a controller.
119
	 *
120 121 122
	 * <code>
	 *		// Call the "show" method on the "user" controller
	 *		$response = Controller::call('user@show');
123
	 *
124
	 *		// Call the "user/admin" controller and pass parameters
125 126
	 *		$response = Controller::call('user.admin@profile', array($username));
	 * </code>
127
	 *
128 129 130
	 * @param  string    $destination
	 * @param  array     $parameters
	 * @return Response
131
	 */
132
	public static function call($destination, $parameters = array())
133
	{
134 135
		static::references($destination, $parameters);

136 137 138 139
		list($bundle, $destination) = Bundle::parse($destination);

		// We will always start the bundle, just in case the developer is pointing
		// 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 146 147 148 149 150 151 152 153 154 155
		$controller = static::resolve($bundle, $name);

		// For convenience we will set the current controller and action on the
		// Request's route instance so they can be easily accessed from the
		// application. This is sometimes useful for dynamic situations.
		if ( ! is_null($route = Request::route()))
		{
			$route->controller = $name;

			$route->controller_action = $method;
		}
156

157 158 159
		// If the controller could not be resolved, we're out of options and
		// will return the 404 error response. If we found the controller,
		// we can execute the requested method on the instance.
160 161 162 163
		if (is_null($controller))
		{
			return Event::first('404');
		}
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 173 174
	 * @param  array   $parameters
	 * @return array
	 */
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 181
		foreach ($parameters as $key => $value)
		{
182 183
			if ( ! is_string($value)) continue;

184 185 186
			$search = '(:'.($key + 1).')';

			$destination = str_replace($search, $value, $destination, $count);
187 188 189 190

			if ($count > 0) unset($parameters[$key]);
		}

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 199 200
	 * @param  string      $controller
	 * @return Controller
	 */
201
	public static function resolve($bundle, $controller)
202
	{
203 204
		if ( ! static::load($bundle, $controller)) return;

205
		$identifier = Bundle::identifier($bundle, $controller);
206

207 208
		// If the controller is registered in the IoC container, we will resolve
		// it out of the container. Using constructor injection on controllers
209 210
		// via the container allows more flexible applications.
		$resolver = 'controller: '.$identifier;
211 212

		if (IoC::registered($resolver))
213
		{
214
			return IoC::resolve($resolver);
215 216
		}

217 218
		$controller = static::format($bundle, $controller);

219 220 221
		// If we couldn't resolve the controller out of the IoC container we'll
		// format the controller name into its proper class name and load it
		// by convention out of the bundle's controller directory.
222 223
		if (Event::listeners(static::factory))
		{
224
			return Event::first(static::factory, $controller);
225 226 227
		}
		else
		{
228
			return new $controller;
229
		}
230 231 232 233 234
	}

	/**
	 * Load the file for a given controller.
	 *
235
	 * @param  string  $bundle
236 237 238
	 * @param  string  $controller
	 * @return bool
	 */
239
	protected static function load($bundle, $controller)
240 241 242
	{
		$controller = strtolower(str_replace('.', '/', $controller));

243
		if (file_exists($path = Bundle::path($bundle).'controllers/'.$controller.EXT))
244
		{
245
			require_once $path;
246 247 248 249 250 251 252 253

			return true;
		}

		return false;
	}

	/**
254 255 256 257 258 259 260 261
	 * Format a bundle and controller identifier into the controller's class name.
	 *
	 * @param  string  $bundle
	 * @param  string  $controller
	 * @return string
	 */
	protected static function format($bundle, $controller)
	{
262
		return Bundle::class_prefix($bundle).Str::classify($controller).'_Controller';
263 264 265
	}

	/**
266 267 268 269 270 271
	 * Execute a controller method with the given parameters.
	 *
	 * @param  string    $method
	 * @param  array     $parameters
	 * @return Response
	 */
272
	public function execute($method, $parameters = array())
273
	{
274 275
		$filters = $this->filters('before', $method);

276 277
		// Again, as was the case with route closures, if the controller "before"
		// filters return a response, it will be considered the response to the
Taylor Otwell committed
278
		// request and the controller method will not be used.
279
		$response = Filter::run($filters, array(), true);
280 281 282

		if (is_null($response))
		{
283 284
			$this->before();

285 286
			$response = $this->response($method, $parameters);
		}
287

288
		$response = Response::prepare($response);
289

290 291
		// The "after" function on the controller is simply a convenient hook
		// so the developer can work on the response before it's returned to
292
		// the browser. This is useful for templating, etc.
293 294
		$this->after($response);

295
		Filter::run($this->filters('after', $method), array($response));
296

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
		return $response;
	}

	/**
	 * Execute a controller action and return the response.
	 *
	 * Unlike the "execute" method, no filters will be run and the response
	 * from the controller action will not be changed in any way before it
	 * is returned to the consumer.
	 *
	 * @param  string  $method
	 * @param  array   $parameters
	 * @return mixed
	 */
	public function response($method, $parameters = array())
	{
		// The developer may mark the controller as being "RESTful" which
		// indicates that the controller actions are prefixed with the
		// HTTP verb they respond to rather than the word "action".
		if ($this->restful)
317
		{
318 319 320 321 322
			$action = strtolower(Request::method()).'_'.$method;
		}
		else
		{
			$action = "action_{$method}";
323
		}
324

325
		$response = call_user_func_array(array($this, $action), $parameters);
326

Pavel committed
327
		// If the controller has specified a layout view the response
328 329 330 331 332 333
		// returned by the controller method will be bound to that
		// view and the layout will be considered the response.
		if (is_null($response) and ! is_null($this->layout))
		{
			$response = $this->layout;
		}
334 335 336 337 338

		return $response;
	}

	/**
339
	 * Register filters on the controller's methods.
340 341 342
	 *
	 * <code>
	 *		// 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 348
	 * </code>
	 *
349
	 * @param  string             $event
350
	 * @param  string|array       $filters
351
	 * @param  mixed              $parameters
352 353
	 * @return Filter_Collection
	 */
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 363
	 * Get an array of filter names defined for the destination.
	 *
364
	 * @param  string  $event
365
	 * @param  string  $method
366 367
	 * @return array
	 */
368
	protected function filters($event, $method)
369
	{
370
		if ( ! isset($this->filters[$event])) return array();
Taylor Otwell committed
371

372 373
		$filters = array();

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 389 390 391
	 * @return View
	 */
	public function layout()
	{
392 393 394 395 396
		if (starts_with($this->layout, 'name: '))
		{
			return View::of(substr($this->layout, 6));
		}

397 398 399 400
		return View::make($this->layout);
	}

	/**
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
	 * This function is called before the action is executed.
	 *
	 * @return void
	 */
	public function before() {}

	/**
	 * This function is called after the action is executed.
	 *
	 * @param  Response  $response
	 * @return void
	 */
	public function after($response) {}

	/**
	 * Magic Method to handle calls to undefined controller functions.
417 418 419 420 421 422 423 424 425 426
	 */
	public function __call($method, $parameters)
	{
		return Response::error('404');
	}

	/**
	 * Dynamically resolve items from the application IoC container.
	 *
	 * <code>
427
	 *		// Retrieve an object registered in the container
428 429 430
	 *		$mailer = $this->mailer;
	 *
	 *		// Equivalent call using the IoC container instance
431
	 *		$mailer = IoC::resolve('mailer');
432 433 434 435
	 * </code>
	 */
	public function __get($key)
	{
436 437 438 439
		if (IoC::registered($key))
		{
			return IoC::resolve($key);
		}
440 441
	}

442
}