Overview

Namespaces

  • None
  • Wei
    • Validator

Classes

  • Wei\Validator\All
  • Wei\Validator\AllOf
  • Wei\Validator\Alnum
  • Wei\Validator\Alpha
  • Wei\Validator\BaseValidator
  • Wei\Validator\Between
  • Wei\Validator\Blank
  • Wei\Validator\Callback
  • Wei\Validator\CharLength
  • Wei\Validator\Chinese
  • Wei\Validator\Color
  • Wei\Validator\Contains
  • Wei\Validator\CreditCard
  • Wei\Validator\Date
  • Wei\Validator\DateTime
  • Wei\Validator\Decimal
  • Wei\Validator\Digit
  • Wei\Validator\Dir
  • Wei\Validator\DivisibleBy
  • Wei\Validator\DoubleByte
  • Wei\Validator\Email
  • Wei\Validator\EndsWith
  • Wei\Validator\EqualTo
  • Wei\Validator\Exists
  • Wei\Validator\FieldExists
  • Wei\Validator\File
  • Wei\Validator\GreaterThan
  • Wei\Validator\GreaterThanOrEqual
  • Wei\Validator\IdCardCn
  • Wei\Validator\IdCardHk
  • Wei\Validator\IdCardMo
  • Wei\Validator\IdCardTw
  • Wei\Validator\IdenticalTo
  • Wei\Validator\Image
  • Wei\Validator\In
  • Wei\Validator\Ip
  • Wei\Validator\Length
  • Wei\Validator\LessThan
  • Wei\Validator\LessThanOrEqual
  • Wei\Validator\Lowercase
  • Wei\Validator\Luhn
  • Wei\Validator\MaxLength
  • Wei\Validator\MinLength
  • Wei\Validator\MobileCn
  • Wei\Validator\NaturalNumber
  • Wei\Validator\NoneOf
  • Wei\Validator\Null
  • Wei\Validator\Number
  • Wei\Validator\OneOf
  • Wei\Validator\Password
  • Wei\Validator\Phone
  • Wei\Validator\PhoneCn
  • Wei\Validator\PlateNumberCn
  • Wei\Validator\PositiveInteger
  • Wei\Validator\PostcodeCn
  • Wei\Validator\Present
  • Wei\Validator\QQ
  • Wei\Validator\RecordExists
  • Wei\Validator\Regex
  • Wei\Validator\Required
  • Wei\Validator\SomeOf
  • Wei\Validator\StartsWith
  • Wei\Validator\Time
  • Wei\Validator\Tld
  • Wei\Validator\Type
  • Wei\Validator\Uppercase
  • Wei\Validator\Url
  • Wei\Validator\Uuid
  • Overview
  • Namespace
  • Function
  1: <?php
  2: /**
  3:  * Wei Framework
  4:  *
  5:  * @copyright   Copyright (c) 2008-2015 Twin Huang
  6:  * @license     http://opensource.org/licenses/mit-license.php MIT License
  7:  */
  8: 
  9: namespace Wei;
 10: 
 11: /**
 12:  * A service to build an MVC application
 13:  *
 14:  * @author      Twin Huang <twinhuang@qq.com>
 15:  * @property    Router $router A service that parse the URL to request data
 16:  * @property    Request $request A service that handles the HTTP request data
 17:  * @property    Response $response A service that handles the HTTP response data
 18:  * @property    View $view A service that use to render PHP template
 19:  */
 20: class App extends Base
 21: {
 22:     /**
 23:      * The exception code for forward action
 24:      */
 25:     const FORWARD = 1302;
 26: 
 27:     /**
 28:      * The format of controller class
 29:      *
 30:      * @var string
 31:      */
 32:     protected $controllerFormat = 'controllers\%controller%';
 33: 
 34:     /**
 35:      * The default controller name
 36:      *
 37:      * @var string
 38:      */
 39:     protected $defaultController = 'index';
 40: 
 41:     /**
 42:      * The default action name
 43:      *
 44:      * @var string
 45:      */
 46:     protected $defaultAction = 'index';
 47: 
 48:     /**
 49:      * The name of current application
 50:      *
 51:      * @var string
 52:      */
 53:     protected $namespace;
 54: 
 55:     /**
 56:      * The name of controller
 57:      *
 58:      * @var string
 59:      */
 60:     protected $controller;
 61: 
 62:     /**
 63:      * The name of action
 64:      *
 65:      * @var string
 66:      */
 67:     protected $action;
 68: 
 69:     /**
 70:      * The instanced controller objects
 71:      *
 72:      * @var array
 73:      */
 74:     protected $controllerInstances = array();
 75: 
 76:     /**
 77:      * An array that stores predefined controller class,
 78:      * the key is controller name and value is controller class name
 79:      *
 80:      * @var array
 81:      */
 82:     protected $controllerMap = array();
 83: 
 84:     /**
 85:      * Startup an MVC application
 86:      *
 87:      * @param array $options
 88:      * @throws \RuntimeException
 89:      * @return $this
 90:      */
 91:     public function __invoke(array $options = array())
 92:     {
 93:         $options && $this->setOption($options);
 94: 
 95:         // Parse the path info to parameter set
 96:         $request = $this->request;
 97:         $paramSet = $this->router->matchParamSet($request->getPathInfo(), $request->getMethod());
 98: 
 99:         // Find out exiting controller action and execute
100:         $notFound = array();
101:         foreach ($paramSet as $params) {
102:             $response = $this->dispatch($params['controller'], $params['action'], $params, false);
103:             if (is_array($response)) {
104:                 $notFound = array_merge($notFound, $response);
105:             } else {
106:                 return $response;
107:             }
108:         }
109:         throw $this->buildException($notFound);
110:     }
111: 
112:     /**
113:      * Dispatch by specified controller and action
114:      *
115:      * @param string $controller The name of controller
116:      * @param null|string $action The name of action
117:      * @param array $params The request parameters
118:      * @param bool $throwException Whether throw exception when application not found
119:      * @return array|Response
120:      */
121:     public function dispatch($controller, $action = null, array $params = array(), $throwException = true)
122:     {
123:         $notFound = array();
124:         $action || $action = $this->defaultAction;
125:         $classes = $this->getControllerClasses($controller);
126: 
127:         foreach ($classes as $class) {
128:             if (!class_exists($class)) {
129:                 $notFound['controllers'][$controller][]  = $class;
130:                 continue;
131:             }
132:             if (!$this->isActionAvailable($class, $action)) {
133:                 $notFound['actions'][$action][$controller][] = $class;
134:                 continue;
135:             }
136: 
137:             // Find out existing controller and action
138:             $this->setController($controller);
139:             $this->setAction($action);
140:             $this->request->set($params);
141:             try {
142:                 $instance = $this->getControllerInstance($class);
143:                 return $this->execute($instance, $action);
144:             } catch (\RuntimeException $e) {
145:                 if ($e->getCode() === self::FORWARD) {
146:                     return $this;
147:                 } else {
148:                     throw $e;
149:                 }
150:             }
151:         }
152: 
153:         if ($throwException) {
154:             throw $this->buildException($notFound);
155:         } else {
156:             return $notFound;
157:         }
158:     }
159: 
160:     /**
161:      * Build 404 exception from notFound array
162:      *
163:      * @param array $notFound
164:      * @return \RuntimeException
165:      */
166:     protected function buildException(array $notFound)
167:     {
168:         $notFound += array('controllers' => array(), 'actions' => array());
169: 
170:         // All controllers and actions were not found, prepare exception message
171:         $message = 'The page you requested was not found';
172:         if ($this->wei->isDebug()) {
173:             $detail = $this->request->get('debug-detail');
174:             foreach ($notFound['controllers'] as $controller => $classes) {
175:                 $message .= sprintf('%s - controller "%s" not found', "\n", $controller);
176:                 $detail && $message .= sprintf(' (class "%s")', implode($classes, '", "'));
177:             }
178:             foreach ($notFound['actions'] as $action => $controllers) {
179:                 foreach ($controllers as $controller => $classes) {
180:                     $message .= sprintf('%s - action method "%s" not found in controller "%s"', "\n", $action, $controller);
181:                     $detail && $message .= sprintf(' (class "%s")', implode($classes, '", "'));
182:                 }
183:             }
184:         }
185: 
186:         // You can use `$wei->error->notFound(function(){});` to custom the 404 page
187:         return new \RuntimeException($message, 404);
188:     }
189: 
190:     /**
191:      * Execute action with middleware
192:      *
193:      * @param \Wei\Base $instance
194:      * @param string $action
195:      * @return Response
196:      */
197:     protected function execute($instance, $action)
198:     {
199:         $app = $this;
200:         $wei = $this->wei;
201:         $middleware = $this->getMiddleware($instance, $action);
202: 
203:         $callback = function () use ($instance, $action, $app) {
204:             $response = $instance->$action($app->request, $app->response);
205:             return $app->handleResponse($response);
206:         };
207: 
208:         $next = function () use (&$middleware, &$next, $callback, $wei) {
209:             $config = array_splice($middleware, 0, 1);
210:             if ($config) {
211:                 $class = key($config);
212:                 $service = new $class(array('wei' => $wei) + $config[$class]);
213:                 $result = $service($next);
214:             } else {
215:                 $result = $callback();
216:             }
217:             return $result ?: $wei->response;
218:         };
219: 
220:         return $next()->send();
221:     }
222: 
223:     /**
224:      * Returns middleware for specified action
225:      *
226:      * @param \Wei\Base $instance
227:      * @param string $action
228:      * @return array
229:      */
230:     protected function getMiddleware($instance, $action)
231:     {
232:         $results = array();
233:         $middleware = (array)$instance->getOption('middleware');
234:         foreach ($middleware as $class => $options) {
235:             if ((!isset($options['only']) || in_array($action, (array)$options['only'])) &&
236:                 (!isset($options['except']) || !in_array($action, (array)$options['except']))
237:             ) {
238:                 $results[$class] = $options;
239:             }
240:         }
241:         return $results;
242:     }
243: 
244:     /**
245:      * Handle the response variable returned by controller action
246:      *
247:      * @param  mixed $response
248:      * @return Response
249:      * @throws \InvalidArgumentException
250:      */
251:     public function handleResponse($response)
252:     {
253:         switch (true) {
254:             // Render default template and use $response as template variables
255:             case is_array($response) :
256:                 $content = $this->view->render($this->getDefaultTemplate(), $response);
257:                 return $this->response->setContent($content);
258: 
259:             // Response directly
260:             case is_scalar($response) || is_null($response) :
261:                 return $this->response->setContent($response);
262: 
263:             // Response if not sent
264:             case $response instanceof Response :
265:                 return $response;
266: 
267:             default :
268:                 throw new \InvalidArgumentException(sprintf(
269:                     'Expected argument of type array, printable variable or \Wei\Response, "%s" given',
270:                     is_object($response) ? get_class($response) : gettype($response)
271:                 ));
272:         }
273:     }
274: 
275:     /**
276:      * Returns the name of the application
277:      *
278:      * @return string
279:      */
280:     public function getNamespace()
281:     {
282:         if (!$this->namespace) {
283:             $this->namespace = $this->request->get('namespace');
284:         }
285:         return $this->namespace;
286:     }
287: 
288:     /**
289:      * Set namespace
290:      *
291:      * @param string $namespace
292:      * @return $this
293:      */
294:     public function setNamespace($namespace)
295:     {
296:         $this->namespace = $namespace;
297:         return $this;
298:     }
299: 
300:     /**
301:      * Get the of name controller
302:      *
303:      * @return string The name of controller
304:      */
305:     public function getController()
306:     {
307:         return $this->controller ?: $this->defaultController;
308:     }
309: 
310:     /**
311:      * Set the name of controller
312:      *
313:      * @param  string $controller The name of controller
314:      * @return string
315:      */
316:     public function setController($controller)
317:     {
318:         $this->controller = $controller;
319:         return $this;
320:     }
321: 
322:     /**
323:      * Get the name of action
324:      *
325:      * @return string
326:      */
327:     public function getAction()
328:     {
329:         return $this->action ?: $this->defaultAction;
330:     }
331: 
332:     /**
333:      * Set the name of action
334:      *
335:      * @param  string $action The name of action
336:      * @return string
337:      */
338:     public function setAction($action)
339:     {
340:         $this->action = $action;
341:         return $this;
342:     }
343: 
344:     /**
345:      * Returns the URI that contains controller and action
346:      *
347:      * @return string
348:      */
349:     public function getControllerAction()
350:     {
351:         return $this->getController() . '/' . $this->getAction();
352:     }
353: 
354:     /**
355:      * Return the controller class names by controllers (without validate if the class exists)
356:      *
357:      * @param string $controller The name of controller
358:      * @return array
359:      */
360:     public function getControllerClasses($controller)
361:     {
362:         $classes = array();
363: 
364:         // Prepare parameters for replacing
365:         $namespace = $this->getNamespace();
366:         $controller = strtr($controller, array('/' => '\\'));
367: 
368:         // Generate class from format
369:         $class = str_replace(
370:             array('%namespace%', '%controller%'),
371:             array($namespace, $controller),
372:             $this->controllerFormat
373:         );
374: 
375:         // Make the class name's first letter uppercase
376:         $upperLetter = strrpos($class, '\\') + 1;
377:         $class[$upperLetter] = strtoupper($class[$upperLetter]);
378:         $classes[] = $class;
379: 
380:         // Add class from predefined classes
381:         if (isset($this->controllerMap[$controller])) {
382:             $classes[] = $this->controllerMap[$controller];
383:         }
384: 
385:         return $classes;
386:     }
387: 
388:     public function setControllerMap(array $controllerMap)
389:     {
390:         $this->controllerMap += $controllerMap;
391:     }
392: 
393:     /**
394:      * Get the controller instance, if not found, return false instead
395:      *
396:      * @param string $class The class name of controller
397:      * @return \Wei\Base
398:      */
399:     protected function getControllerInstance($class)
400:     {
401:         if (!isset($this->controllerInstances[$class])) {
402:             $this->controllerInstances[$class] = new $class(array(
403:                 'wei' => $this->wei,
404:                 'app' => $this,
405:             ));
406:         }
407:         return $this->controllerInstances[$class];
408:     }
409: 
410:     /**
411:      * Check if action name is available
412:      *
413:      * Returns false when
414:      * 1. method is not found
415:      * 2. method is not public
416:      * 3. method letters case error
417:      * 4. method is starts with "_"
418:      *
419:      * @param object $object The object of controller
420:      * @param string $action The name of action
421:      * @return bool
422:      */
423:     public function isActionAvailable($object, $action)
424:     {
425:         try {
426:             $ref = new \ReflectionMethod($object, $action);
427:             if ($ref->isPublic() && $action === $ref->name && $action[0] !== '_') {
428:                 return true;
429:             } else {
430:                 return false;
431:             }
432:         } catch (\ReflectionException $e) {
433:             return false;
434:         }
435:     }
436: 
437:     /**
438:      * Throws a exception to prevent the previous dispatch process
439:      *
440:      * @throws \RuntimeException
441:      */
442:     public function preventPreviousDispatch()
443:     {
444:         throw new \RuntimeException('Forwarding, please ignore me', self::FORWARD);
445:     }
446: 
447:     /**
448:      * Get default template file according to the controller, action and file
449:      * extension provided by the view engine
450:      *
451:      * @return string
452:      */
453:     public function getDefaultTemplate()
454:     {
455:         return lcfirst($this->controller) . '/' . $this->action . $this->view->getExtension();
456:     }
457: 
458:     /**
459:      * Forwards to the given controller and action
460:      *
461:      * @param string $controller The name of controller
462:      * @param null|string $action The name of action
463:      */
464:     public function forward($controller, $action = null)
465:     {
466:         $this->dispatch($controller, $action);
467:         $this->preventPreviousDispatch();
468:     }
469: }
Wei Framework API documentation generated by ApiGen