├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── composer.json
├── phpunit.xml.dist
├── src
├── Action.php
├── ActionController.php
├── ActionMiddleware.php
├── App.php
├── Bag.php
├── Configurator.php
├── Container.php
├── Controller.php
├── Dumper
│ ├── BaseDumper.php
│ ├── CliDumper.php
│ ├── DumperInterface.php
│ └── HtmlDumper.php
├── Exceptions
│ ├── FatalErrorException.php
│ ├── HttpErrorException.php
│ └── HttpNotFoundException.php
├── Hook.php
├── Http
│ ├── Request.php
│ ├── Response.php
│ ├── ResponseHeaderBag.php
│ └── UploadedFile.php
├── MacroableTrait.php
├── Provider.php
├── Router
│ ├── Route.php
│ ├── RouteGroup.php
│ └── Router.php
├── Util
│ ├── Arr.php
│ └── Str.php
└── View
│ ├── BasicViewEngine.php
│ ├── View.php
│ ├── ViewEngineInterface.php
│ └── ViewServiceProvider.php
└── tests
├── BagTests.php
├── ContainerTests.php
├── HookTests.php
├── RouterTests.php
├── RunAppTests.php
└── resources
└── views
├── hello-name.php
└── hello.php
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | composer.lock
3 | phpunit
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.5
5 | - 5.6
6 | - 7.0
7 |
8 | before_script:
9 | - composer self-update
10 | - composer install --dev
11 |
12 | script: phpunit --coverage-text
13 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ##Copyright (c) 2015 Muhammad Syifa
2 |
3 | #MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Rakit Framework
2 | =================
3 |
4 | [](https://travis-ci.org/rakit/framework)
5 | [](https://www.versioneye.com/user/projects/5683f558eb4f47003c000b64)
6 | [](https://github.com/emsifa/rakit-framework/issues)
7 | [](http://doge.mit-license.org)
8 |
9 | Rakit framework adalah PHP micro framework untuk membantu kamu membuat web app atau web service dengan mudah.
10 | Rakit framework pada dasarnya terinspirasi dari Laravel dan Slim framework.
11 | Untuk itu beberapa API akan terlihat mirip-mirip dengan framework tersebut.
12 |
13 | ## Dokumentasi
14 |
15 | * Indonesia: [https://rakit.github.io/id](https://rakit.github.io/id/index.html)
16 | * English: [https://rakit.github.io](https://rakit.github.io)
17 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rakit/framework",
3 | "description": "Another PHP micro framework inspired by Slim 2, laravel and lumen",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Muhammad Syifa",
8 | "email": "emsifa@gmail.com"
9 | }
10 | ],
11 | "autoload": {
12 | "psr-4": {
13 | "Rakit\\Framework\\": "src/"
14 | }
15 | },
16 | "minimum-stability": "dev",
17 | "require": {},
18 | "require-dev": {
19 | "phpunit/phpunit": "4.*"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ./tests
5 |
6 |
7 |
8 |
9 | ./src
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Action.php:
--------------------------------------------------------------------------------
1 | app = $app;
35 | $this->type = $type;
36 | $this->index = $index;
37 | $this->action = $action;
38 | }
39 |
40 | /**
41 | * Get application instance
42 | *
43 | * @return Rakit\Framework\App
44 | */
45 | public function getApp()
46 | {
47 | return $this->app;
48 | }
49 |
50 | /**
51 | * Get action index
52 | *
53 | * @return int
54 | */
55 | public function getIndex()
56 | {
57 | return $this->index;
58 | }
59 |
60 | /**
61 | * Get action type
62 | *
63 | * @return string
64 | */
65 | public function getType()
66 | {
67 | return $this->type;
68 | }
69 |
70 | /**
71 | * Get defined action
72 | *
73 | * @return mixed
74 | */
75 | public function getDefinedAction()
76 | {
77 | return $this->action;
78 | }
79 |
80 | /**
81 | * Abstract get resolved callable
82 | *
83 | * @return callable
84 | */
85 | abstract public function getCallable();
86 |
87 | /**
88 | * Get next action
89 | *
90 | * @return Rakit\Framework\Action | null
91 | */
92 | public function getNext()
93 | {
94 | $actions = $this->app['actions'];
95 | $next_index = $this->index+1;
96 | return array_key_exists($next_index, $actions)? $actions[$next_index] : null;
97 | }
98 |
99 | /**
100 | * Check if action is controller
101 | *
102 | * @return boolean
103 | */
104 | public function isController()
105 | {
106 | return $this->getType() == 'controller';
107 | }
108 |
109 | /**
110 | * Check if action is middleware
111 | *
112 | * @return boolean
113 | */
114 | public function isMiddleware()
115 | {
116 | return $this->getType() == 'middleware';
117 | }
118 |
119 | /**
120 | * Check if defined action using string class method like ClassName@method
121 | *
122 | * @return boolean
123 | */
124 | public function useStringClassMethod()
125 | {
126 | $defined_action = (string) $this->getDefinedAction();
127 | return count(explode('@', $defined_action)) == 2;
128 | }
129 |
130 | /**
131 | * Run action
132 | *
133 | * @return Rakit\Framework\Http\Response
134 | */
135 | public function run()
136 | {
137 | $app = $this->getApp();
138 | $callable = $this->getCallable();
139 |
140 | if (!is_callable($callable)) {
141 | $defined_action = (string) $this->getDefinedAction();
142 | $reason = $defined_action." is not callable";
143 | if ($this->useStringClassMethod()) {
144 | list($class, $method) = explode('@', $defined_action, 2);
145 | if (!class_exists($class)) {
146 | $reason = "Class {$class} doesn't exists";
147 | } elseif (!method_exists($class, $method)) {
148 | $reason = "Method {$class}::{$method} doesn't exists";
149 | }
150 | }
151 |
152 | throw new \InvalidArgumentException("Cannot run action ".$this->getType()." '".$this->getDefinedAction()."'. ".$reason, 1);
153 | }
154 |
155 | $returned = call_user_func($callable);
156 |
157 | if(is_array($returned)) {
158 | $app->response->json($returned, null, $app->response->getContentType());
159 | } elseif(is_string($returned)) {
160 | $app->response->html($returned, null, $app->response->getContentType());
161 | }
162 |
163 | return $returned;
164 | }
165 |
166 | /**
167 | * Invoke action
168 | *
169 | * @return Rakit\Framework\Http\Response
170 | */
171 | public function __invoke()
172 | {
173 | return $this->run();
174 | }
175 |
176 | }
--------------------------------------------------------------------------------
/src/ActionController.php:
--------------------------------------------------------------------------------
1 | getDefinedAction();
20 | $matched_route = $this->app->request->route();
21 | $params = $matched_route->params;
22 |
23 | $callable = $this->app->resolveController($action, $params);
24 |
25 | return $callable;
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/src/ActionMiddleware.php:
--------------------------------------------------------------------------------
1 | getNext();
22 | $action = $this->getDefinedAction();
23 |
24 | $params = [$this->app->request, $this->app->response, $next];
25 | $callable = $this->app->resolveMiddleware($action, $params);
26 |
27 | return $callable;
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/src/App.php:
--------------------------------------------------------------------------------
1 | name = $name;
57 | $default_configs = [];
58 | $configs = array_merge($default_configs, $configs);
59 |
60 | $this->container = new Container;
61 | $this['app'] = $this;
62 | $this['config'] = new Configurator($configs);
63 | $this['router'] = new Router($this);
64 | $this['hook'] = new Hook($this);
65 | $this['request'] = new Request($this);
66 | $this['response'] = new Response($this);
67 |
68 | static::$instances[$name] = $this;
69 |
70 | if(count(static::$instances) == 1) {
71 | static::setDefaultInstance($name);
72 | }
73 |
74 | $this->registerErrorHandlers();
75 | $this->registerBaseHooks();
76 | $this->registerDefaultMacros();
77 | $this->registerBaseProviders();
78 | }
79 |
80 | /**
81 | * Register a Service Provider into waiting lists
82 | *
83 | * @param string $class
84 | */
85 | public function provide($class)
86 | {
87 | $this->providers[$class] = $provider = $this->container->make($class);
88 | if(false === $provider instanceof Provider) {
89 | throw new InvalidArgumentException("Provider {$class} must be instance of Rakit\\Framework\\Provider", 1);
90 | }
91 |
92 | $provider->register();
93 | }
94 |
95 | /**
96 | * Register hook
97 | *
98 | * @param string $event
99 | * @param Closure $callable
100 | */
101 | public function on($event, Closure $callable)
102 | {
103 | return $this->hook->on($event, $callable);
104 | }
105 |
106 | /**
107 | * Register hook once
108 | *
109 | * @param string $event
110 | * @param Closure $callable
111 | */
112 | public function once($event, Closure $callable)
113 | {
114 | return $this->hook->once($event, $callable);
115 | }
116 |
117 | /**
118 | * Set middleware
119 | *
120 | * @param string $name
121 | * @param mixed $callable
122 | * @return void
123 | */
124 | public function setMiddleware($name, $callable)
125 | {
126 | $this->middlewares[$name] = $callable;
127 | }
128 |
129 | /**
130 | * Check middleware is registered or not
131 | *
132 | * @param string $name
133 | * @return void
134 | */
135 | public function hasMiddleware($name)
136 | {
137 | return isset($this->middlewares[$name]);
138 | }
139 |
140 | /**
141 | * Add global middleware
142 | *
143 | * @param string|callable $name_or_callable
144 | * @return void
145 | */
146 | public function useMiddleware($name_or_callable)
147 | {
148 | if (!is_callable($name_or_callable)) {
149 | if (is_string($name_or_callable) AND $this->hasMiddleware($name_or_callable)) {
150 | $callable = $this->middlewares[$name_or_callable];
151 | } else {
152 | throw new InvalidArgumentException("Cannot use global middleware. Middleware must be callable or registered middleware", 1);
153 | }
154 | } else {
155 | $callable = $name_or_callable;
156 | }
157 |
158 | $this->global_middlewares[] = $callable;
159 | }
160 |
161 | /**
162 | * Register GET route
163 | *
164 | * @param string $path
165 | * @param mixed $action
166 | * @return Rakit\Framework\Routing\Route
167 | */
168 | public function get($path, $action)
169 | {
170 | return $this->route('GET', $path, $action);
171 | }
172 |
173 | /**
174 | * Register POST route
175 | *
176 | * @param string $path
177 | * @param mixed $action
178 | * @return Rakit\Framework\Routing\Route
179 | */
180 | public function post($path, $action)
181 | {
182 | return $this->route('POST', $path, $action);
183 | }
184 |
185 | /**
186 | * Register PUT route
187 | *
188 | * @param string $path
189 | * @param mixed $action
190 | * @return Rakit\Framework\Routing\Route
191 | */
192 | public function put($path, $action)
193 | {
194 | return $this->route('PUT', $path, $action);
195 | }
196 |
197 | /**
198 | * Register PATCH route
199 | *
200 | * @param string $path
201 | * @param mixed $action
202 | * @return Rakit\Framework\Routing\Route
203 | */
204 | public function patch($path, $action)
205 | {
206 | return $this->route('PATCH', $path, $action);
207 | }
208 |
209 | /**
210 | * Register DELETE route
211 | *
212 | * @param string $path
213 | * @param mixed $action
214 | * @return Rakit\Framework\Routing\Route
215 | */
216 | public function delete($path, $action)
217 | {
218 | return $this->route('DELETE', $path, $action);
219 | }
220 |
221 | /**
222 | * Register Group route
223 | *
224 | * @param string $path
225 | * @param Closure $grouper
226 | * @return Rakit\Framework\Routing\Route
227 | */
228 | public function group($prefix, Closure $grouper)
229 | {
230 | return $this->router->group($prefix, $grouper);
231 | }
232 |
233 | /**
234 | * Registering a route
235 | *
236 | * @param string $path
237 | * @param mixed $action
238 | * @return Rakit\Framework\Routing\Route
239 | */
240 | public function route($methods, $path, $action)
241 | {
242 | $route = $this->router->add($methods, $path, $action);
243 | if (!empty($this->global_middlewares)) {
244 | $route->middleware($this->global_middlewares);
245 | }
246 | return $route;
247 | }
248 |
249 | /**
250 | * Handle specified exception
251 | */
252 | public function handle($exception_class, Closure $fn)
253 | {
254 | $this->exception_handlers[$exception_class] = $fn;
255 | }
256 |
257 | /**
258 | * Booting app
259 | *
260 | * @return boolean
261 | */
262 | public function boot()
263 | {
264 | if($this->booted) return false;
265 |
266 | $app = $this;
267 |
268 | $providers = $this->providers;
269 | foreach($providers as $provider) {
270 | $provider->boot();
271 | }
272 |
273 | // reset providers, we don't need them anymore
274 | $this->providers = [];
275 |
276 | return $this->booted = true;
277 | }
278 |
279 | /**
280 | * Run application
281 | *
282 | * @param string $path
283 | * @param string $method
284 | * @return void
285 | */
286 | public function run($method = null, $path = null)
287 | {
288 | try {
289 | $this->boot();
290 |
291 | $path = $path ?: $this->request->path();
292 | $method = $method ?: $this->request->server['REQUEST_METHOD'];
293 |
294 | /**
295 | * for HEAD request
296 | * instead to add some code in router that will slow down performance
297 | * we trick it by change it to GET for dispatching only
298 | */
299 | $matched_route = $this->router->findMatch($path, $method == 'HEAD'? 'GET' : $method);
300 |
301 | if(!$matched_route) {
302 | return $this->notFound();
303 | }
304 |
305 | $this->request->defineRoute($matched_route);
306 | $this->hook->apply(strtoupper($method), [$matched_route, $this]);
307 |
308 | $middlewares = $matched_route->getMiddlewares();
309 | $action = $matched_route->getAction();
310 |
311 | $actions = $this->makeActions($middlewares, $action);
312 | if(isset($actions[0])) {
313 | $actions[0]();
314 | }
315 |
316 | $this->response->send();
317 |
318 | return $this;
319 | } catch (Exception $e) {
320 | return $this->exception($e);
321 | }
322 | }
323 |
324 | public function exception(Exception $e)
325 | {
326 | $status_code = $e->getCode();
327 | $status_message = $this->response->getStatusMessage($status_code);
328 |
329 | // if status message is null,
330 | // that mean 'exception code' is not one of 'available http response status codes'
331 | // so, change it to 500
332 | if(!$status_message) {
333 | $status_code = 500;
334 | }
335 |
336 | $this->response->setStatus($status_code);
337 |
338 | // because we register exception by handle() method,
339 | // we will manually catch exception class
340 | // first we need to get exception class
341 | $exception_class = get_class($e);
342 |
343 | // then we need parent classes too
344 | $exception_classes = array_values(class_parents($exception_class));
345 | array_unshift($exception_classes, $exception_class);
346 |
347 | // now $exception_classes should be ['CatchedException', 'CatchedExceptionParent', ..., 'Exception']
348 | // next, we need to get exception handler
349 | $custom_handler = null;
350 | foreach($exception_classes as $xclass) {
351 | if(array_key_exists($xclass, $this->exception_handlers)) {
352 | $custom_handler = $this->exception_handlers[$xclass];
353 | }
354 |
355 | $this->hook->apply($xclass, [$e]);
356 | }
357 |
358 | $this->hook->apply('error', [$e]);
359 |
360 | if(true === $this->config['app.debug']) {
361 | $this->debugException($e);
362 | } else {
363 | if($custom_handler) {
364 | $this->container->call($custom_handler, [$e]);
365 | } elseif($e instanceof HttpNotFoundException) {
366 | $this->response->html("Error 404! Page not found");
367 | } else {
368 | $this->response->html("Something went wrong");
369 | }
370 | }
371 |
372 | $this->response->send();
373 | return $this;
374 | }
375 |
376 | protected function debugException(Exception $e)
377 | {
378 | $debugger = PHP_SAPI == 'cli'? new CliDumper : new HtmlDumper;
379 | $this->response->html($debugger->render($e));
380 | }
381 |
382 | /**
383 | * Stop application
384 | *
385 | * @return void
386 | */
387 | public function stop()
388 | {
389 | $this->hook->apply("app.exit", [$this]);
390 | exit();
391 | }
392 |
393 | /**
394 | * Not Found
395 | */
396 | public function notFound()
397 | {
398 | $method = $this->request->server['REQUEST_METHOD'];
399 | $path = $this->request->path();
400 |
401 | if($this->request->route()) {
402 | $message = "Error 404! Looks like you are throwing this manually";
403 | } else {
404 | $message = "Error 404! No route matched with '{$method} {$path}'";
405 | }
406 |
407 | throw new HttpNotFoundException($message);
408 | }
409 |
410 | /**
411 | * Abort app
412 | *
413 | * @param int $status
414 | *
415 | * @return void
416 | */
417 | public function abort($status, $message = null)
418 | {
419 | if($status == 404) {
420 | return $this->notFound();
421 | } else {
422 | throw new HttpErrorException;
423 | }
424 | }
425 |
426 | /**
427 | * Set default instance name
428 | *
429 | * @param string $name
430 | */
431 | public static function setDefaultInstance($name)
432 | {
433 | static::$default_instance = $name;
434 | }
435 |
436 | /**
437 | * Getting an application instance
438 | *
439 | * @param string $name
440 | */
441 | public static function getInstance($name = null)
442 | {
443 | if(!$name) $name = static::$default_instance;
444 | return static::$instances[$name];
445 | }
446 |
447 | /**
448 | * Make/build app actions
449 | *
450 | * @param array $middlewares
451 | * @param mixed $controller
452 | * @return void
453 | */
454 | protected function makeActions(array $middlewares, $controller)
455 | {
456 | $app = $this;
457 | $actions = array_merge($middlewares, [$controller]);
458 | $index_controller = count($actions)-1;
459 |
460 | $actions = [];
461 | foreach($middlewares as $i => $action) {
462 | $actions[] = new ActionMiddleware($this, $i, $action);
463 | }
464 |
465 | $actions[] = new ActionController($this, count($middlewares), $controller);
466 |
467 | $this['actions'] = $actions;
468 | return $actions;
469 | }
470 |
471 | /**
472 | * Resolving middleware action
473 | */
474 | public function resolveMiddleware($middleware_action, array $params = array())
475 | {
476 | if(is_string($middleware_action)) {
477 | $explode_params = explode(':', $middleware_action);
478 |
479 | $middleware_name = $explode_params[0];
480 | if(isset($explode_params[1])) {
481 | $params = array_merge($params, explode(',', $explode_params[1]));
482 | }
483 |
484 | // if middleware is registered, get middleware
485 | if(array_key_exists($middleware_name, $this->middlewares)) {
486 | // Get middleware. so now, callable should be string Foo@bar, Closure, or function name
487 | $callable = $this->middlewares[$middleware_name];
488 | $resolved_callable = $this->resolveCallable($callable, $params);
489 | } else {
490 | // othwewise, middleware_name should be Foo@bar or Foo
491 | $callable = $middleware_name;
492 | $resolved_callable = $this->resolveCallable($callable, $params);
493 | }
494 | } else {
495 | $resolved_callable = $this->resolveCallable($middleware_action, $params);
496 | }
497 |
498 | if(!is_callable($resolved_callable)) {
499 | if(is_array($middleware_action)) {
500 | $invalid_middleware = 'Array';
501 | } else {
502 | $invalid_middleware = $middleware_action;
503 | }
504 |
505 | throw new \Exception('Middleware "'.$invalid_middleware.'" is not valid middleware or it is not registered');
506 | }
507 |
508 | return $resolved_callable;
509 | }
510 |
511 | public function resolveController($controller_action, array $params = array())
512 | {
513 | return $this->resolveCallable($controller_action, $params);
514 | }
515 |
516 | /**
517 | * Register base hooks
518 | */
519 | protected function registerBaseHooks()
520 | {
521 |
522 | }
523 |
524 | /**
525 | * Register base providers
526 | */
527 | public function registerBaseProviders()
528 | {
529 | $base_providers = [
530 | 'Rakit\Framework\View\ViewServiceProvider',
531 | ];
532 |
533 | foreach($base_providers as $provider_class) {
534 | $this->provide($provider_class);
535 | }
536 | }
537 |
538 | /**
539 | * Register error handler
540 | */
541 | public function registerErrorHandlers()
542 | {
543 | $app = $this;
544 |
545 | // set error handler
546 | set_error_handler(function($severity, $message, $file, $line) use ($app) {
547 | if (!(error_reporting() & $severity)) {
548 | return;
549 | }
550 |
551 | $exception = new ErrorException($message, 500, $severity, $file, $line);
552 | $app->exception($exception);
553 | $app->stop();
554 | });
555 |
556 | // set fatal error handler
557 | register_shutdown_function(function() use ($app) {
558 | $error = error_get_last();
559 | if($error) {
560 | $errno = $error["type"];
561 | $errfile = $error["file"];
562 | $errline = $error["line"];
563 | $errstr = $error["message"];
564 |
565 | $message = "[$errno] $errstr in $errfile line $errline";
566 |
567 | $exception = new FatalErrorException($message, 500, 1, $errfile, $errline);
568 |
569 | $app->exception($exception);
570 | $app->stop();
571 | }
572 | });
573 | }
574 |
575 | /**
576 | * Register default macros
577 | */
578 | protected function registerDefaultMacros()
579 | {
580 | $this->macro('resolveCallable', function($unresolved_callable, array $params = array()) {
581 | if(is_string($unresolved_callable)) {
582 | // in case "Foo@bar:baz,qux", baz and qux should be parameters, separate it!
583 | $explode_params = explode(':', $unresolved_callable);
584 |
585 | $unresolved_callable = $explode_params[0];
586 | if(isset($explode_params[1])) {
587 | $params = array_merge($params, explode(',', $explode_params[1]));
588 | }
589 |
590 | // now $unresolved_callable should be "Foo@bar" or "foo",
591 | // if there is '@' character, transform it to array class callable
592 | $explode_method = explode('@', $unresolved_callable);
593 | if(isset($explode_method[1])) {
594 | $callable = [$explode_method[0], $explode_method[1]];
595 | } else {
596 | // otherwise, just leave it as string, maybe that was function name
597 | $callable = $explode_method[0];
598 | }
599 | } else {
600 | $callable = $unresolved_callable;
601 | }
602 |
603 | $app = $this;
604 |
605 | // last.. wrap callable in Closure
606 | return !is_callable($callable)? false : function() use ($app, $callable, $params) {
607 | if ($callable instanceof Closure) {
608 | $callable = Closure::bind($callable, $app, App::class);
609 | }
610 | return $app->container->call($callable, $params);
611 | };
612 | });
613 |
614 | $this->macro('baseUrl', function($path) {
615 | $path = '/'.trim($path, '/');
616 | $base_url = trim($this->config->get('app.base_url', 'http://localhost:8000'), '/');
617 |
618 | return $base_url.$path;
619 | });
620 |
621 | $this->macro('asset', function($path) {
622 | return $this->baseUrl($path);
623 | });
624 |
625 | $this->macro('indexUrl', function($path) {
626 | $path = trim($path, '/');
627 | $index_file = trim($this->config->get('app.index_file', ''), '/');
628 | return $this->baseUrl($index_file.'/'.$path);
629 | });
630 |
631 | $this->macro('routeUrl', function($route_name, array $params = array()) {
632 | if($route_name instanceof Route) {
633 | $route = $route_name;
634 | } else {
635 | $route = $this->router->findRouteByName($route_name);
636 | if(! $route) {
637 | throw new \Exception("Trying to get url from unregistered route named '{$route_name}'");
638 | }
639 | }
640 |
641 | $path = $route->getPath();
642 | $path = str_replace(['(',')'], '', $path);
643 | foreach($params as $param => $value) {
644 | $path = preg_replace('/:'.$param.'\??/', $value, $path);
645 | }
646 |
647 | $path = preg_replace('/\/?\:[a-zA-Z0-9._-]+/','', $path);
648 |
649 | return $this->indexUrl($path);
650 | });
651 |
652 | $this->macro('redirect', function($defined_url) {
653 | if(preg_match('/http(s)?\:\/\//', $defined_url)) {
654 | $url = $defined_url;
655 | } elseif($this->router->findRouteByName($defined_url)) {
656 | $url = $this->routeUrl($defined_url);
657 | } else {
658 | $url = $this->indexUrl($defined_url);
659 | }
660 |
661 | $this->hook->apply('response.redirect', [$url, $defined_url]);
662 |
663 | header("Location: ".$url);
664 | exit();
665 | });
666 |
667 |
668 | $this->macro('dd', function() {
669 | var_dump(func_get_args());
670 | exit();
671 | });
672 |
673 | $app = $this;
674 | $this->response->macro('redirect', function($defined_url) use ($app) {
675 | return $app->redirect($defined_url);
676 | });
677 | }
678 |
679 | public function bind($key, $value)
680 | {
681 | if (is_string($value)) {
682 | if (!class_exists($value)) {
683 | throw new InvalidArgumentException("Cannot bind {$value}, class {$value} is not exists");
684 | }
685 |
686 | $value = function($container) use ($value) {
687 | return $container->getOrMake($value);
688 | };
689 | }
690 |
691 | $this->container->register($key, $value);
692 | }
693 |
694 | /**
695 | * ---------------------------------------------------------------
696 | * Setter and getter
697 | * ---------------------------------------------------------------
698 | */
699 | public function __set($key, $value)
700 | {
701 | $this->container->register($key, $value);
702 | }
703 |
704 | public function __get($key)
705 | {
706 | return $this->container->get($key);
707 | }
708 |
709 | /**
710 | * ---------------------------------------------------------------
711 | * ArrayAccess interface methods
712 | * ---------------------------------------------------------------
713 | */
714 | public function offsetSet($key, $value) {
715 | return $this->container->register($key, $value);
716 | }
717 |
718 | public function offsetExists($key) {
719 | return $this->container->has($key);
720 | }
721 |
722 | public function offsetUnset($key) {
723 | return $this->container->remove($key);
724 | }
725 |
726 | public function offsetGet($key) {
727 | return $this->container->get($key);
728 | }
729 |
730 | }
731 |
--------------------------------------------------------------------------------
/src/Bag.php:
--------------------------------------------------------------------------------
1 | items = $items;
21 | }
22 |
23 | /**
24 | * Check existances of a key
25 | *
26 | * @param string $key
27 | * @return boolean
28 | */
29 | public function has($key) {
30 | $key = $this->resolveKey($key);
31 | return Arr::has($this->items, $key);
32 | }
33 |
34 | /**
35 | * Set item
36 | *
37 | * @param string $key
38 | * @param mixed $value
39 | * @return self
40 | */
41 | public function set($key, $value) {
42 | $key = $this->resolveKey($key);
43 |
44 | if(is_array($key)) {
45 | $this->items = $key;
46 | } else {
47 | Arr::set($this->items, $key, $value);
48 | }
49 |
50 | return $this;
51 | }
52 |
53 | /**
54 | * Get item value by given key
55 | *
56 | * @param string $key
57 | * @param mixed $default
58 | * @return mixed
59 | */
60 | public function get($key, $default = null) {
61 | $key = $this->resolveKey($key);
62 |
63 | return Arr::get($this->items, $key, $default);
64 | }
65 |
66 | /**
67 | * Remove item by given key
68 | *
69 | * @param string $key
70 | * @return self
71 | */
72 | public function remove($key) {
73 | $key = $this->resolveKey($key);
74 |
75 | Arr::remove($this->items, $key);
76 | return $this;
77 | }
78 |
79 | /**
80 | * Get items by given keys
81 | *
82 | * @param array $keys
83 | * @param mixed $default
84 | * @return array
85 | */
86 | public function only($keys, $result_array = false)
87 | {
88 | $keys = (array) $keys;
89 | $bag = new static;
90 | foreach($keys as $key) {
91 | $key = $this->resolveKey($key);
92 | $bag[$key] = $this->get($key);
93 | }
94 |
95 | return $result_array? $bag->all() : $bag;
96 | }
97 |
98 | /**
99 | * Get all items, except given keys
100 | *
101 | * @param array $keys
102 | * @return array
103 | */
104 | public function except($keys, $result_array = false)
105 | {
106 | $keys = (array) $keys;
107 | $bag = new static;
108 |
109 | foreach($this->all(false) as $key => $value) {
110 | $key = $this->resolveKey($key);
111 | $bag->set($key, $value);
112 | }
113 |
114 | foreach($keys as $key) {
115 | $key = $this->resolveKey($key);
116 | $bag->remove($key);
117 | }
118 |
119 | return $result_array? $bag->all() : $bag;
120 | }
121 |
122 | /**
123 | * Get all items
124 | *
125 | * @param bool $deep
126 | * @return array
127 | */
128 | public function all($deep = true) {
129 | $items = $this->items;
130 |
131 | if($deep) {
132 | foreach($this->namespaces as $ns => $bag) {
133 | $items[$ns] = $bag->all(true);
134 | }
135 | }
136 |
137 | return $items;
138 | }
139 |
140 | /**
141 | * Get count item keys
142 | *
143 | * @param bool $deep
144 | * @return int
145 | */
146 | public function count($deep = false)
147 | {
148 | $items = $this->all($deep);
149 | return count($items);
150 | }
151 |
152 | /**
153 | * Get items size
154 | *
155 | * @param bool $deep
156 | * @return int
157 | */
158 | public function size($deep = true)
159 | {
160 | $values = Arr::flatten($this->all($deep));
161 | return count($values);
162 | }
163 |
164 | public function resolveKey($key)
165 | {
166 | return $key;
167 | }
168 |
169 | /**
170 | * ---------------------------------------------------------------------------------------
171 | * ArrayAccess interface methods
172 | * ---------------------------------------------------------------------------------------
173 | */
174 | public function offsetSet($key, $value) {
175 | $this->set($key, $value);
176 | }
177 |
178 | public function offsetExists($key) {
179 | return $this->has($key);
180 | }
181 |
182 | public function offsetUnset($key) {
183 | $this->remove($key);
184 | }
185 |
186 | public function offsetGet($key) {
187 | return $this->get($key, null);
188 | }
189 |
190 | /**
191 | * ---------------------------------------------------------------------------------------
192 | * Namespace setter and getter
193 | * ---------------------------------------------------------------------------------------
194 | */
195 | public function __set($namespace, $value) {
196 | $bag = new Bag();
197 | $this->namespaces[$namespace] = $bag;
198 |
199 | if(is_array($value)) {
200 | $bag->set($value, null);
201 | }
202 | }
203 |
204 | public function __get($namespace) {
205 | if(isset($this->namespaces[$namespace])) {
206 | return $this->namespaces[$namespace];
207 | } else {
208 | throw new \Exception("Undefined config namespace {$namespace}");
209 | }
210 | }
211 |
212 | }
213 |
--------------------------------------------------------------------------------
/src/Configurator.php:
--------------------------------------------------------------------------------
1 | loadFile($file);
20 | }
21 | }
22 |
23 | /**
24 | * Merge configs from configurations in a directory
25 | *
26 | * @param string $dir
27 | * @return void
28 | */
29 | public function mergeDir($dir)
30 | {
31 | $configs = new static;
32 | $configs->loadDir($dir);
33 |
34 | $this_configs = $this->all(false);
35 | $merge_configs = $configs->all(false);
36 |
37 | $merged_configs = array_replace_recursive($this_configs, $merge_configs);
38 |
39 | $this->items = $merged_configs;
40 | }
41 |
42 | /**
43 | * Load configs from a file
44 | *
45 | * @param string $file
46 | * @return void
47 | */
48 | public function loadFile($file)
49 | {
50 | $filename = pathinfo($file, PATHINFO_FILENAME);
51 | $this->set($filename, require($file));
52 | }
53 |
54 | /**
55 | * Set configuration namespace
56 | *
57 | * @param string $namespace
58 | * @param mixed $value
59 | * @return void
60 | */
61 | public function __set($namespace, $value)
62 | {
63 | $bag = new static;
64 | $this->namespaces[$namespace] = $bag;
65 |
66 | if(is_array($value)) {
67 | $bag->set($value, null);
68 | } elseif(is_string($value)) {
69 | $bag->loadDir($value);
70 | }
71 | }
72 |
73 | }
--------------------------------------------------------------------------------
/src/Container.php:
--------------------------------------------------------------------------------
1 | container[$this->resolveKey($k)] = $value;
47 | }
48 | }
49 |
50 | /**
51 | * Protect anonymous function value
52 | *
53 | * @param Closure $fn
54 | * @return Closure
55 | */
56 | public function protect(Closure $fn)
57 | {
58 | return function() use ($fn) {
59 | return $fn;
60 | };
61 | }
62 |
63 | /**
64 | * Wrap singleton service in a closure
65 | *
66 | * @param Closure $fn
67 | * @return Closure
68 | */
69 | public function singleton(Closure $fn)
70 | {
71 | return function($container) use ($fn) {
72 | static $object;
73 |
74 | if(!$object) {
75 | $object = $fn($container);
76 | }
77 |
78 | return $object;
79 | };
80 | }
81 |
82 | /**
83 | * Check key
84 | *
85 | * @param string $key
86 | * @return bool
87 | */
88 | public function has($key)
89 | {
90 | return isset($this->container[$key]);
91 | }
92 |
93 | /**
94 | * Get value for specified key in container
95 | *
96 | * @param string $key
97 | * @return mixed
98 | */
99 | public function get($key)
100 | {
101 | if(!$this->has($key)) return null;
102 |
103 | $closure = $this->container[$key];
104 | $value = $closure($this);
105 |
106 | // register classname into container if not exists
107 | if(is_object($value)) {
108 | $class = get_class($value);
109 | if(!$this->has($class)) $this->register($class, $closure);
110 | }
111 |
112 | return $value;
113 | }
114 |
115 | /**
116 | * Get or make value in container
117 | *
118 | * @param string $key
119 | * @param array $args
120 | * @return mixed
121 | */
122 | public function getOrMake($key, array $args = array())
123 | {
124 | if(!$this->has($key)) {
125 | return $this->make($key, $args);
126 | } else {
127 | return $this->get($key);
128 | }
129 | }
130 |
131 | /**
132 | * Remove a key in container
133 | *
134 | * @param string $key
135 | */
136 | public function remove($key)
137 | {
138 | unset($this->container[$key]);
139 | }
140 |
141 | /**
142 | * Make object with Injecting some dependencies into constructor
143 | *
144 | * @param string $class
145 | * @param array $args
146 | * @return Object
147 | */
148 | public function make($class, array $args = array())
149 | {
150 | $class = $this->resolveKey($class);
151 | $reflection = new ReflectionClass($class);
152 | $constructor = $reflection->getConstructor();
153 |
154 | if(!empty($constructor)) {
155 | $parameters = $constructor->getParameters();
156 | $args = $this->resolveArguments($parameters, $args);
157 | }
158 |
159 | return $reflection->newInstanceArgs($args);
160 | }
161 |
162 | /**
163 | * Call closure or method with injecting parameters
164 | *
165 | * @param callable $callable
166 | * @param array $args
167 | * @return mixed
168 | */
169 | public function call($callable, array $args = array(), array $class_args = array())
170 | {
171 | if(!is_callable($callable)) {
172 | return new InvalidArgumentException("Callable must be callable, ".gettype($callable)." given");
173 | }
174 |
175 | if(is_array($callable)) {
176 | list($class, $method) = $callable;
177 | $reflection = new ReflectionMethod($class, $method);
178 | if(is_string($callable[0])) {
179 | $callable[0] = $this->make($callable[0], $class_args);
180 | }
181 | } else {
182 | $reflection = new ReflectionFunction($callable);
183 | }
184 |
185 | $parameters = $reflection->getParameters();
186 | $args = $this->resolveArguments($parameters, $args);
187 |
188 | return call_user_func_array($callable, $args);
189 | }
190 |
191 | /**
192 | * Resolve key
193 | *
194 | * @param string $key
195 | * @return string resolved key
196 | */
197 | protected function resolveKey($key)
198 | {
199 | return trim($key, "\\");
200 | }
201 |
202 | /**
203 | * Build arguments from array ReflectionParameter
204 | *
205 | * @param ReflectionParameter[] $parameters
206 | * @param array $args
207 | * @return array
208 | */
209 | public function resolveArguments(array $parameters, array $additional_args = array())
210 | {
211 | $resolved_args = [];
212 | $container = $this->container;
213 | foreach($additional_args as $arg_value) {
214 | if(is_object($arg_value)) {
215 | $container[get_class($arg_value)] = $arg_value;
216 | }
217 | }
218 |
219 | foreach($parameters as $i => $param) {
220 | $param_class = $param->getClass();
221 |
222 | if($param_class) {
223 | $classname = $param_class->getName();
224 | $arg = isset($additional_args[0])? $additional_args[0] : null;
225 | if (is_object($arg) AND get_class($arg) == $classname) {
226 | $resolved_args[] = $arg;
227 | } else {
228 | $resolved_args[] = $this->getOrMake($classname);
229 | }
230 | } elseif(!empty($additional_args)) {
231 | $resolved_args[] = array_shift($additional_args);
232 | }
233 | }
234 |
235 | return $resolved_args;
236 | }
237 |
238 | /**
239 | * Get Callable dependencies
240 | *
241 | * @return array Class dependencies
242 | */
243 | public static function getCallableDependencies($callable)
244 | {
245 | if(is_array($callable)) {
246 | list($class, $method) = $callable;
247 | $reflection = new ReflectionMethod($class, $method);
248 | } else {
249 | $reflection = new ReflectionFunction($callable);
250 | }
251 |
252 | $parameters = $reflection->getParameters();
253 |
254 | $dependencies = [];
255 |
256 | foreach($parameters as $param) {
257 | if($param->getClass()) {
258 | $dependencies[] = $param->getClass()->getName();
259 | }
260 | }
261 |
262 | return $dependencies;
263 | }
264 |
265 | /**
266 | * ---------------------------------------------------------------
267 | * ArrayAccess interface methods
268 | * ---------------------------------------------------------------
269 | */
270 | public function offsetSet($key, $value) {
271 | return $this->register($key, $value);
272 | }
273 |
274 | public function offsetExists($key) {
275 | return $this->has($key);
276 | }
277 |
278 | public function offsetUnset($key) {
279 | return $this->remove($key);
280 | }
281 |
282 | public function offsetGet($key) {
283 | return $this->get($key);
284 | }
285 |
286 | }
287 |
--------------------------------------------------------------------------------
/src/Controller.php:
--------------------------------------------------------------------------------
1 | app = $app;
10 | }
11 |
12 | public function __call($method, array $args)
13 | {
14 | return call_user_func_array([$this->app, $method], $args);
15 | }
16 |
17 | public function __get($key)
18 | {
19 | return $this->app->{$key};
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/Dumper/BaseDumper.php:
--------------------------------------------------------------------------------
1 | getFile()) {
13 | $file = $e->getFile();
14 | } elseif(isset($trace_data['class'])) {
15 | $ref = new \ReflectionClass($trace_data['class']);
16 | $file = $ref->getFileName();
17 | } else {
18 | $file = "unknown file";
19 | }
20 |
21 | return $file;
22 | }
23 |
24 | protected function getPossibilityLine(array $trace_data, \Exception $e = null)
25 | {
26 | if(isset($trace_data['line'])) {
27 | $line = $trace_data['line'];
28 | } elseif($e AND $e->getLine()) {
29 | $line = $e->getLine();
30 | } else {
31 | $line = null;
32 | }
33 |
34 | return $line;
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/src/Dumper/CliDumper.php:
--------------------------------------------------------------------------------
1 | getTrace();
11 | $main_cause = array_shift($trace);
12 | $index = count($e->getTrace());
13 | $result = "";
14 |
15 | $message = $e->getMessage();
16 | $class = get_class($e);
17 |
18 | // $result .= str_repeat("=", 60);
19 | $result .= "\n\n You got an '{$class}'\n";
20 | $result .= " \"{$message}\"\n";
21 |
22 | foreach($trace as $i => $data) {
23 | $file = $this->getPossibilityFile($data);
24 | $line = $this->getPossibilityLine($data);
25 | $message = $this->getMessage($data);
26 | $result .= "\n".str_pad(count($trace)-$i, 3, ' ', STR_PAD_LEFT).") ".$message;
27 | $result .= "\n {$file} [{$line}]\n";
28 | }
29 |
30 | $result .= "\n\n";
31 | // $result .= str_repeat("=", 60);
32 |
33 | return $result;
34 | }
35 |
36 | protected function getMessage($trace_data)
37 | {
38 | if(isset($trace_data['class'])) {
39 | $message = $trace_data['class'];
40 | if(isset($trace_data['function'])) {
41 | $message .= $trace_data['type'].$trace_data['function'].'()';
42 | }
43 | } elseif($trace_data['function']) {
44 | $message = $trace_data['function'].'()';
45 | } else {
46 | $message = "";
47 | }
48 |
49 | return $message;
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/src/Dumper/DumperInterface.php:
--------------------------------------------------------------------------------
1 | getTrace();
11 | $main_cause = array_shift($trace);
12 | $index = count($e->getTrace());
13 |
14 | if($e instanceof \ErrorException AND count($trace) > 0) {
15 | $main_cause = array_shift($trace);
16 | $index -= 1;
17 | }
18 |
19 | $dom_main_cause = $this->renderMainCause($index, $e, $main_cause);
20 | $dom_traces = $this->renderTraces($trace);
21 | $message = $e->getMessage();
22 | $class = get_class($e);
23 |
24 | $result = "
25 |
26 |
27 |
137 |
138 |
139 |
140 |
{$class}
141 |
{$message}
142 |
{$dom_main_cause}{$dom_traces}
143 |
144 |
145 |
146 | ";
147 |
148 | return $result;
149 | }
150 |
151 | protected function renderMainCause($index, \Exception $e, array $trace)
152 | {
153 | $message = $this->getMessage($trace);
154 | $file = $this->getPossibilityFile($trace, $e);
155 | $line = $this->getPossibilityLine($trace, $e);
156 |
157 | return "".$this->renderTrace($index, $message, $file, $line)."";
158 | }
159 |
160 | protected function renderTraces(array $traces)
161 | {
162 | $dom_traces = "";
163 | $count_traces = count($traces);
164 | foreach($traces as $i => $trace) {
165 | $no = $count_traces-$i;
166 | $message = $this->getMessage($trace);
167 | $file = $this->getPossibilityFile($trace);
168 | $line = $this->getPossibilityLine($trace);
169 |
170 | $dom_traces .= ''.$this->renderTrace($no, $message, $file, $line).'';
171 | }
172 | return $dom_traces;
173 | }
174 |
175 | protected function renderTrace($index, $message, $file, $line = null)
176 | {
177 | $dom_index = "#{$index}";
178 | $dom_message = "{$message}
";
179 | $info_line = $line? " on line {$line}": ", unknown line";
180 | $dom_info = "{$file}{$info_line}";
181 |
182 | return "{$dom_index}{$dom_message}{$dom_info}";
183 | }
184 |
185 | protected function getMessage($trace_data)
186 | {
187 | $args_string = implode(', ', $this->getArgsDefinition($trace_data));
188 |
189 | if(isset($trace_data['class'])) {
190 | $message = $trace_data['class'];
191 | if(isset($trace_data['function'])) {
192 | $message .= ''.$trace_data['type'].''.$trace_data['function'].'('.$args_string.')';
193 | }
194 | } elseif($trace_data['function']) {
195 | $message = $trace_data['function'].'('.$args_string.')';
196 | } else {
197 | $message = "";
198 | }
199 |
200 | return $message;
201 | }
202 |
203 | protected function getArgsDefinition(array $trace_data)
204 | {
205 | $args = isset($trace_data['args'])? $trace_data['args'] : [];
206 | $args_definitions = [];
207 | foreach ($args as $arg) {
208 | if (is_object($arg)) {
209 | $args_definitions[] = get_class($arg);
210 | } elseif(is_array($arg)) {
211 | $args_definitions[] = 'Array('.count($arg).')';
212 | } elseif(is_bool($arg)) {
213 | $args_definitions[] = $arg? 'true' : 'false';
214 | } elseif(is_string($arg)) {
215 | $args_definitions[] = '"'.$arg.'"';
216 | } else {
217 | $args_definitions[] = $arg;
218 | }
219 | }
220 |
221 | return $args_definitions;
222 | }
223 |
224 | }
--------------------------------------------------------------------------------
/src/Exceptions/FatalErrorException.php:
--------------------------------------------------------------------------------
1 | app = $app;
14 | $this->hooks = new Bag;
15 | }
16 |
17 | public function on($event, $actions, $limit = null)
18 | {
19 | $actions = (array) $actions;
20 | $hooks = $this->hooks->get($event, []);
21 | foreach($actions as $action) {
22 | $id = uniqid();
23 | $path = $event.'.'.$id;
24 | $hook = new \stdClass;
25 | $hook->action = $action;
26 | $hook->limit = $limit;
27 | $hook->path = $path;
28 |
29 | $hooks[$id] = $hook;
30 | }
31 |
32 | $this->hooks->set($event, $hooks);
33 | return $this;
34 | }
35 |
36 | public function once($event, $actions)
37 | {
38 | return $this->on($event, $actions, 1);
39 | }
40 |
41 | public function applyFirst($event)
42 | {
43 | $hooks = Arr::flatten($this->hooks->get($event, []));
44 | $first_hook = array_shift($hooks);
45 | return $first_hook? $this->applyHook($hook) : null;
46 | }
47 |
48 | public function applyLast($event)
49 | {
50 | $hooks = Arr::flatten($this->hooks->get($event, []));
51 | $last_hook = array_pop($hooks);
52 | return $last_hook? $this->applyHook($hook) : null;
53 | }
54 |
55 | public function apply($event, array $params = array())
56 | {
57 | $hooks = Arr::flatten($this->hooks->get($event, []));
58 | $results = [];
59 |
60 | foreach($hooks as $hook) {
61 | $results[] = $this->applyHook($hook, $params);
62 | }
63 |
64 | return $results;
65 | }
66 |
67 | protected function applyHook($hook, $params)
68 | {
69 | $path = $hook->path;
70 | $limit = $hook->limit;
71 | $action = $this->resolveCallable($hook->action, $params);
72 |
73 | $result = $this->app->container->call($action, $params);
74 |
75 | if(is_int($limit)) {
76 | $limit -= 1;
77 |
78 | if($limit < 1) {
79 | $this->hooks->remove($path);
80 | } else {
81 | $hook->limit = $limit;
82 | }
83 | }
84 |
85 | return $result;
86 | }
87 |
88 | public function resolveCallable($callable, $params)
89 | {
90 | return $this->app->resolveCallable($callable, $params);
91 | }
92 |
93 | }
--------------------------------------------------------------------------------
/src/Http/Request.php:
--------------------------------------------------------------------------------
1 | defineRoute($route);
27 | $this->app = $app;
28 |
29 | $this->inputs = new Bag((array) $_POST + (array) $_GET);
30 | $this->files = new Bag((array) $_FILES);
31 | $this->server = new Bag($_SERVER);
32 | }
33 |
34 | public function path()
35 | {
36 | $path_info = $this->server->get('PATH_INFO');
37 |
38 | if(!$path_info) {
39 | $request_uri = $this->server->get('REQUEST_URI');
40 | $script_name = $this->server->get('SCRIPT_NAME');
41 | if (pathinfo($script_name, PATHINFO_EXTENSION) == 'php') {
42 | $path_info = str_replace($script_name, '', $request_uri);
43 | } else {
44 | $path_info = $request_uri;
45 | }
46 |
47 | $path_info = explode('?', $path_info, 2)[0];
48 | }
49 |
50 | return '/'.trim($path_info, '/');
51 | }
52 |
53 | public function segment($index)
54 | {
55 | $paths = explode('/', $this->path());
56 | return isset($paths[$index])? $paths[$index] : null;
57 | }
58 |
59 | public function all()
60 | {
61 | $all_inputs = $this->inputs->all();
62 | $all_files = $this->files->all();
63 | foreach($all_files as $key => $value) {
64 | $all_files[$key] = $this->hasMultiFiles($key)? $this->multiFiles($key) : $this->file($key);
65 | }
66 |
67 | return array_merge($all_inputs, $all_files);
68 | }
69 |
70 | public function has($key)
71 | {
72 | return $this->inputs->has($key);
73 | }
74 |
75 | public function get($key, $default = null)
76 | {
77 | return $this->inputs->get($key, $default);
78 | }
79 |
80 | public function only(array $keys)
81 | {
82 | return $this->inputs->only($keys, true);
83 | }
84 |
85 | public function except(array $keys)
86 | {
87 | return $this->inputs->except($keys, true);
88 | }
89 |
90 | public function file($key)
91 | {
92 | $_file = $this->files[$key];
93 | return $this->hasFile($key)? $this->makeInputFile($_file) : NULL;
94 | }
95 |
96 | public function multiFiles($key)
97 | {
98 | if(!$this->hasMultiFiles($key)) return array();
99 |
100 | $input_files = array();
101 |
102 | $files = $this->files[$key];
103 |
104 | $names = $files["name"];
105 | $types = $files["type"];
106 | $temps = $files["tmp_name"];
107 | $errors = $files["error"];
108 | $sizes = $files["size"];
109 |
110 | foreach($temps as $i => $tmp) {
111 | if(empty($tmp) OR !is_uploaded_file($tmp)) continue;
112 |
113 | $_file = array(
114 | 'name' => $names[$i],
115 | 'type' => $types[$i],
116 | 'tmp_name' => $tmp,
117 | 'error' => $errors[$i],
118 | 'size' => $sizes[$i]
119 | );
120 |
121 | $input_files[] = $this->makeInputFile($_file);
122 | }
123 |
124 | return $input_files;
125 | }
126 |
127 | public function hasFile($key)
128 | {
129 | $file = $this->files[$key];
130 |
131 | if(!$file) return FALSE;
132 |
133 | $tmp = $file["tmp_name"];
134 |
135 | if(!is_string($tmp)) return FALSE;
136 |
137 | return is_uploaded_file($tmp);
138 | }
139 |
140 | public function hasMultiFiles($key)
141 | {
142 | $files = $this->files[$key];
143 |
144 | if(!$files) return FALSE;
145 |
146 | $uploaded_files = $files["tmp_name"];
147 | if(!is_array($uploaded_files)) return FALSE;
148 |
149 | foreach($uploaded_files as $tmp_file) {
150 | if(!empty($tmp_file) AND is_uploaded_file($tmp_file)) return TRUE;
151 | }
152 |
153 | return FALSE;
154 | }
155 |
156 | protected function makeInputFile(array $_file)
157 | {
158 | return new UploadedFile($_file);
159 | }
160 |
161 | public function defineRoute(Route $route)
162 | {
163 | if($this->route) return;
164 |
165 | $this->params = $route->params;
166 | $this->route = $route;
167 | }
168 |
169 | public function route()
170 | {
171 | return $this->route;
172 | }
173 |
174 | public function isMethod($method)
175 | {
176 | return strtoupper($this->method()) == strtoupper($method);
177 | }
178 |
179 | public function isMethodGet()
180 | {
181 | return $this->isMethod(static::METHOD_GET);
182 | }
183 |
184 | public function isMethodPost()
185 | {
186 | return $this->isMethod(static::METHOD_POST);
187 | }
188 |
189 | public function isMethodPut()
190 | {
191 | return $this->isMethod(static::METHOD_PUT);
192 | }
193 |
194 | public function isMethodPatch()
195 | {
196 | return $this->isMethod(static::METHOD_PATCH);
197 | }
198 |
199 | public function isMethodDelete()
200 | {
201 | return $this->isMethod(static::METHOD_DELETE);
202 | }
203 |
204 | public function method()
205 | {
206 | return $_SERVER['REQUEST_METHOD'];
207 | }
208 |
209 | public function isHttps()
210 | {
211 | if( isset($_SERVER['HTTPS'] ) ) {
212 | return true;
213 | } else {
214 | return false;
215 | }
216 | }
217 |
218 | public function isHttp()
219 | {
220 | return !$this->isHttps();
221 | }
222 |
223 | public function isAjax()
224 | {
225 | if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
226 | return true;
227 | } else {
228 | return false;
229 | }
230 | }
231 |
232 | public function isJson()
233 | {
234 | return $this->json() !== NULL;
235 | }
236 |
237 | public function json()
238 | {
239 | $raw_body = $this->body();
240 | $json = json_decode($raw_body, true);
241 |
242 | if(is_array($json)) {
243 | $data = new Bag($json);
244 | } else {
245 | $data = NULL;
246 | }
247 |
248 | return $data;
249 | }
250 |
251 | public function body()
252 | {
253 | return file_get_contents("php://input");
254 | }
255 |
256 | public function param($key, $default = null)
257 | {
258 | $params = $this->params;
259 | return (array_key_exists($key, $params))? $params[$key] : $default;
260 | }
261 |
262 | public function params()
263 | {
264 | return $this->params;
265 | }
266 |
267 | }
268 |
--------------------------------------------------------------------------------
/src/Http/Response.php:
--------------------------------------------------------------------------------
1 | '100 Continue',
21 | 101 => '101 Switching Protocols',
22 |
23 | //Successful 2xx
24 | 200 => '200 OK',
25 | 201 => '201 Created',
26 | 202 => '202 Accepted',
27 | 203 => '203 Non-Authoritative Information',
28 | 204 => '204 No Content',
29 | 205 => '205 Reset Content',
30 | 206 => '206 Partial Content',
31 |
32 | //Redirection 3xx
33 | 300 => '300 Multiple Choices',
34 | 301 => '301 Moved Permanently',
35 | 302 => '302 Found',
36 | 303 => '303 See Other',
37 | 304 => '304 Not Modified',
38 | 305 => '305 Use Proxy',
39 | 306 => '306 (Unused)',
40 | 307 => '307 Temporary Redirect',
41 |
42 | //Client Error 4xx
43 | 400 => '400 Bad Request',
44 | 401 => '401 Unauthorized',
45 | 402 => '402 Payment Required',
46 | 403 => '403 Forbidden',
47 | 404 => '404 Not Found',
48 | 405 => '405 Method Not Allowed',
49 | 406 => '406 Not Acceptable',
50 | 407 => '407 Proxy Authentication Required',
51 | 408 => '408 Request Timeout',
52 | 409 => '409 Conflict',
53 | 410 => '410 Gone',
54 | 411 => '411 Length Required',
55 | 412 => '412 Precondition Failed',
56 | 413 => '413 Request Entity Too Large',
57 | 414 => '414 Request-URI Too Long',
58 | 415 => '415 Unsupported Media Type',
59 | 416 => '416 Requested Range Not Satisfiable',
60 | 417 => '417 Expectation Failed',
61 | 418 => '418 I\'m a teapot',
62 | 422 => '422 Unprocessable Entity',
63 | 423 => '423 Locked',
64 |
65 | //Server Error 5xx
66 | 500 => '500 Internal Server Error',
67 | 501 => '501 Not Implemented',
68 | 502 => '502 Bad Gateway',
69 | 503 => '503 Service Unavailable',
70 | 504 => '504 Gateway Timeout',
71 | 505 => '505 HTTP Version Not Supported'
72 | );
73 |
74 | public $body = "";
75 |
76 | public $dump_output = "";
77 |
78 | protected $has_sent = false;
79 |
80 | public function __construct(App $app)
81 | {
82 | $this->app = $app;
83 | $this->headers = new ResponseHeaderBag;
84 | $this->reset();
85 | }
86 |
87 | /**
88 | * set http status code
89 | *
90 | * @param int $status http status to set
91 | */
92 | public function setStatus($status)
93 | {
94 | $status = (int) $status;
95 | if(!array_key_exists($status, $this->http_status_messages)) return $this;
96 |
97 | $this->status = $status;
98 | return $this;
99 | }
100 |
101 | /**
102 | * get response status code
103 | *
104 | * @return int
105 | */
106 | public function getStatus()
107 | {
108 | return (int) $this->status;
109 | }
110 |
111 | /**
112 | * Check response status code
113 | *
114 | * @param string|int status code, can be 4xx, 2xx, 40x, 20x, etc
115 | * @return boolean
116 | */
117 | public function isStatus($status_code)
118 | {
119 | $status = (string) $this->getStatus();
120 | $regex = '/'.str_replace('x', '[0-9]', $status_code).'/'; // it should be /4[0-9][0-9]/
121 |
122 | return (preg_match($regex, $status) != 0);
123 | }
124 |
125 | /**
126 | * set response content type
127 | *
128 | * @param string $type response content type
129 | */
130 | public function setContentType($type)
131 | {
132 | $this->headers["CONTENT_TYPE"] = $type;
133 | return $this;
134 | }
135 |
136 | /**
137 | * get setted response content type
138 | *
139 | * @return string response content type
140 | */
141 | public function getContentType()
142 | {
143 | return $this->headers["CONTENT_TYPE"];
144 | }
145 |
146 | public function json(array $data, $status = null, $content_type = null)
147 | {
148 | $json = json_encode($data);
149 | $this->setContentType($content_type ? $content_type : static::CONTENT_TYPE_JSON);
150 | $this->setStatus($status);
151 | $this->body = $json;
152 |
153 | return $this;
154 | }
155 |
156 | public function html($content, $status = null, $content_type = null)
157 | {
158 | $this->setContentType($content_type ? $content_type : static::CONTENT_TYPE_HTML);
159 | $this->setStatus($status);
160 | $this->body = $content;
161 |
162 | return $this;
163 | }
164 |
165 | public function isJson()
166 | {
167 | return ($this->getContentType() == static::CONTENT_TYPE_JSON);
168 | }
169 |
170 | public function isHtml()
171 | {
172 | return ($this->getContentType() == static::CONTENT_TYPE_HTML);
173 | }
174 |
175 | public function reset()
176 | {
177 | $this->has_sent = false;
178 |
179 | return $this
180 | ->setStatus(200)
181 | ->clean();
182 | }
183 |
184 | public function clean()
185 | {
186 | $this->body = "";
187 | $this->dump_output = "";
188 | return $this;
189 | }
190 |
191 | public function send($output = null, $status = null)
192 | {
193 | if($this->has_sent) return;
194 |
195 | if($output) {
196 | $this->body .= $output;
197 | }
198 |
199 | if($status) {
200 | $this->setStatus($status);
201 | }
202 |
203 | $status_str = (string) $this->status;
204 |
205 | $this->app->hook->apply($status_str[0].'xx', [$this, $this->app]);
206 | $this->app->hook->apply($status_str[0].$status_str[1].'x', [$this, $this->app]);
207 | $this->app->hook->apply($this->status, [$this, $this->app]);
208 | $this->app->hook->apply("response.before_send", [$this, $this->app]);
209 |
210 | if(!headers_sent()) $this->writeHeaders();
211 |
212 | if('HEAD' != $this->app->request->server['REQUEST_METHOD']) {
213 | echo $this->body;
214 | }
215 |
216 | $this->has_sent = true;
217 | $this->app->hook->apply("response.after_send", [$this, $this->app]);
218 | }
219 |
220 | public function getStatusMessage($status_code)
221 | {
222 | if(array_key_exists($status_code, $this->http_status_messages)) {
223 | return $this->http_status_messages[$status_code];
224 | } else {
225 | return null;
226 | }
227 | }
228 |
229 | protected function writeHeaders()
230 | {
231 | $headers = $this->headers->all(false);
232 | $content_type = ($type = $this->getContentType()) ? $type : static::CONTENT_TYPE_HTML;
233 |
234 | // http://stackoverflow.com/questions/6163970/set-response-status-code
235 | header("HTTP/1.1 ".$this->getStatusMessage($this->status), true, $this->status);
236 | header('Content-type: '.$content_type);
237 | foreach($headers as $key => $value) {
238 | $header = $this->normalizeHeaderKey($key).': '.$value;
239 | header($header);
240 | }
241 | }
242 |
243 | // http://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields
244 | protected function normalizeHeaderKey($key)
245 | {
246 | return $this->headers->unresolveKey($key);
247 | }
248 |
249 |
250 | }
--------------------------------------------------------------------------------
/src/Http/ResponseHeaderBag.php:
--------------------------------------------------------------------------------
1 | resolveKey($key);
17 |
18 | $not_ucwords = array(
19 | 'P3P' => 'P3P',
20 | 'X XSS PROTECTION' => 'X-XSS-Protection',
21 | 'X UA COMPATIBLE', 'X-UA-Compatible',
22 | 'X WEBKIT CSP' => 'X-WebKit-CSP',
23 | 'WWW AUTHENTICATE' => 'WWW-Authenticate'
24 | );
25 |
26 | if(array_key_exists($key, $not_ucwords)) {
27 | return $not_ucwords[$key];
28 | } else {
29 | $key = ucwords($key);
30 | $key = str_replace(' ', '-', $key);
31 |
32 | return $key;
33 | }
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/src/Http/UploadedFile.php:
--------------------------------------------------------------------------------
1 | tmp = $_file['tmp_name'];
30 | $this->size = $_file['size'];
31 | $this->error = $_file['error'];
32 | $this->mimeType = $_file['type'];
33 | $this->location = $this->tmp;
34 | $this->originalName = $_file['name'];
35 | $this->name = pathinfo($_file['name'], PATHINFO_FILENAME);
36 | $this->extension = pathinfo($_file['name'], PATHINFO_EXTENSION);
37 | }
38 |
39 | public function getClientOriginalName()
40 | {
41 | return $this->originalName;
42 | }
43 |
44 | public function getFilename()
45 | {
46 | $ext = empty($this->extension)? "" : ".".$this->extension;
47 | return $this->name.$ext;
48 | }
49 |
50 | public function getTemporaryFile()
51 | {
52 | return $this->tmp;
53 | }
54 |
55 | public function getMimeType()
56 | {
57 | return $this->mimeType;
58 | }
59 |
60 | public function getLocation()
61 | {
62 | return $this->location;
63 | }
64 |
65 | public function move($location, $filename = null)
66 | {
67 | if ($filename) {
68 | $pathinfo = pathinfo($filename);
69 | $this->extension = $pathinfo['extension'];
70 | $this->name = $pathinfo['filename'];
71 | }
72 |
73 | if(!is_uploaded_file($this->tmp)) return FALSE;
74 |
75 | $location = rtrim($location, "/");
76 |
77 | if(!is_dir($location)) {
78 | throw new \RuntimeException("Upload directory '{$location}' not found", 1);
79 | } else if(!is_writable($location)) {
80 | throw new \RuntimeException("Upload directory '{$location}' is not writable", 2);
81 | }
82 |
83 | $filepath = $location."/".$this->getFilename();
84 |
85 | move_uploaded_file($this->tmp, $filepath);
86 |
87 | $has_moved = (false == is_uploaded_file($this->tmp));
88 |
89 | if($has_moved) {
90 | $this->location = $filepath;
91 | } else {
92 | throw new \RuntimeException("
93 | Upload file failed because unexpected reason.
94 | Maybe there is miss configuration in your php.ini settings"
95 | , 3);
96 | }
97 | }
98 |
99 | public function __toString()
100 | {
101 | return (string) file_get_contents($this->getLocation());
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/src/MacroableTrait.php:
--------------------------------------------------------------------------------
1 | bindTo($this, get_class($this)), $parameters);
82 | }
83 | else
84 | {
85 | return call_user_func_array(static::$macros[$method], $parameters);
86 | }
87 | }
88 |
89 | throw new BadMethodCallException("Method {$method} does not exist.");
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/src/Provider.php:
--------------------------------------------------------------------------------
1 | app = $app;
10 | }
11 |
12 | abstract public function register();
13 |
14 | abstract public function boot();
15 |
16 | }
--------------------------------------------------------------------------------
/src/Router/Route.php:
--------------------------------------------------------------------------------
1 | allowed_method = $allowed_method;
50 | $this->path = $this->resolvePath($path);
51 | $this->action = $action;
52 | $this->middlewares = $middlewares;
53 | $this->conditions = $conditions;
54 | }
55 |
56 | /**
57 | * Get route name
58 | *
59 | * @return string
60 | */
61 | public function getName()
62 | {
63 | return $this->name;
64 | }
65 |
66 | /**
67 | * Get route path
68 | *
69 | * @return string
70 | */
71 | public function getPath()
72 | {
73 | return $this->path;
74 | }
75 |
76 | /**
77 | * Get route allowed methods
78 | *
79 | * @return array
80 | */
81 | public function getMethod()
82 | {
83 | return $this->allowed_method;
84 | }
85 |
86 | /**
87 | * Get route parameter conditions
88 | *
89 | * @return array
90 | */
91 | public function getConditions()
92 | {
93 | return $this->conditions;
94 | }
95 |
96 | /**
97 | * Get registered middlewares
98 | *
99 | * @return array
100 | */
101 | public function getMiddlewares()
102 | {
103 | return $this->middlewares;
104 | }
105 |
106 | /**
107 | * Get route action controller
108 | *
109 | * @return mixed action
110 | */
111 | public function getAction()
112 | {
113 | return $this->action;
114 | }
115 |
116 | /**
117 | * Set route name
118 | *
119 | * @param string $name
120 | * @return self
121 | */
122 | public function name($name)
123 | {
124 | $this->name = $name;
125 |
126 | return $this;
127 | }
128 |
129 | /**
130 | * Set parameter condition(regex)
131 | *
132 | * @param string $param
133 | * @param string $regex
134 | * @return self
135 | */
136 | public function where($param, $regex)
137 | {
138 | $this->conditions[$param] = $regex;
139 | return $this;
140 | }
141 |
142 | /**
143 | * Set route middleware
144 | *
145 | * @param string|array $middlewares
146 | * @return void
147 | */
148 | public function middleware($middlewares)
149 | {
150 | $this->middlewares = array_merge($this->middlewares, (array) $middlewares);
151 | return $this;
152 | }
153 |
154 | /**
155 | * Check if route is GET
156 | *
157 | * @return bool
158 | */
159 | public function isGet()
160 | {
161 | return $this->isAllowing("GET");
162 | }
163 |
164 | /**
165 | * Check if route is POST
166 | *
167 | * @return bool
168 | */
169 | public function isPost()
170 | {
171 | return $this->isAllowing("POST");
172 | }
173 |
174 | /**
175 | * Check if route is PUT
176 | *
177 | * @return bool
178 | */
179 | public function isPut()
180 | {
181 | return $this->isAllowing("PUT");
182 | }
183 |
184 | /**
185 | * Check if route is PATCH
186 | *
187 | * @return bool
188 | */
189 | public function isPatch()
190 | {
191 | return $this->isAllowing("PATCH");
192 | }
193 |
194 | /**
195 | * Check if route is DELETE
196 | *
197 | * @return bool
198 | */
199 | public function isDelete()
200 | {
201 | return $this->isAllowing("DELETE");
202 | }
203 |
204 | /**
205 | * Check if route allowing given method
206 | *
207 | * @return bool
208 | */
209 | public function isAllowing($method)
210 | {
211 | return $this->resolveMethodName($method) == $this->getMethod();
212 | }
213 |
214 | /**
215 | * Resolve method name
216 | *
217 | * @return string
218 | */
219 | protected function resolveMethodName($method)
220 | {
221 | return strtoupper($method);
222 | }
223 |
224 | /**
225 | * Resolve path
226 | *
227 | * @return string
228 | */
229 | protected function resolvePath($path)
230 | {
231 | return '/'.trim($path, '/');
232 | }
233 |
234 | public function __call($method, $params)
235 | {
236 | $params = implode(',', $params);
237 | $middleware = $method.':'.$params;
238 | return $this->middleware($middleware);
239 | }
240 |
241 | }
242 |
--------------------------------------------------------------------------------
/src/Router/RouteGroup.php:
--------------------------------------------------------------------------------
1 | path = $this->resolvePath($path);
25 | $this->grouper = $grouper;
26 | $this->middlewares = $middlewares;
27 | $this->conditions = $conditions;
28 | }
29 |
30 | /**
31 | * Get path
32 | *
33 | * @return string $path
34 | */
35 | public function getPath()
36 | {
37 | return $this->path;
38 | }
39 |
40 | /**
41 | * Get route collections
42 | *
43 | * @return array of Rakit\Framework\Router\Route
44 | */
45 | public function getRoutes()
46 | {
47 | // reset routes and groups
48 | $this->routes = array();
49 | $this->groups = array();
50 | // run grouper
51 | call_user_func($this->grouper, $this);
52 |
53 | $routes = $this->routes;
54 | $groups = $this->groups;
55 | foreach($groups as $group) {
56 | $routes = array_merge($routes, $group->getRoutes());
57 | }
58 |
59 | return $routes;
60 | }
61 |
62 | /**
63 | * Register routes by many methods
64 | *
65 | * @param array $methods
66 | * @param string $path
67 | * @return Route[]
68 | */
69 | public function map(array $methods, $path, $handler)
70 | {
71 | return $this->group($path, function($group) use ($methods, $handler) {
72 | foreach($methods as $method) {
73 | $group->add($method, '/', $handler);
74 | }
75 | });
76 | }
77 |
78 | /**
79 | * Register a Route
80 | *
81 | * @param string $method
82 | * @param string $path
83 | * @return Route
84 | */
85 | public function add($method, $path, $handler)
86 | {
87 | $middlewares = $this->getMiddlewares();
88 | $conditions = $this->getConditions();
89 | $path = $this->getPath().$path;
90 |
91 | $route = new Route($method, $path, $handler, $middlewares, $conditions);
92 | $this->routes[] = $route;
93 |
94 | return $route;
95 | }
96 |
97 | /**
98 | * Register GET route
99 | *
100 | * @param string $path
101 | * @return Route
102 | */
103 | public function get($path, $handler)
104 | {
105 | return $this->add('GET', $path, $handler);
106 | }
107 |
108 | /**
109 | * Register POST route
110 | *
111 | * @param string $path
112 | * @return Route
113 | */
114 | public function post($path, $handler)
115 | {
116 | return $this->add('POST', $path, $handler);
117 | }
118 |
119 | /**
120 | * Register PUT route
121 | *
122 | * @param string $path
123 | * @return Route
124 | */
125 | public function put($path, $handler)
126 | {
127 | return $this->add('PUT', $path, $handler);
128 | }
129 |
130 | /**
131 | * Register PATCH route
132 | *
133 | * @param string $path
134 | * @return Route
135 | */
136 | public function patch($path, $handler)
137 | {
138 | return $this->add('PATCH', $path, $handler);
139 | }
140 |
141 | /**
142 | * Register DELETE route
143 | *
144 | * @param string $path
145 | * @return Route
146 | */
147 | public function delete($path, $handler)
148 | {
149 | return $this->add('DELETE', $path, $handler);
150 | }
151 |
152 | /**
153 | * Grouping routes with Closure
154 | *
155 | * @param string $path
156 | * @param Closure $grouper
157 | * @return RouteMap
158 | */
159 | public function group($path, Closure $grouper)
160 | {
161 | $path = $this->getPath().$path;
162 | $middlewares = $this->getMiddlewares();
163 | $conditions = $this->getConditions();
164 | $group = new RouteGroup($path, $grouper, $middlewares, $conditions);
165 |
166 | $this->groups[] = $group;
167 |
168 | return $group;
169 | }
170 |
171 | /**
172 | * Set parameter condition
173 | *
174 | * @param string $param
175 | * @param string $condition
176 | * @return void
177 | */
178 | public function where($param, $condition)
179 | {
180 | $this->conditions[$param] = $condition;
181 | return $this;
182 | }
183 |
184 | /**
185 | * Append middlewares
186 | *
187 | * @param mixed $middlewares
188 | * @return void
189 | */
190 | public function middleware($middlewares)
191 | {
192 | $this->middlewares = array_merge($this->middlewares, (array) $middlewares);
193 | return $this;
194 | }
195 |
196 | /**
197 | * Get parameter conditions
198 | *
199 | * @return array
200 | */
201 | public function getConditions()
202 | {
203 | return $this->conditions;
204 | }
205 |
206 | /**
207 | * Get middlewares
208 | *
209 | * @return array
210 | */
211 | public function getMiddlewares()
212 | {
213 | return $this->middlewares;
214 | }
215 |
216 | /**
217 | * Resolve path
218 | *
219 | * @return string
220 | */
221 | protected function resolvePath($path)
222 | {
223 | return '/'.trim($path, '/');
224 | }
225 |
226 | public function __call($method, $params)
227 | {
228 | $params = implode(',', $params);
229 | $middleware = $method.':'.$params;
230 | return $this->middleware($middleware);
231 | }
232 |
233 | }
234 |
--------------------------------------------------------------------------------
/src/Router/Router.php:
--------------------------------------------------------------------------------
1 | routes;
64 | foreach($this->groups as $group) {
65 | $routes = array_merge($routes, $group->getRoutes());
66 | }
67 |
68 | return $routes;
69 | }
70 |
71 | /**
72 | * Register a Route
73 | *
74 | * @param string $method
75 | * @param string $path
76 | * @return Route
77 | */
78 | public function add($method, $path, $handler)
79 | {
80 | $route = new Route($method, $path, $handler);
81 | $this->routes[] = $route;
82 |
83 | return $route;
84 | }
85 |
86 | /**
87 | * Register GET Route
88 | *
89 | * @param string $path
90 | * @param Closure|string $handler
91 | * @return Route
92 | */
93 | public function get($path, $handler)
94 | {
95 | return $this->add('GET', $path, $handler);
96 | }
97 |
98 | /**
99 | * Register POST Route
100 | *
101 | * @param string $path
102 | * @param Closure|string $handler
103 | * @return Route
104 | */
105 | public function post($path, $handler)
106 | {
107 | return $this->add('POST', $path, $handler);
108 | }
109 |
110 | /**
111 | * Register PUT Route
112 | *
113 | * @param string $path
114 | * @param Closure|string $handler
115 | * @return Route
116 | */
117 | public function put($path, $handler)
118 | {
119 | return $this->add('PUT', $path, $handler);
120 | }
121 |
122 | /**
123 | * Register PATCH Route
124 | *
125 | * @param string $path
126 | * @param Closure|string $handler
127 | * @return Route
128 | */
129 | public function patch($path, $handler)
130 | {
131 | return $this->add('PATCH', $path, $handler);
132 | }
133 |
134 | /**
135 | * Register DELETE Route
136 | *
137 | * @param string $path
138 | * @param Closure|string $handler
139 | * @return Route
140 | */
141 | public function delete($path, $handler)
142 | {
143 | return $this->add('DELETE', $path, $handler);
144 | }
145 |
146 | /**
147 | * Grouping routes with Closure
148 | *
149 | * @param string $path
150 | * @param Closure $grouper
151 | * @return RouteGroup
152 | */
153 | public function group($path, Closure $grouper)
154 | {
155 | $group = new RouteGroup($path, $grouper);
156 | $this->groups[] = $group;
157 | return $group;
158 | }
159 |
160 | /**
161 | * Grouping some routes in different methods
162 | *
163 | * @param string $path
164 | * @param Closure|string $handler
165 | * @return RouteGroup
166 | */
167 | public function map(array $methods, $path, $handler)
168 | {
169 | $group = new RouteGroup($path, function($group) use ($methods, $handler) {
170 | foreach($methods as $method) {
171 | $group->add($method, '/', $handler);
172 | }
173 | });
174 |
175 | $this->groups[] = $group;
176 | return $group;
177 | }
178 |
179 | /**
180 | * Register GET, POST, PUT, PATCH & DELETE routes with same path & handler
181 | *
182 | * @param string $path
183 | * @param Closure|string $handler
184 | * @return RouteGroup
185 | */
186 | public function any($path, $handler)
187 | {
188 | return $this->map(['GET','POST','PUT','PATCH','DELETE'], $path, $handler);
189 | }
190 |
191 | /**
192 | * Enable cache routes by giving cache file name
193 | *
194 | * @param string|callable $cache_file
195 | * @return null
196 | */
197 | public function cache($cache_file)
198 | {
199 | if (!is_string($cache_file) AND !is_callable($cache_file)) {
200 | throw new \InvalidArgumentException("Cache name must be string or callable", 1);
201 | }
202 |
203 | $this->cache_file = $cache_file;
204 | }
205 |
206 | /**
207 | * Find route by given path and method
208 | *
209 | * @param string $path
210 | * @param string $method
211 | * @return null|Route
212 | */
213 | public function dispatch($method, $path)
214 | {
215 | $pattern = $this->makePattern($method, $path);
216 | $all_routes = $this->getRoutes();
217 |
218 | $cache_file = $this->cache_file;
219 | $chunk_routes = array_chunk($all_routes, $this->max_routes_in_regex);
220 |
221 | if ($cache_file AND is_callable($cache_file)) {
222 | $cache_file = call_user_func_array($cache_file, [$all_routes, $this]);
223 | }
224 |
225 | if ($cache_file AND file_exists($cache_file) AND $cached_routes = $this->getCachedRoutes($cache_file)) {
226 | $route_regexes = $cached_routes;
227 | } else {
228 | $route_regexes = [];
229 |
230 | foreach($chunk_routes as $i => $routes) {
231 | $regex = [];
232 | foreach($routes as $i => $route) {
233 | $regex[] = $this->toRegex($route, $i);
234 | }
235 | $regex = "~^(?|".implode("|", $regex).")$~x";
236 | $route_regexes[] = $regex;
237 | }
238 |
239 | if ($cache_file) {
240 | $this->cacheRoutes($cache_file, $route_regexes);
241 | }
242 | }
243 |
244 | foreach($route_regexes as $i => $regex) {
245 | $routes = $chunk_routes[$i];
246 |
247 | if(!preg_match($regex, $pattern, $matches)) {
248 | continue;
249 | }
250 |
251 | $index = (count($matches) - 1 - $this->max_params);
252 |
253 | $matched_route = $routes[$index];
254 | $matched_route->params = [];
255 | $path = $matched_route->getPath();
256 |
257 | $params = $this->getDeclaredPathParams($matched_route);
258 | foreach($params as $i => $param) {
259 | // find param index in $matches
260 | // the problem is if using optional parameter like:
261 | // /foo/:bar(/:baz)
262 | // the regex should look like this:
263 | // /foo/()(/())?
264 | // so the index of :baz is not $i,
265 | // so the trick is to add $i with count char '(' before that :param in route path
266 | // for example if route path like this:
267 | // /foo/:bar(/:baz(/:qux)), the regex: /foo/()(/()(/())?)?
268 | // so index for :qux is 3+2, where 2 is count '(' before :qux
269 | $pos = strpos($path, ':'.$param);
270 | $count_open_bracket = substr_count($path, '(', 0, $pos);
271 | $value = $matches[$i+1+$count_open_bracket];
272 |
273 | if($value) {
274 | $matched_route->params[$param] = $value;
275 | }
276 | }
277 |
278 | return $matched_route;
279 | }
280 |
281 | return null;
282 | }
283 |
284 | /**
285 | * Alias for dispatch
286 | */
287 | public function findMatch($path, $method)
288 | {
289 | return $this->dispatch($method, $path);
290 | }
291 |
292 | /**
293 | * Get route by given name
294 | *
295 | * @param string $route_name
296 | * @return null|Route
297 | */
298 | public function findRouteByName($name)
299 | {
300 | $routes = $this->getRoutes();
301 | foreach($routes as $route) {
302 | if($route->getName() == $name) return $route;
303 | }
304 | return null;
305 | }
306 |
307 | /**
308 | * Get cache route regex from cache file
309 | *
310 | * @param string $cache_file
311 | * @return array
312 | */
313 | protected function getCachedRoutes($cache_file)
314 | {
315 | $content = file_get_contents($cache_file);
316 | return unserialize($content);
317 | }
318 |
319 | /**
320 | * Save routes to cache file
321 | *
322 | * @param string $cache_file
323 | * @return array
324 | */
325 | protected function cacheRoutes($cache_file, $route_regexes)
326 | {
327 | $content = serialize($route_regexes);
328 | return file_put_contents($cache_file, $content);
329 | }
330 |
331 | /**
332 | * Tranfrom route path into Regex
333 | *
334 | * @param Route $route
335 | * @return string regex
336 | */
337 | protected function toRegex(Route $route, $index)
338 | {
339 | $method = $route->getMethod();
340 | $path = $route->getPath();
341 | $conditions = $route->getConditions();
342 | $params = $this->getDeclaredPathParams($route);
343 |
344 | $regex = $this->makePattern($method, $path);
345 |
346 | // transform /foo/:bar(/:baz) => /foo/:bar(/:baz)?
347 | $regex = str_replace(')', ')?', $regex);
348 |
349 | foreach($params as $param) {
350 | if (array_key_exists($param, $conditions)) {
351 | $regex = str_replace(':'.$param, '('.$conditions[$param].')', $regex);
352 | } else {
353 | $regex = str_replace(':'.$param, '('.$this->default_param_regex.')', $regex);
354 | }
355 | }
356 |
357 | $count_brackets = substr_count($regex, "(");
358 | $count_added_brackets = $this->max_params + ($index - $count_brackets);
359 |
360 | $regex .= str_repeat("()", $count_added_brackets);
361 |
362 | return $regex;
363 | }
364 |
365 | /**
366 | * Getting declared parameters by given route object
367 | *
368 | * @param Route $route
369 | * @return array
370 | */
371 | protected function getDeclaredPathParams(Route $route)
372 | {
373 | $path = $route->getPath();
374 | preg_match_all('/\:([a-z_][a-z0-9_]+)/i', $path, $match);
375 | return $match[1];
376 | }
377 |
378 | public function makePattern($method, $path)
379 | {
380 | return $method.$path;
381 | }
382 |
383 | }
384 |
--------------------------------------------------------------------------------
/src/Util/Arr.php:
--------------------------------------------------------------------------------
1 | $value)
44 | {
45 | list($innerKey, $innerValue) = call_user_func($callback, $key, $value);
46 |
47 | $results[$innerKey] = $innerValue;
48 | }
49 |
50 | return $results;
51 | }
52 |
53 | /**
54 | * Divide an array into two arrays. One with keys and the other with values.
55 | *
56 | * @param array $array
57 | * @return array
58 | */
59 | public static function divide($array)
60 | {
61 | return [array_keys($array), array_values($array)];
62 | }
63 |
64 | /**
65 | * Flatten a multi-dimensional associative array with dots.
66 | *
67 | * @param array $array
68 | * @param string $prepend
69 | * @return array
70 | */
71 | public static function dot($array, $prepend = '')
72 | {
73 | $results = [];
74 |
75 | foreach ($array as $key => $value)
76 | {
77 | if (is_array($value))
78 | {
79 | $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
80 | }
81 | else
82 | {
83 | $results[$prepend.$key] = $value;
84 | }
85 | }
86 |
87 | return $results;
88 | }
89 |
90 | /**
91 | * Get all of the given array except for a specified array of items.
92 | *
93 | * @param array $array
94 | * @param array|string $keys
95 | * @return array
96 | */
97 | public static function except($array, $keys)
98 | {
99 | return array_diff_key($array, array_flip((array) $keys));
100 | }
101 |
102 | /**
103 | * Fetch a flattened array of a nested array element.
104 | *
105 | * @param array $array
106 | * @param string $key
107 | * @return array
108 | */
109 | public static function fetch($array, $key)
110 | {
111 | foreach (explode('.', $key) as $segment)
112 | {
113 | $results = [];
114 |
115 | foreach ($array as $value)
116 | {
117 | if (array_key_exists($segment, $value = (array) $value))
118 | {
119 | $results[] = $value[$segment];
120 | }
121 | }
122 |
123 | $array = array_values($results);
124 | }
125 |
126 | return array_values($results);
127 | }
128 |
129 | /**
130 | * Return the first element in an array passing a given truth test.
131 | *
132 | * @param array $array
133 | * @param callable $callback
134 | * @param mixed $default
135 | * @return mixed
136 | */
137 | public static function first($array, callable $callback, $default = null)
138 | {
139 | foreach ($array as $key => $value)
140 | {
141 | if (call_user_func($callback, $key, $value)) return $value;
142 | }
143 |
144 | return $default;
145 | }
146 |
147 | /**
148 | * Return the last element in an array passing a given truth test.
149 | *
150 | * @param array $array
151 | * @param callable $callback
152 | * @param mixed $default
153 | * @return mixed
154 | */
155 | public static function last($array, callable $callback, $default = null)
156 | {
157 | return static::first(array_reverse($array), $callback, $default);
158 | }
159 |
160 | /**
161 | * Flatten a multi-dimensional array into a single level.
162 | *
163 | * @param array $array
164 | * @return array
165 | */
166 | public static function flatten($array)
167 | {
168 | $return = [];
169 |
170 | array_walk_recursive($array, function($x) use (&$return) { $return[] = $x; });
171 |
172 | return $return;
173 | }
174 |
175 | /**
176 | * Remove one or many array items from a given array using "dot" notation.
177 | *
178 | * @param array $array
179 | * @param array|string $keys
180 | * @return void
181 | */
182 | public static function forget(&$array, $keys)
183 | {
184 | $original =& $array;
185 |
186 | foreach ((array) $keys as $key)
187 | {
188 | $parts = explode('.', $key);
189 |
190 | while (count($parts) > 1)
191 | {
192 | $part = array_shift($parts);
193 |
194 | if (isset($array[$part]) && is_array($array[$part]))
195 | {
196 | $array =& $array[$part];
197 | }
198 | }
199 |
200 | unset($array[array_shift($parts)]);
201 |
202 | // clean up after each pass
203 | $array =& $original;
204 | }
205 | }
206 |
207 | /**
208 | * Get an item from an array using "dot" notation.
209 | *
210 | * @param array $array
211 | * @param string $key
212 | * @param mixed $default
213 | * @return mixed
214 | */
215 | public static function get($array, $key, $default = null)
216 | {
217 | if (is_null($key)) return $array;
218 |
219 | if (isset($array[$key])) return $array[$key];
220 |
221 | foreach (explode('.', $key) as $segment)
222 | {
223 | if ( ! is_array($array) || ! array_key_exists($segment, $array))
224 | {
225 | return $default;
226 | }
227 |
228 | $array = $array[$segment];
229 | }
230 |
231 | return $array;
232 | }
233 |
234 | /**
235 | * Check if an item exists in an array using "dot" notation.
236 | *
237 | * @param array $array
238 | * @param string $key
239 | * @return bool
240 | */
241 | public static function has($array, $key)
242 | {
243 | if (empty($array) || is_null($key)) return false;
244 |
245 | if (array_key_exists($key, $array)) return true;
246 |
247 | foreach (explode('.', $key) as $segment)
248 | {
249 | if ( ! is_array($array) || ! array_key_exists($segment, $array))
250 | {
251 | return false;
252 | }
253 |
254 | $array = $array[$segment];
255 | }
256 |
257 | return true;
258 | }
259 |
260 | /**
261 | * Get a subset of the items from the given array.
262 | *
263 | * @param array $array
264 | * @param array|string $keys
265 | * @return array
266 | */
267 | public static function only($array, $keys)
268 | {
269 | return array_intersect_key($array, array_flip((array) $keys));
270 | }
271 |
272 | /**
273 | * Pluck an array of values from an array.
274 | *
275 | * @param array $array
276 | * @param string $value
277 | * @param string $key
278 | * @return array
279 | */
280 | public static function pluck($array, $value, $key = null)
281 | {
282 | $results = [];
283 |
284 | foreach ($array as $item)
285 | {
286 | $itemValue = data_get($item, $value);
287 |
288 | // If the key is "null", we will just append the value to the array and keep
289 | // looping. Otherwise we will key the array using the value of the key we
290 | // received from the developer. Then we'll return the final array form.
291 | if (is_null($key))
292 | {
293 | $results[] = $itemValue;
294 | }
295 | else
296 | {
297 | $itemKey = data_get($item, $key);
298 |
299 | $results[$itemKey] = $itemValue;
300 | }
301 | }
302 |
303 | return $results;
304 | }
305 |
306 | /**
307 | * Get a value from the array, and remove it.
308 | *
309 | * @param array $array
310 | * @param string $key
311 | * @param mixed $default
312 | * @return mixed
313 | */
314 | public static function pull(&$array, $key, $default = null)
315 | {
316 | $value = static::get($array, $key, $default);
317 |
318 | static::forget($array, $key);
319 |
320 | return $value;
321 | }
322 |
323 | /**
324 | * Set an array item to a given value using "dot" notation.
325 | *
326 | * If no key is given to the method, the entire array will be replaced.
327 | *
328 | * @param array $array
329 | * @param string $key
330 | * @param mixed $value
331 | * @return array
332 | */
333 | public static function set(&$array, $key, $value)
334 | {
335 | if (is_null($key)) return $array = $value;
336 |
337 | $keys = explode('.', $key);
338 |
339 | while (count($keys) > 1)
340 | {
341 | $key = array_shift($keys);
342 |
343 | // If the key doesn't exist at this depth, we will just create an empty array
344 | // to hold the next value, allowing us to create the arrays to hold final
345 | // values at the correct depth. Then we'll keep digging into the array.
346 | if ( ! isset($array[$key]) || ! is_array($array[$key]))
347 | {
348 | $array[$key] = [];
349 | }
350 |
351 | $array =& $array[$key];
352 | }
353 |
354 | $array[array_shift($keys)] = $value;
355 |
356 | return $array;
357 | }
358 |
359 | /**
360 | * Filter the array using the given callback.
361 | *
362 | * @param array $array
363 | * @param callable $callback
364 | * @return array
365 | */
366 | public static function where($array, callable $callback)
367 | {
368 | $filtered = [];
369 |
370 | foreach ($array as $key => $value)
371 | {
372 | if (call_user_func($callback, $key, $value)) $filtered[$key] = $value;
373 | }
374 |
375 | return $filtered;
376 | }
377 |
378 | public static function remove(array &$array, $key)
379 | {
380 | $keys = explode('.', $key);
381 |
382 | while(count($keys) > 1) {
383 | $key = array_shift($keys);
384 |
385 | if(!isset($array[$key]) OR !is_array($array[$key])) {
386 | $array[$key] = array();
387 | }
388 |
389 | $array =& $array[$key];
390 | }
391 |
392 | unset($array[array_shift($keys)]);
393 | }
394 |
395 | }
396 |
--------------------------------------------------------------------------------
/src/Util/Str.php:
--------------------------------------------------------------------------------
1 | view_path = $view_path;
14 | }
15 |
16 | public function render($file, array $data = array())
17 | {
18 | $view_file = $this->view_path.'/'.$file;
19 |
20 | if(!file_exists($view_file)) {
21 | throw new RuntimeException("Cannot render view '{$view_file}', file not found", 1);
22 | }
23 |
24 | $render = function($__file, array $__data) {
25 | extract($__data);
26 |
27 | ob_start();
28 | include($__file);
29 | return ob_get_clean();
30 | };
31 |
32 | return $render($view_file, $data);
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/src/View/View.php:
--------------------------------------------------------------------------------
1 | app = $app;
18 | $this->setEngine($engine);
19 | }
20 |
21 | public function setEngine(ViewEngineInterface $engine)
22 | {
23 | $this->engine = $engine;
24 | }
25 |
26 | public function set($key, $value)
27 | {
28 | $this->data[$key] = $value;
29 | }
30 |
31 | public function get($key, $default = null)
32 | {
33 | return isset($this->data[$key])? $this->data[$key] : $default;
34 | }
35 |
36 | public function render($file, array $data = array())
37 | {
38 | $data = array_merge($this->data, $data);
39 | $data['app'] = $this->app;
40 |
41 | return $this->engine->render($file, $data);
42 | }
43 |
44 | public function __call($method, $params)
45 | {
46 | return call_user_func_array([$this->engine, $method], $params);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/View/ViewEngineInterface.php:
--------------------------------------------------------------------------------
1 | app;
15 | $app['view:Rakit\Framework\View\View'] = $app->container->singleton(function($container) use ($app) {
16 | $view_path = $app->config->get('view.path');
17 | $engine = $app->config->get('view.engine');
18 |
19 | if(!$engine) {
20 | $engine = new BasicViewEngine($view_path);
21 | } elseif(is_string($engine)) {
22 | $engine = $container->make($engine, [$view_path]);
23 | }
24 |
25 | return new View($container['app'], $engine);
26 | });
27 | }
28 |
29 | public function boot()
30 | {
31 | $app = $this->app;
32 | $app->response->macro('view', function($file, array $data = array()) use ($app) {
33 | $rendered = $app->view->render($file, $data);
34 | return $app->response->html($rendered);
35 | });
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/tests/BagTests.php:
--------------------------------------------------------------------------------
1 | bag = new Bag();
11 | }
12 |
13 | public function tearDown() {
14 | $this->bag = null;
15 | }
16 |
17 | public function testSet()
18 | {
19 | $this->bag->set("foo.bar", "foobar");
20 |
21 | $items = $this->bag->all();
22 |
23 | $this->assertEquals($items['foo']['bar'], "foobar");
24 | }
25 |
26 | public function testSetDotNotation()
27 | {
28 | $this->bag["foo.bar"] = "foobar";
29 |
30 | $items = $this->bag->all();
31 |
32 | $this->assertEquals($items['foo']['bar'], "foobar");
33 | }
34 |
35 | public function testGet()
36 | {
37 | $this->bag->set("foo.bar", "foobar");
38 |
39 | $value = $this->bag->get("foo.bar");
40 | $undefined = $this->bag->get("undefined.key");
41 | $default_value = $this->bag->get("undefined.key", "default value");
42 |
43 | $this->assertEquals($value, "foobar");
44 | $this->assertEquals(NULL, $undefined);
45 | $this->assertEquals($default_value, "default value");
46 | }
47 |
48 | public function testGetDotNotation()
49 | {
50 | $this->bag->set("foo.bar", "foobar");
51 |
52 | $value = $this->bag["foo.bar"];
53 | $undefined = $this->bag["undefined.key"];
54 |
55 | $this->assertEquals($value, "foobar");
56 | }
57 |
58 | public function testHas()
59 | {
60 | $this->bag->set("foo.bar", "foobar");
61 |
62 | $this->assertTrue($this->bag->has("foo.bar"));
63 | $this->assertFalse($this->bag->has("undefined.key"));
64 | }
65 |
66 | public function testExcept()
67 | {
68 | $this->bag->set('foo.A', 'A');
69 | $this->bag->set('foo.B', 'B');
70 | $this->bag->set('foo.C', 'C');
71 | $this->bag->set('bar.D', 'C');
72 |
73 | $new_bag = $this->bag->except(['foo.B']);
74 |
75 | $this->assertTrue(isset($new_bag['foo']));
76 | $this->assertTrue(isset($new_bag['bar']));
77 | $this->assertTrue(isset($new_bag['foo']['A']));
78 | $this->assertTrue(isset($new_bag['foo']['C']));
79 | $this->assertFalse(isset($new_bag['foo']['B']));
80 | }
81 |
82 | public function testOnly()
83 | {
84 | $this->bag->set('foo.A', 'A');
85 | $this->bag->set('foo.B', 'B');
86 | $this->bag->set('foo.C', 'C');
87 |
88 | $new_bag = $this->bag->only(['foo.A', 'foo.C']);
89 |
90 | $this->assertTrue(isset($new_bag['foo']));
91 | $this->assertTrue(isset($new_bag['foo']['A']));
92 | $this->assertTrue(isset($new_bag['foo']['C']));
93 | $this->assertFalse(isset($new_bag['foo']['B']));
94 | }
95 |
96 | public function testNamespace()
97 | {
98 | // add namespace store
99 | $this->bag->store = array(
100 | 'store_name' => 'my store',
101 | 'database' => array(
102 | 'host' => 'localhost',
103 | 'username' => 'db_user',
104 | 'password' => 'db_password',
105 | 'dbname' => 'db_store'
106 | )
107 | );
108 |
109 | // get simple value from namespace
110 | $store_name = $this->bag->store->get("store_name");
111 | $this->assertEquals($store_name, "my store");
112 |
113 | // get value using dot notation
114 | $db_host = $this->bag->store["database.host"];
115 | $this->assertEquals($db_host, "localhost");
116 |
117 | // set value using set
118 | $this->bag->store->set("database.host", "new_db_host");
119 | // set value using dot notation
120 | $this->bag->store->set("database.dbname", "new_store_db");
121 |
122 | // get all changed items store
123 | $items = $this->bag->store->all();
124 |
125 | $this->assertEquals(5, $this->bag->size());
126 | $this->assertEquals($items["database"]["host"], "new_db_host");
127 | $this->assertEquals($items["database"]["dbname"], "new_store_db");
128 | }
129 |
130 | }
--------------------------------------------------------------------------------
/tests/ContainerTests.php:
--------------------------------------------------------------------------------
1 | foo = $foo;
21 | }
22 |
23 | }
24 |
25 | class HasMethodDependFoo {
26 |
27 | public function getFoo(Foo $foo)
28 | {
29 | return $foo->foo;
30 | }
31 |
32 | }
33 | /**
34 | * -------------------------------------------------------------------------
35 | *
36 | */
37 |
38 | class ContainerTests extends PHPUnit_Framework_TestCase {
39 |
40 | protected $container;
41 |
42 | public function setUp() {
43 | $this->container = new Container();
44 | }
45 |
46 | public function tearDown() {
47 | $this->container = null;
48 | }
49 |
50 | public function testValueInjection()
51 | {
52 | $this->container['foo'] = "bar";
53 |
54 | $this->assertEquals("bar", $this->container['foo']);
55 | }
56 |
57 | public function testSingleton()
58 | {
59 | $this->container['foo'] = $this->container->singleton(function() {
60 | return new Foo;
61 | });
62 |
63 | $this->assertTrue($this->container['foo'] === $this->container['foo']);
64 | }
65 |
66 | public function testNotSingleton()
67 | {
68 | $this->container['foo'] = function() {
69 | return new Foo;
70 | };
71 |
72 | $this->assertTrue($this->container['foo'] !== $this->container['foo']);
73 | }
74 |
75 | public function testConstructorInjection()
76 | {
77 | $this->container['foo:Foo'] = $this->container->singleton(function() {
78 | return new Foo;
79 | });
80 |
81 | $this->container['dependFoo'] = $this->container->make('DependFoo');
82 | $this->assertTrue($this->container['foo'] === $this->container['dependFoo']->foo);
83 | }
84 |
85 | public function testConstructorInjectionAlsoInjectUnregisteredClass()
86 | {
87 | $dependFoo = $this->container->make('DependFoo');
88 | $this->assertTrue($dependFoo->foo instanceof Foo);
89 | }
90 |
91 | public function testMethodInjection()
92 | {
93 | $this->container['foo:Foo'] = $this->container->singleton(function() {
94 | return new Foo;
95 | });
96 |
97 | $this->assertEquals(
98 | $this->container['foo']->foo,
99 | $this->container->call(['HasMethodDependFoo', 'getFoo'])
100 | );
101 | }
102 |
103 | public function testMethodInjectionAlsoInjectUnregisteredClass()
104 | {
105 | $this->assertEquals($this->container->call(['HasMethodDependFoo', 'getFoo']), "foobar");
106 | }
107 |
108 | }
--------------------------------------------------------------------------------
/tests/HookTests.php:
--------------------------------------------------------------------------------
1 | app = new App('');
11 | $this->hook = $this->app->hook;
12 | }
13 |
14 | public function tearDown() {
15 | $this->app = null;
16 | $this->hook = null;
17 | }
18 |
19 | public function testApply()
20 | {
21 | $hook = $this->hook;
22 |
23 | $hook->on('test', function() {
24 | echo "foo";
25 | });
26 |
27 | $hook->on('test', function() {
28 | echo "bar";
29 | });
30 |
31 | $this->assertEquals("foobar", $this->getOutput(
32 | function() use ($hook) {
33 | $hook->apply("test");
34 | }
35 | ));
36 | }
37 |
38 | public function testApplyOnceEvent()
39 | {
40 | $hook = $this->hook;
41 |
42 | $hook->once('test', function() {
43 | echo "foo";
44 | });
45 |
46 | $this->assertEquals("foo", $this->getOutput(
47 | function() use ($hook) {
48 | $hook->apply("test");
49 | }
50 | ));
51 |
52 | $this->assertEmpty($this->getOutput(
53 | function() use ($hook) {
54 | $hook->apply("test");
55 | }
56 | ));
57 | }
58 |
59 |
60 | public function testApplyWithDotNotation()
61 | {
62 | $hook = $this->hook;
63 |
64 | $hook->on('test.foo', function() {
65 | echo "foo";
66 | });
67 |
68 | $hook->on('test.bar.baz', function() {
69 | echo "barbaz";
70 | });
71 |
72 | $hook->on('test.qux', function() {
73 | echo "qux";
74 | });
75 |
76 | $this->assertEquals("foobarbazqux", $this->getOutput(
77 | function() use ($hook) {
78 | $hook->apply("test");
79 | }
80 | ));
81 | }
82 |
83 | protected function getOutput(\Closure $callback)
84 | {
85 | ob_start();
86 | $callback();
87 | return ob_get_clean();
88 | }
89 |
90 | }
--------------------------------------------------------------------------------
/tests/RouterTests.php:
--------------------------------------------------------------------------------
1 | app = new App('router-test', ['app' => ['debug' => true]]);
12 | $this->router = $this->app->router;
13 | }
14 |
15 | public function tearDown() {
16 | $this->app = null;
17 | }
18 |
19 | public function testDispatchRouteGet()
20 | {
21 | $this->router->post('/route/:param', 'handler');
22 | $this->router->get('/route/:param', 'handler');
23 | $this->router->put('/route/:param', 'handler');
24 |
25 | $route = $this->router->dispatch('GET', '/route/value');
26 |
27 | $this->assertInstanceOf('Rakit\Framework\Router\Route', $route);
28 | $this->assertEquals('GET', $route->getMethod());
29 | }
30 |
31 | public function testDispatchRoutePost()
32 | {
33 | $this->router->get('/route/:param', 'handler');
34 | $this->router->post('/route/:param', 'handler');
35 | $this->router->put('/route/:param', 'handler');
36 |
37 | $route = $this->router->dispatch('POST', '/route/value');
38 |
39 | $this->assertInstanceOf('Rakit\Framework\Router\Route', $route);
40 | $this->assertEquals('POST', $route->getMethod());
41 | }
42 |
43 | public function testDispatchRoutePut()
44 | {
45 | $this->router->get('/route/:param', 'handler');
46 | $this->router->put('/route/:param', 'handler');
47 | $this->router->post('/route/:param', 'handler');
48 |
49 | $route = $this->router->dispatch('PUT', '/route/value');
50 |
51 | $this->assertInstanceOf('Rakit\Framework\Router\Route', $route);
52 | $this->assertEquals('PUT', $route->getMethod());
53 | }
54 |
55 | public function testDispatchRoutePatch()
56 | {
57 | $this->router->get('/route/:param', 'handler');
58 | $this->router->patch('/route/:param', 'handler');
59 | $this->router->post('/route/:param', 'handler');
60 |
61 | $route = $this->router->dispatch('PATCH', '/route/value');
62 |
63 | $this->assertInstanceOf('Rakit\Framework\Router\Route', $route);
64 | $this->assertEquals('PATCH', $route->getMethod());
65 | }
66 |
67 | public function testDispatchRouteDelete()
68 | {
69 | $this->router->get('/route/:param', 'handler');
70 | $this->router->delete('/route/:param', 'handler');
71 | $this->router->post('/route/:param', 'handler');
72 |
73 | $route = $this->router->dispatch('DELETE', '/route/value');
74 |
75 | $this->assertInstanceOf('Rakit\Framework\Router\Route', $route);
76 | $this->assertEquals('DELETE', $route->getMethod());
77 | }
78 |
79 | public function testDispatchParameters()
80 | {
81 | $this->router->get('/route/:foo/:bar', 'handler');
82 |
83 | $route = $this->router->dispatch('GET', '/route/param1/param2');
84 |
85 | $this->assertInstanceOf('Rakit\Framework\Router\Route', $route);
86 |
87 | $params = $route->params;
88 | $this->assertEquals(2, count($params));
89 | $this->assertEquals($params['foo'], 'param1');
90 | $this->assertEquals($params['bar'], 'param2');
91 | }
92 |
93 | public function testDispatchOptionalParameter()
94 | {
95 | $this->router->get('/route/:foo(/:bar(/:baz))', 'handler');
96 |
97 | foreach(['/route/one' => 1, '/route/one/two' => 2, '/route/one/two/three' => 3] as $path => $expected_count) {
98 | $route = $this->router->dispatch('GET', $path);
99 | $this->assertInstanceOf('Rakit\Framework\Router\Route', $route);
100 | $this->assertEquals($expected_count, count($route->params));
101 | }
102 | }
103 |
104 | public function testDispatchCustomRegexParameter()
105 | {
106 | $this->router->get('/edit/:id', 'handler')->where('id', '\d{2}');
107 |
108 | $tests = [
109 | '/edit/foo' => false,
110 | '/edit/12foo' => false,
111 | '/edit/foo99' => false,
112 | '/edit/1' => false,
113 | '/edit/123' => false,
114 | '/edit/11' => true,
115 | ];
116 |
117 | foreach($tests as $path => $expected) {
118 | $route = $this->router->dispatch('GET', $path);
119 | $this->assertEquals($expected, !is_null($route));
120 | }
121 | }
122 |
123 | public function testRouteNaming()
124 | {
125 | $this->router->get('/login', 'handler')->name('form-login');
126 | $this->router->post('/login', 'handler')->name('post-login');
127 |
128 | $route = $this->router->findRouteByName('post-login');
129 |
130 | $this->assertInstanceOf('Rakit\Framework\Router\Route', $route);
131 | $this->assertEquals('POST', $route->getMethod());
132 | }
133 |
134 | }
--------------------------------------------------------------------------------
/tests/RunAppTests.php:
--------------------------------------------------------------------------------
1 | app = new App('test', [
15 | 'app' => [
16 | 'debug' => false
17 | ]
18 | ]);
19 |
20 | // handle 404
21 | $this->app->on(404, function($response) {
22 | return $response->setStatus(404)->html("Not Found!");
23 | });
24 | }
25 |
26 | public function tearDown() {
27 | $this->app = null;
28 | }
29 |
30 | /**
31 | * @runInSeparateProcess
32 | * @preserveGlobalState enabled
33 | */
34 | public function testHelloWorld()
35 | {
36 | $this->app->get("/", function() {
37 | return "Hello World!";
38 | });
39 |
40 | $this->assertResponse("GET", "/", "Hello World!", 200);
41 | }
42 |
43 | /**
44 | * @runInSeparateProcess
45 | * @preserveGlobalState enabled
46 | */
47 | public function testNotFound()
48 | {
49 | $app = $this->app;
50 |
51 | $this->assertResponse("GET", "/unregistered-route", 'Not Found!', 404);
52 | }
53 |
54 | /**
55 | * @runInSeparateProcess
56 | * @preserveGlobalState enabled
57 | */
58 | public function testMethodGet()
59 | {
60 | $this->app->get("/foo", function() {
61 | return "get";
62 | });
63 |
64 | $this->assertResponse("GET", "/foo", 'get');
65 | }
66 |
67 | /**
68 | * @runInSeparateProcess
69 | * @preserveGlobalState enabled
70 | */
71 | public function testMethodPost()
72 | {
73 | $this->app->post("/foo", function() {
74 | return "post";
75 | });
76 |
77 | $this->assertResponse("POST", "/foo", 'post');
78 | }
79 |
80 | /**
81 | * @runInSeparateProcess
82 | * @preserveGlobalState enabled
83 | */
84 | public function testMethodPut()
85 | {
86 | $this->app->put("/foo", function() {
87 | return "put";
88 | });
89 |
90 | $this->assertResponse("PUT", "/foo", 'put');
91 | }
92 |
93 | /**
94 | * @runInSeparateProcess
95 | * @preserveGlobalState enabled
96 | */
97 | public function testMethodPatch()
98 | {
99 | $this->app->patch("/foo", function() {
100 | return "patch";
101 | });
102 |
103 | $this->assertResponse("PATCH", "/foo", 'patch');
104 | }
105 |
106 | /**
107 | * @runInSeparateProcess
108 | * @preserveGlobalState enabled
109 | */
110 | public function testMethodDelete()
111 | {
112 | $this->app->delete("/foo", function() {
113 | return "delete";
114 | });
115 |
116 | $this->assertResponse("DELETE", "/foo", 'delete');
117 | }
118 |
119 | /**
120 | * @runInSeparateProcess
121 | * @preserveGlobalState enabled
122 | */
123 | public function testMethodHead()
124 | {
125 | $this->app->get("/foo", function() {
126 | return "head";
127 | });
128 |
129 | // in HEAD request, we don't send response body
130 | $this->assertResponse("HEAD", "/foo", '');
131 | }
132 |
133 | /**
134 | * @runInSeparateProcess
135 | * @preserveGlobalState enabled
136 | */
137 | public function testRouteParam()
138 | {
139 | $this->app->get("/hello/:name/:age", function($name, $age) {
140 | return $name."-".$age;
141 | });
142 |
143 | $this->assertResponse("GET", "/hello/foo/12", 'foo-12');
144 | $this->assertResponse("GET", "/hello/bar/24", 'bar-24');
145 | $this->assertResponse("GET", "/hello/bar", 'Not Found!', 404);
146 | }
147 |
148 | /**
149 | * @runInSeparateProcess
150 | * @preserveGlobalState enabled
151 | */
152 | public function testOptionalRouteParam()
153 | {
154 | $this->app->get("/hello/:name(/:age)", function($name, $age = 1) {
155 | return $name."-".$age;
156 | });
157 |
158 | $this->assertResponse("GET", "/hello/bar", 'bar-1');
159 | $this->assertResponse("GET", "/hello/foo/12", 'foo-12');
160 | }
161 |
162 | /**
163 | * @runInSeparateProcess
164 | * @preserveGlobalState enabled
165 | */
166 | public function testRouteParamCondition()
167 | {
168 | $this->app->get("/hello/:name/:age", function($name, $age = 1) {
169 | return $name."-".$age;
170 | })->where('age', '\d+');
171 |
172 | $this->assertResponse("GET", "/hello/foo/12", 'foo-12');
173 | $this->assertResponse("GET", "/hello/bar/baz", 'Not Found!', 404);
174 | }
175 |
176 | /**
177 | * @runInSeparateProcess
178 | * @preserveGlobalState enabled
179 | */
180 | public function testMiddlewareBefore()
181 | {
182 | $this->app->setMiddleware('foobar', function($req, $res, $next) {
183 | $req->foobar = "foobar";
184 | return $next();
185 | });
186 |
187 | $this->app->get("/foo", function(Request $request) {
188 | return $request->foobar;
189 | })->middleware('foobar');
190 |
191 | $this->assertResponse("GET", "/foo", 'foobar');
192 | }
193 |
194 | /**
195 | * @runInSeparateProcess
196 | * @preserveGlobalState enabled
197 | */
198 | public function testMiddlewareAfter()
199 | {
200 | $this->app->setMiddleware('uppercase', function($req, $res, $next) {
201 | $next();
202 | return strtoupper($res->body);
203 | });
204 |
205 | $this->app->get("/foo", function(Request $request) {
206 | return "foo";
207 | })->uppercase();
208 |
209 | $this->assertResponse("GET", "/foo", 'FOO');
210 | }
211 |
212 | /**
213 | * @runInSeparateProcess
214 | * @preserveGlobalState enabled
215 | */
216 | public function testMiddlewareBeforeAndAfter()
217 | {
218 | $this->app->setMiddleware('uppercase', function($req, $res, $next) {
219 | $req->foobar = "foobar";
220 |
221 | $next();
222 |
223 | return strtoupper($res->body);
224 | });
225 |
226 | $this->app->get("/foo", function(Request $request) {
227 | return $request->foobar."bazQux";
228 | })->uppercase();
229 |
230 | $this->assertResponse("GET", "/foo", 'FOOBARBAZQUX');
231 | }
232 |
233 | /**
234 | * @runInSeparateProcess
235 | * @preserveGlobalState enabled
236 | */
237 | public function testMiddlewareParam()
238 | {
239 | $this->app->setMiddleware('setStr', function($req, $res, $next, $str) {
240 | $req->str = $str;
241 | return $next();
242 | });
243 |
244 | $this->app->get("/foo", function(Request $request) {
245 | return $request->str;
246 | })->setStr('foobar');
247 |
248 | $this->assertResponse("GET", "/foo", 'foobar');
249 | }
250 |
251 | /**
252 | * @runInSeparateProcess
253 | * @preserveGlobalState enabled
254 | */
255 | public function testMultipleMiddleware()
256 | {
257 | $this->app->setMiddleware('setStr', function($req, $res, $next, $str) {
258 | $req->str = $str;
259 | return $next();
260 | });
261 |
262 | $this->app->setMiddleware('uppercase', function($req, $res, $next) {
263 | $next();
264 | return strtoupper($res->body);
265 | });
266 |
267 | $this->app->setMiddleware('jsonify', function($req, $res, $next) {
268 | $next();
269 | return $res->json(['body' => $res->body]);
270 | });
271 |
272 | $this->app->get("/foo", function(Request $request) {
273 | return $request->str."bazQux";
274 | })->setStr('foobar')->jsonify()->uppercase();
275 |
276 | $this->assertResponse("GET", "/foo", '{"body":"FOOBARBAZQUX"}', 200, 'application/json');
277 | }
278 |
279 | /**
280 | * @runInSeparateProcess
281 | * @preserveGlobalState enabled
282 | */
283 | public function testIgnoringController()
284 | {
285 | $this->app->setMiddleware('no-controller', function($req, $res, $next) {
286 | return "controller ignored";
287 | });
288 |
289 | $this->app->get("/foo", function(Request $request) {
290 | return "foobar";
291 | })->middleware('no-controller');
292 |
293 | $this->assertResponse("GET", "/foo", 'controller ignored');
294 | }
295 |
296 | /**
297 | * @runInSeparateProcess
298 | * @preserveGlobalState enabled
299 | */
300 | public function testRouteGroup()
301 | {
302 | $this->app->group('/group', function($group) {
303 |
304 | $group->get('/hello', function() {
305 | return "IM IN GROUP";
306 | });
307 |
308 | });
309 |
310 | $this->assertResponse("GET", "/group/hello", 'IM IN GROUP');
311 | }
312 |
313 | /**
314 | * @runInSeparateProcess
315 | * @preserveGlobalState enabled
316 | */
317 | public function testResponseJson()
318 | {
319 | $this->app->get('/anything.json', function() {
320 | return [
321 | 'message' => 'hello'
322 | ];
323 | });
324 |
325 | $this->assertResponse("GET", "/anything.json", '{"message":"hello"}', 200, 'application/json');
326 | }
327 |
328 | /**
329 | * @runInSeparateProcess
330 | * @preserveGlobalState enabled
331 | */
332 | public function testMiddlewareKeepResponseToJson()
333 | {
334 | $this->app->setMiddleware('uppercase', function($req, $res, $next) {
335 | $next();
336 | return strtoupper($res->body);
337 | });
338 |
339 | $this->app->get('/anything.json', function() {
340 | return [
341 | 'message' => 'hello'
342 | ];
343 | })->uppercase();
344 |
345 | $this->assertResponse("GET", "/anything.json", '{"MESSAGE":"HELLO"}', 200, 'application/json');
346 | }
347 |
348 | /**
349 | * @runInSeparateProcess
350 | * @preserveGlobalState enabled
351 | */
352 | public function testGlobalMiddleware()
353 | {
354 | $this->app->setMiddleware('uppercase', function($req, $res, $next) {
355 | $next();
356 | return strtoupper($res->body);
357 | });
358 |
359 | $this->app->useMiddleware('uppercase');
360 |
361 | $this->app->get('/anything.json', function() {
362 | return [
363 | 'message' => 'hello'
364 | ];
365 | });
366 |
367 | $this->assertResponse("GET", "/anything.json", '{"MESSAGE":"HELLO"}', 200, 'application/json');
368 | }
369 |
370 | /**
371 | * @runInSeparateProcess
372 | * @preserveGlobalState enabled
373 | */
374 | public function testRouteGroupParamCondition()
375 | {
376 | $this->app->group('/u/:username', function($group) {
377 |
378 | $group->get('/profile', function($username, Request $request) {
379 | return $username.' profile';
380 | });
381 |
382 | })->where('username', '[a-zA-Z_]+');
383 |
384 | $this->assertResponse("GET", "/u/foobar/profile", 'foobar profile');
385 | $this->assertResponse("GET", "/u/foobar/123", 'Not Found!', 404);
386 | }
387 |
388 | /**
389 | * @runInSeparateProcess
390 | * @preserveGlobalState enabled
391 | */
392 | public function testRouteGroupMiddleware()
393 | {
394 | $this->app->setMiddleware('setStr', function($req, $res, $next, $str) {
395 | $req->str = $str;
396 | return $next();
397 | });
398 |
399 | $this->app->group('/group', function($group) {
400 |
401 | $group->get('/hello', function(Request $req) {
402 | return $req->str;
403 | });
404 |
405 | })->setStr('foobar');
406 |
407 | $this->assertResponse("GET", "/group/hello", 'foobar');
408 | }
409 |
410 | /**
411 | * @runInSeparateProcess
412 | * @preserveGlobalState enabled
413 | */
414 | public function testResponseView()
415 | {
416 | $this->app->config['view.path'] = __DIR__.'/resources/views';
417 |
418 | $this->app->get("/hello", function(Response $response) {
419 | return $response->view('hello.php');
420 | });
421 |
422 | $this->assertResponse("GET", "/hello", 'Hello World!
');
423 | }
424 |
425 | /**
426 | * @runInSeparateProcess
427 | * @preserveGlobalState enabled
428 | */
429 | public function testPassDataIntoView()
430 | {
431 | $this->app->config['view.path'] = __DIR__.'/resources/views';
432 |
433 | $this->app->get("/hello", function(Response $response) {
434 | return $response->view('hello-name.php', [
435 | 'name' => 'John'
436 | ]);
437 | });
438 |
439 | $this->assertResponse("GET", "/hello", 'Hello John!
');
440 | }
441 |
442 | /**
443 | * @runInSeparateProcess
444 | * @preserveGlobalState enabled
445 | */
446 | public function testAppBindedToClosure()
447 | {
448 | $this->app->something = "foo";
449 | $this->app->foo = "bar";
450 | $this->app->setMiddleware('test', function($req, $res, $next) {
451 | $next();
452 | return strtoupper($this->something.$res->body);
453 | });
454 |
455 | $this->app->get("/hello", function() {
456 | return $this->foo;
457 | })->test();
458 |
459 | $this->assertResponse("GET", "/hello", 'FOOBAR');
460 | }
461 |
462 | /**
463 | * @runInSeparateProcess
464 | * @preserveGlobalState enabled
465 | */
466 | public function testException()
467 | {
468 | $this->app->get("/error", function(Response $response) {
469 | throw new \Exception;
470 | });
471 |
472 | $this->assertResponse("GET", "/error", 'Something went wrong', 500);
473 | }
474 |
475 | /**
476 | * @runInSeparateProcess
477 | * @preserveGlobalState enabled
478 | */
479 | public function testHandleException()
480 | {
481 | $this->app->handle('InvalidArgumentException', function($e, Response $response) {
482 | return $response->html("Invalid Argument", 501);
483 | });
484 |
485 | $this->app->get("/error", function(Response $response) {
486 | throw new \InvalidArgumentException;
487 | });
488 |
489 | $this->assertResponse("GET", "/error", 'Invalid Argument', 501);
490 | }
491 |
492 | protected function runAndGetResponse($method, $path)
493 | {
494 | $this->app->request->server['REQUEST_METHOD'] = $method;
495 |
496 | //buffer output, so output won't appear in terminal
497 | ob_start();
498 | $this->app->run($method, $path);
499 | $rendered = ob_get_clean();
500 |
501 | $response = clone $this->app->response;
502 | $this->app->response->reset();
503 |
504 | return [$response, $rendered];
505 | }
506 |
507 | protected function assertResponse($method, $path, $assert_body, $assert_status = 200, $assert_content_type = 'text/html')
508 | {
509 | $at = $method.' '.$path.' => '.$assert_body;
510 | list($response, $rendered) = $this->runAndGetResponse($method, $path);
511 |
512 | $this->assertEquals($rendered, $assert_body, $at);
513 | $this->assertEquals($response->getStatus(), $assert_status, $at);
514 | $this->assertEquals($response->getContentType(), $assert_content_type, $at);
515 | }
516 |
517 | }
--------------------------------------------------------------------------------
/tests/resources/views/hello-name.php:
--------------------------------------------------------------------------------
1 | Hello !
--------------------------------------------------------------------------------
/tests/resources/views/hello.php:
--------------------------------------------------------------------------------
1 | Hello World!
--------------------------------------------------------------------------------