21 | */
22 | class AnnotationsServiceProvider implements ServiceProviderInterface
23 | {
24 | public function register(ContainerInterface $container)
25 | {
26 | $container->add([
27 | 'annotations.debug' => false,
28 | 'annotations.options' => [
29 | 'cache_driver' => 'array',
30 | 'cache_dir' => null,
31 | ]
32 | ]);
33 | $container['annotation_reader'] = function () use ($container) {
34 | return new AnnotationReader();
35 | };
36 |
37 | $container['annotations.cached_reader.factory'] = $container->protect(function ($options) use ($container) {
38 | if (!isset($container['cache'])) {
39 | throw new \LogicException(
40 | 'You must register the DoctrineCacheServiceProvider to use the AnnotationServiceProvider.'
41 | );
42 | }
43 |
44 | return $container['cache_factory']($options['cache_driver'], $options);
45 | });
46 |
47 | $container['annotations.cached_reader'] = function () use ($container) {
48 | return new CachedReader(
49 | $container['annotation_reader'],
50 | $container['annotations.cached_reader.factory']($container['annotations.options']),
51 | $container['annotations.debug']
52 | );
53 | };
54 |
55 | $container['annotations'] = function () use ($container) {
56 | return $container['annotations.cached_reader'];
57 | };
58 | }
59 | }
--------------------------------------------------------------------------------
/src/Jade/HttpKernel/ControllerResolver.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\HttpKernel;
13 |
14 | use Jade\ContainerAwareInterface;
15 | use Jade\ContainerInterface;
16 | use Psr\Http\Message\ServerRequestInterface;
17 |
18 | class ControllerResolver implements ControllerResolverInterface
19 | {
20 | /**
21 | * @var ContainerInterface
22 | */
23 | protected $container;
24 |
25 | public function __construct(ContainerInterface $container)
26 | {
27 | $this->container = $container;
28 | }
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | public function getController(ServerRequestInterface $request)
34 | {
35 | $route = $request->getAttribute('_route');
36 | if (null === $route) { //如果没有route则直接中断
37 | throw new \RuntimeException(sprintf('Cannot find route'));
38 | }
39 | $action = $route->getAction();
40 | if ($action instanceof \Closure) { // 如果是可调用的结构直接返回
41 | return $action;
42 | }
43 | return $this->createController($action);
44 | }
45 |
46 | /**
47 | * 创建控制器
48 | *
49 | * @param string|array $controller
50 | * @return array
51 | */
52 | protected function createController($controller)
53 | {
54 | list($class, $method) = is_string($controller) ? explode('::', $controller) : $controller;
55 |
56 | if (!class_exists($class)) {
57 | throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
58 | }
59 | return [$this->configureController($this->instantiateController($class)), $method];
60 | }
61 |
62 | /**
63 | * 创建控制器实例
64 | *
65 | * @param string $class A class name
66 | *
67 | * @return object
68 | */
69 | protected function instantiateController($class)
70 | {
71 | return new $class();
72 | }
73 |
74 | protected function configureController($controller)
75 | {
76 | if ($controller instanceof ContainerAwareInterface) {
77 | $controller->setContainer($this->container);
78 | }
79 | return $controller;
80 | }
81 | }
--------------------------------------------------------------------------------
/src/Jade/HttpKernel/HttpKernelProvider.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\HttpKernel;
13 |
14 | use Jade\ContainerInterface;
15 | use Jade\EventProviderInterface;
16 | use Jade\Routing\Router;
17 | use Jade\ServiceProviderInterface;
18 | use Symfony\Component\EventDispatcher\EventDispatcherInterface;
19 | use Zend\HttpHandlerRunner\Emitter\SapiEmitter;
20 | use Zend\HttpHandlerRunner\Emitter\SapiStreamEmitter;
21 |
22 | class HttpKernelProvider implements ServiceProviderInterface, EventProviderInterface
23 | {
24 | public function register(ContainerInterface $container)
25 | {
26 | $container->add($this->getDefaults());
27 | // 路由控制
28 | $container['router'] = function($c){
29 | return new Router($c['route_collector']->getRoutes());
30 | };
31 | $container['router_listener'] = function($c){
32 | return new RouterListener($c['router']);
33 | };
34 | // http kernel
35 | $container['controller_resolver'] = function($c){
36 | return new $c['http_kernel.controller_resolver_class']($c);
37 | };
38 | $container['argument_resolver'] = function($c){
39 | return new $c['http_kernel.argument_resolver_class'];
40 | };
41 | // http response emitter
42 | $container['http_emitter'] = function(){
43 | return new EmitterDecorator(
44 | new SapiEmitter(),
45 | new SapiStreamEmitter()
46 | );
47 | };
48 | $container['http_kernel'] = function($c){
49 | return new HttpKernel(
50 | $c['event_dispatcher'],
51 | $c['controller_resolver'],
52 | $c['argument_resolver'],
53 | $c['middleware_pipeline'],
54 | $c['http_emitter']
55 | );
56 | };
57 | }
58 |
59 | protected function getDefaults()
60 | {
61 | return [
62 | 'http_kernel.controller_resolver_class' => ControllerResolver::class,
63 | 'http_kernel.argument_resolver_class' => ArgumentResolver::class
64 | ];
65 | }
66 |
67 | public function subscribe(EventDispatcherInterface $eventDispatcher, ContainerInterface $container)
68 | {
69 | $eventDispatcher->addSubscriber($container['router_listener']);
70 | }
71 | }
--------------------------------------------------------------------------------
/src/Jade/HttpKernel/ArgumentResolver.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\HttpKernel;
13 |
14 | use Psr\Http\Message\ServerRequestInterface;
15 |
16 | class ArgumentResolver implements ArgumentResolverInterface
17 | {
18 | protected $defaults = [];
19 |
20 | public function __construct($defaults = [])
21 | {
22 | $this->defaults = $defaults;
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function getArguments(ServerRequestInterface $request, callable $controller)
29 | {
30 | $this->addDefault('request', $request);
31 | $providedArguments = array_merge(
32 | $this->defaults,
33 | $request->getAttribute('_route')->getArguments()
34 | );
35 | $arguments = [];
36 | $parameters = $this->createArgumentMetadata($controller);
37 | foreach ($parameters as $parameter) {
38 | $name = $parameter->getName();
39 | $arguments[$name] = isset($providedArguments[$name])
40 | ? $providedArguments[$name] : (
41 | $parameter->isOptional() ? $parameter->getDefaultValue() : null
42 | );
43 | }
44 | return $arguments;
45 | }
46 |
47 | /**
48 | * 添加一个默认参数
49 | *
50 | * @param string $name
51 | * @param mixed $value
52 | */
53 | public function addDefault($name, $value)
54 | {
55 | $this->defaults[$name] = $value;
56 | }
57 |
58 | /**
59 | * 设置默认
60 | *
61 | * @param array $defaults
62 | */
63 | public function setDefaults(array $defaults): void
64 | {
65 | $this->defaults = $defaults;
66 | }
67 |
68 | /**
69 | * 分析控制器的参数
70 | *
71 | * @param callable $controller
72 | * @return \ReflectionParameter[]
73 | * @throws \ReflectionException
74 | */
75 | protected function createArgumentMetadata($controller)
76 | {
77 | if (\is_array($controller)) {
78 | $reflection = new \ReflectionMethod($controller[0], $controller[1]);
79 | } elseif (\is_object($controller) && !$controller instanceof \Closure) {
80 | $reflection = (new \ReflectionObject($controller))->getMethod('__invoke');
81 | } else {
82 | $reflection = new \ReflectionFunction($controller);
83 | }
84 | return $reflection->getParameters();
85 | }
86 | }
--------------------------------------------------------------------------------
/src/Jade/HttpKernel/RouterListener.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\HttpKernel;
13 |
14 | use FastRoute\Dispatcher;
15 | use Jade\Exception\MethodNotAllowedHttpException;
16 | use Jade\Exception\NotFoundHttpException;
17 | use Jade\HttpKernel\Event\GetResponseEvent;
18 | use Jade\Routing\Route;
19 | use Jade\Routing\Router;
20 | use Symfony\Component\EventDispatcher\EventSubscriberInterface;
21 |
22 | class RouterListener implements EventSubscriberInterface
23 | {
24 | /**
25 | * @var Router
26 | */
27 | protected $router;
28 |
29 | public function __construct(Router $router)
30 | {
31 | $this->router = $router;
32 | }
33 |
34 | public static function getSubscribedEvents()
35 | {
36 | return [
37 | KernelEvents::MIDDLEWARE => 'onRequest'
38 | ];
39 | }
40 |
41 | public function onRequest(GetResponseEvent $event)
42 | {
43 | $request = $event->getRequest();
44 | $matches = $this->router->dispatch($request);
45 | switch ($matches[0]) {
46 | case Dispatcher::FOUND:
47 | $route = $this->router->searchRoute($matches[1]);
48 | $this->prepareRoute($route, $matches);
49 | // 记录当前路由到指定请求体里
50 | $request = $request->withAttribute('_route', $route);
51 | break;
52 | case Dispatcher::METHOD_NOT_ALLOWED:
53 | $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)',
54 | $request->getMethod(), $request->getUri()->getPath(),
55 | implode(',', $matches[1])
56 | );
57 | throw new MethodNotAllowedHttpException($message);
58 | case Dispatcher::NOT_FOUND:
59 | $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getUri()->getPath());
60 | throw new NotFoundHttpException($message);
61 | }
62 | $event->setRequest($request);
63 | }
64 |
65 | /**
66 | * 预处理 route
67 | *
68 | * @param Route $route
69 | * @param array $matches
70 | */
71 | protected function prepareRoute(Route $route, $matches)
72 | {
73 | $routeArguments = [];
74 | foreach ($matches[2] as $k => $v) {
75 | $routeArguments[$k] = urldecode($v);
76 | }
77 | $route->setArguments($routeArguments);
78 | }
79 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Jade is a flexible PHP micro framework to develop web applications and APIs
25 |
26 | ## Installation
27 |
28 | The recommended way to install Jade is through Composer:
29 |
30 | ```bash
31 | $ composer require jadephp/jade
32 | ```
33 |
34 | ## Quick Start
35 |
36 | ```php
37 |
38 | use Psr\Http\Message\ServerRequestInterface;
39 | use Psr\Http\Server\RequestHandlerInterface;
40 | use Zend\Diactoros\Response;
41 |
42 | // 1. Create App
43 | $app = new Jade\App();
44 |
45 | // 2. Add routes
46 | $app->get('/ping', function(ServerRequestInterface $request){
47 | return new Response\TextResponse('pong');
48 | });
49 |
50 | // 3. Add middlewares
51 | $app->pipe(function(ServerRequestInterface $request, RequestHandlerInterface $handler){
52 | $response = $handler->handle($request);
53 | return $response->withHeader('X-Jade-Version', '0.0.1');
54 | });
55 |
56 | // 4. Listen and serve.
57 | $app->serve();
58 | ```
59 |
60 | The above code can create a simple heartbeat application.
61 |
62 | Test this with the built-in PHP server:
63 |
64 | ```bash
65 | php -S 127.0.0.1:8000
66 | ```
67 | Use the browser open `http://127.0.0.1:8000/ping`
68 |
69 | ## Documentation
70 |
71 | Read the [documentation](./docs/index.md) for more information
72 |
73 | ## Tests
74 |
75 | To run the test suite, you need PHPUnit:
76 |
77 | ```bash
78 | $ phpunit
79 | ```
80 |
81 | ## License
82 |
83 | Jade is licensed under The MIT license. See [MIT](https://opensource.org/licenses/MIT)
84 |
85 |
--------------------------------------------------------------------------------
/tests/Routing/RouterTest.php:
--------------------------------------------------------------------------------
1 | assertSame($routes, $router->getRoutes());
19 | }
20 |
21 | public function testSearchRoute()
22 | {
23 | $routes = new RouteCollection();
24 | $router = new Router($routes);
25 | $route = new Route('route1', '/foo', function(){}, ['GET']);
26 | $routes->add($route);
27 | $this->assertSame($routes->search('route1'), $router->searchRoute('route1'));
28 | }
29 |
30 | public function testDispatcher()
31 | {
32 | $routes = new RouteCollection();
33 | $router = new Router($routes);
34 | $this->assertInstanceOf(Dispatcher::class, $router->getDispatcher());
35 | }
36 |
37 | protected function routerFactory()
38 | {
39 | $routes = new RouteCollection();
40 | $route = new Route('route1', '/foo', function(){}, ['GET']);
41 | $route2 = new Route('route2', '/hello/{username}', function(){}, ['GET']);
42 | $route3 = new Route('route3', '/greet/{username}', function(){}, ['POST']);
43 | $routes->add($route);
44 | $routes->add($route2);
45 | $routes->add($route3);
46 | return new Router($routes);
47 | }
48 |
49 | public function testDispatch()
50 | {
51 | $router = $this->routerFactory();
52 | $request = new ServerRequest([], [], '/foo', 'GET');
53 |
54 | $routerInfo = $router->dispatch($request);
55 | $this->assertEquals(1, $routerInfo[0]);
56 | }
57 |
58 | public function testDispatchWithName()
59 | {
60 | $router = $this->routerFactory();
61 | $request = new ServerRequest([], [], '/hello/Jade', 'GET');
62 |
63 | $routerInfo = $router->dispatch($request);
64 | $this->assertEquals(1, $routerInfo[0]);
65 | $this->assertArrayHasKey('username', $routerInfo[2]);
66 | $this->assertEquals('Jade', $routerInfo[2]['username']);
67 | }
68 |
69 | public function testDispatchWithNonMatchedMethod()
70 | {
71 | $router = $this->routerFactory();
72 | $request = new ServerRequest([], [], '/greet/Jade', 'GET');
73 |
74 | $routerInfo = $router->dispatch($request);
75 | $this->assertEquals(2, $routerInfo[0]);
76 | $this->assertEquals(['POST'], $routerInfo[1]);
77 | }
78 | // 其它类型调度 fastroute 已经测试通过
79 | }
--------------------------------------------------------------------------------
/src/Jade/Routing/RouteCollector.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\Routing;
13 |
14 | class RouteCollector
15 | {
16 | /**
17 | * @var string
18 | */
19 | protected $prefix;
20 |
21 | /**
22 | * @var Route[]|RouteCollection
23 | */
24 | protected $routes = [];
25 |
26 | public function __construct($prefix = '')
27 | {
28 | $this->prefix = $prefix;
29 | $this->routes = new RouteCollection();
30 | }
31 |
32 | /**
33 | * 获取路由集合
34 | *
35 | * @return RouteCollection
36 | */
37 | public function getRoutes()
38 | {
39 | return $this->routes;
40 | }
41 |
42 | /**
43 | * 创建一条 http get 路由
44 | *
45 | * @param string $path
46 | * @param string|callable $action
47 | * @return Route
48 | */
49 | public function get($path, $action)
50 | {
51 | return $this->map($path, $action, 'GET');
52 | }
53 |
54 | /**
55 | * 创建一条 http post 路由
56 | *
57 | * @param string $path
58 | * @param string|callable $action
59 | * @return Route
60 | */
61 | public function post($path, $action)
62 | {
63 | return $this->map($path, $action, 'POST');
64 | }
65 |
66 | /**
67 | * 创建一条 http delete 路由
68 | *
69 | * @param string $path
70 | * @param string|callable $action
71 | * @return Route
72 | */
73 | public function delete($path, $action)
74 | {
75 | return $this->map($path, $action, 'DELETE');
76 | }
77 |
78 | /**
79 | * 创建一条 http put/patch 路由
80 | *
81 | * @param string $path
82 | * @param string|callable $action
83 | * @return Route
84 | */
85 | public function put($path, $action)
86 | {
87 | return $this->map($path, $action, ['PUT', 'PATCH']);
88 | }
89 |
90 | /**
91 | * 创建一条 http options 路由
92 | *
93 | * @param string $path
94 | * @param string|callable $action
95 | * @return Route
96 | */
97 | public function options($path, $action)
98 | {
99 | return $this->map($path, $action, 'OPTIONS');
100 | }
101 |
102 | /**
103 | * 创建一条 http 请求路由
104 | *
105 | * @param string $path
106 | * @param string|callable $action
107 | * @param array|string $methods
108 | * @return Route
109 | */
110 | public function map($path, $action, $methods = [])
111 | {
112 | $path = $this->prefix . $path;
113 | $methods = array_map('strtoupper', (array)$methods);
114 | $route = new Route(null, $path, $action, $methods);
115 | $this->getRoutes()->add($route);
116 | return $route;
117 | }
118 |
119 | public function any($path, $action)
120 | {
121 | return $this->map($path, $action);
122 | }
123 |
124 | /**
125 | * 创建一条通用前缀的路由组
126 | *
127 | * @param string $prefix
128 | * @param callable $callback
129 | */
130 | public function prefix($prefix, callable $callback)
131 | {
132 | $collector = new RouteCollector($prefix);
133 | call_user_func($callback, $collector);
134 | $this->routes->merge($collector->getRoutes());
135 | }
136 | }
--------------------------------------------------------------------------------
/tests/AppTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(RouteCollection::class, $app->getRoutes());
22 | }
23 |
24 | public function testGetRoute()
25 | {
26 | $path = '/foo';
27 | $callable = function ($req, $res) {
28 | // Do something
29 | };
30 | $app = new App();
31 | $route = $app->get($path, $callable);
32 |
33 | $this->assertInstanceOf(Route::class, $route);
34 | $this->assertAttributeContains('GET', 'methods', $route);
35 | }
36 |
37 | public function testPostRoute()
38 | {
39 | $path = '/foo';
40 | $callable = function ($req, $res) {
41 | // Do something
42 | };
43 | $app = new App();
44 | $route = $app->post($path, $callable);
45 |
46 | $this->assertInstanceOf(Route::class, $route);
47 | $this->assertAttributeContains('POST', 'methods', $route);
48 | }
49 |
50 | public function testPutRoute()
51 | {
52 | $path = '/foo';
53 | $callable = function ($req, $res) {
54 | // Do something
55 | };
56 | $app = new App();
57 | $route = $app->put($path, $callable);
58 |
59 | $this->assertInstanceOf(Route::class, $route);
60 | $this->assertAttributeContains('PUT', 'methods', $route);
61 | $this->assertAttributeContains('PATCH', 'methods', $route);
62 | }
63 |
64 | public function testDeleteRoute()
65 | {
66 | $path = '/foo';
67 | $callable = function ($req, $res) {
68 | // Do something
69 | };
70 | $app = new App();
71 | $route = $app->delete($path, $callable);
72 |
73 | $this->assertInstanceOf(Route::class, $route);
74 | $this->assertAttributeContains('DELETE', 'methods', $route);
75 | }
76 |
77 | public function testOptionsRoute()
78 | {
79 | $path = '/foo';
80 | $callable = function ($req, $res) {
81 | // Do something
82 | };
83 | $app = new App();
84 | $route = $app->options($path, $callable);
85 |
86 | $this->assertInstanceOf(Route::class, $route);
87 | $this->assertAttributeContains('OPTIONS', 'methods', $route);
88 | }
89 |
90 | public function testContainer()
91 | {
92 | $app = new App();
93 | $this->assertInstanceOf(ContainerInterface::class, $app->getContainer());
94 | // 测试core provider
95 | $this->assertNotEmpty($app->getProviders());
96 | $this->assertInstanceOf(ServiceProviderInterface::class, $app->getProviders()[0]);
97 | }
98 |
99 | public function testBaseService()
100 | {
101 | $app = new App();
102 | $container = $app->getContainer();
103 | $this->assertInstanceOf(EventDispatcherInterface::class, $container->get('event_dispatcher'));
104 | $this->assertInstanceOf(MiddlewarePipeInterface::class, $container->get('middleware_pipeline'));
105 | $this->assertInstanceOf(MiddlewareFactory::class, $container->get('middleware_factory'));
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Jade/Routing/Route.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\Routing;
13 |
14 | class Route
15 | {
16 | /**
17 | * @var string
18 | */
19 | protected $name;
20 |
21 | /**
22 | * @var string
23 | */
24 | protected $pattern;
25 |
26 | /**
27 | * @var string|callable
28 | */
29 | protected $action;
30 |
31 | /**
32 | * @var array
33 | */
34 | protected $methods;
35 |
36 | /**
37 | * @var array
38 | */
39 | protected $arguments = [];
40 |
41 | public function __construct($name, $pattern, $action, $methods = [])
42 | {
43 | $this->name = $name;
44 | $this->pattern = $pattern;
45 | $this->action = $action;
46 | $this->methods = $methods;
47 | }
48 |
49 | /**
50 | * 返回路由唯一名称
51 | *
52 | * @return string
53 | */
54 | public function getName(): ?string
55 | {
56 | return $this->name;
57 | }
58 |
59 | /**
60 | * 设置唯一名称
61 | *
62 | * @param string $name
63 | */
64 | public function setName(string $name): void
65 | {
66 | $this->name = $name;
67 | }
68 |
69 | /**
70 | * 返回路由 pattern
71 | *
72 | * @return string
73 | */
74 | public function getPattern(): string
75 | {
76 | return $this->pattern;
77 | }
78 |
79 | /**
80 | * 设置路由 pattern
81 | *
82 | * @param string $pattern
83 | */
84 | public function setPattern(string $pattern): void
85 | {
86 | $this->pattern = $pattern;
87 | }
88 |
89 | /**
90 | * 获取路由动作
91 | *
92 | * @return callable|string
93 | */
94 | public function getAction()
95 | {
96 | return $this->action;
97 | }
98 |
99 | /**
100 | * 设置路由动作
101 | *
102 | * @param callable|string $action
103 | */
104 | public function setAction($action): void
105 | {
106 | $this->action = $action;
107 | }
108 |
109 | /**
110 | * 获取路由限制的请求
111 | *
112 | * @return array
113 | */
114 | public function getMethods(): array
115 | {
116 | return $this->methods;
117 | }
118 |
119 | /**
120 | * 设置路由限制请求
121 | *
122 | * @param array $methods
123 | */
124 | public function setMethods(array $methods): void
125 | {
126 | $this->methods = $methods;
127 | }
128 |
129 | /**
130 | * 获取参数
131 | *
132 | * @return array
133 | */
134 | public function getArguments(): array
135 | {
136 | return $this->arguments;
137 | }
138 |
139 | /**
140 | * 设置参数
141 | *
142 | * @param array $arguments
143 | */
144 | public function setArguments(array $arguments): void
145 | {
146 | $this->arguments = $arguments;
147 | }
148 |
149 | /**
150 | * 设置单个参数
151 | *
152 | * @param string $name
153 | * @param mixed $value
154 | */
155 | public function setArgument($name, $value)
156 | {
157 | $this->arguments[$name] = $value;
158 | }
159 |
160 | /**
161 | * 获取单个参数
162 | *
163 | * @param string $name
164 | * @param mixed $default
165 | * @return mixed
166 | */
167 | public function getArgument($name, $default = null)
168 | {
169 | return $this->arguments[$name] ?? $default;
170 | }
171 | }
--------------------------------------------------------------------------------
/src/Jade/Twig/TwigExtension.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\Twig;
13 |
14 | use Jade\Routing\Router;
15 | use Psr\Http\Message\UriInterface;
16 |
17 | class TwigExtension extends \Twig\Extension\AbstractExtension
18 | {
19 | /**
20 | * @var Router
21 | */
22 | private $router;
23 | /**
24 | * @var string|UriInterface
25 | */
26 | private $uri;
27 |
28 | public function __construct($router, $uri)
29 | {
30 | $this->router = $router;
31 | $this->uri = $uri;
32 | }
33 |
34 | public function getName()
35 | {
36 | return 'slim';
37 | }
38 |
39 | public function getFunctions()
40 | {
41 | return [
42 | new \Twig\TwigFunction('path_for', array($this, 'pathFor')),
43 | new \Twig\TwigFunction('full_url_for', array($this, 'fullUrlFor')),
44 | new \Twig\TwigFunction('base_url', array($this, 'baseUrl')),
45 | new \Twig\TwigFunction('is_current_path', array($this, 'isCurrentPath')),
46 | new \Twig\TwigFunction('current_path', array($this, 'currentPath')),
47 | ];
48 | }
49 |
50 | public function pathFor($name, $data = [], $queryParams = [], $appName = 'default')
51 | {
52 | return $this->router->pathFor($name, $data, $queryParams);
53 | }
54 |
55 | /**
56 | * Similar to pathFor but returns a fully qualified URL
57 | *
58 | * @param string $name The name of the route
59 | * @param array $data Route placeholders
60 | * @param array $queryParams
61 | * @param string $appName
62 | * @return string fully qualified URL
63 | */
64 | public function fullUrlFor($name, $data = [], $queryParams = [], $appName = 'default')
65 | {
66 | $path = $this->pathFor($name, $data, $queryParams, $appName);
67 | /** @var Uri $uri */
68 | if (is_string($this->uri)) {
69 | $uri = Uri::createFromString($this->uri);
70 | } else {
71 | $uri = $this->uri;
72 | }
73 | $scheme = $uri->getScheme();
74 | $authority = $uri->getAuthority();
75 | $host = ($scheme ? $scheme . ':' : '')
76 | . ($authority ? '//' . $authority : '');
77 | return $host . $path;
78 | }
79 |
80 | public function baseUrl()
81 | {
82 | if (is_string($this->uri)) {
83 | return $this->uri;
84 | }
85 | if (method_exists($this->uri, 'getBaseUrl')) {
86 | return $this->uri->getBaseUrl();
87 | }
88 | }
89 |
90 | public function isCurrentPath($name, $data = [])
91 | {
92 | return $this->router->pathFor($name, $data) === $this->uri->getBasePath() . '/' . ltrim($this->uri->getPath(), '/');
93 | }
94 |
95 | /**
96 | * Returns current path on given URI.
97 | *
98 | * @param bool $withQueryString
99 | * @return string
100 | */
101 | public function currentPath($withQueryString = false)
102 | {
103 | if (is_string($this->uri)) {
104 | return $this->uri;
105 | }
106 | $path = $this->uri->getBasePath() . '/' . ltrim($this->uri->getPath(), '/');
107 | if ($withQueryString && '' !== $query = $this->uri->getQuery()) {
108 | $path .= '?' . $query;
109 | }
110 | return $path;
111 | }
112 |
113 | /**
114 | * Set the base url
115 | *
116 | * @param string|Slim\Http\Uri $baseUrl
117 | * @return void
118 | */
119 | public function setBaseUrl($baseUrl)
120 | {
121 | $this->uri = $baseUrl;
122 | }
123 | }
--------------------------------------------------------------------------------
/src/Jade/HttpKernel/HttpKernel.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\HttpKernel;
13 |
14 | use Psr\Http\Message\ResponseInterface;
15 | use Psr\Http\Message\ServerRequestInterface;
16 | use Psr\Http\Server\RequestHandlerInterface;
17 | use Jade\HttpKernel\Event\FilterControllerEvent;
18 | use Jade\HttpKernel\Event\FilterResponseEvent;
19 | use Jade\HttpKernel\Event\GetResponseEvent;
20 | use Jade\HttpKernel\Event\PostResponseEvent;
21 | use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22 | use Zend\HttpHandlerRunner\Emitter\EmitterInterface;
23 | use Zend\Stratigility\MiddlewarePipeInterface;
24 |
25 | final class HttpKernel implements RequestHandlerInterface
26 | {
27 | /**
28 | * @var EventDispatcherInterface
29 | */
30 | protected $eventDispatcher;
31 |
32 | /**
33 | * @var MiddlewarePipeInterface
34 | */
35 | protected $pipeline;
36 |
37 | /**
38 | * @var ControllerResolverInterface
39 | */
40 | protected $controllerResolver;
41 |
42 | /**
43 | * @var ArgumentResolverInterface
44 | */
45 | protected $argumentResolver;
46 |
47 | /**
48 | * @var EmitterInterface
49 | */
50 | protected $emitter;
51 |
52 | public function __construct(
53 | EventDispatcherInterface $eventDispatcher,
54 | ControllerResolverInterface $controllerResolver,
55 | ArgumentResolverInterface $argumentResolver,
56 | MiddlewarePipeInterface $pipeline,
57 | EmitterInterface $emitter
58 | ){
59 | $this->eventDispatcher = $eventDispatcher;
60 | $this->controllerResolver = $controllerResolver;
61 | $this->argumentResolver = $argumentResolver;
62 | $this->pipeline = $pipeline;
63 | $this->emitter = $emitter;
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | public function handle(ServerRequestInterface $request): ResponseInterface
70 | {
71 | // 1. 触发事件
72 | $event = new GetResponseEvent($this, $request);
73 | $this->eventDispatcher->dispatch($event, KernelEvents::REQUEST);
74 | // 2. 调度middleware
75 | return $this->pipeline->process($event->getRequest(), new CallableRequestHandler([$this, 'handleRequest']));
76 | }
77 |
78 | /**
79 | * Emit response.
80 | *
81 | * @param ServerRequestInterface $request
82 | * @param ResponseInterface $response
83 | */
84 | public function terminate(ServerRequestInterface $request, ResponseInterface $response)
85 | {
86 | $event = new PostResponseEvent($this, $request, $response);
87 | $this->eventDispatcher->dispatch($event, KernelEvents::TERMINATE);
88 |
89 | $this->emitter->emit($response);
90 | }
91 |
92 | /**
93 | * 处理请求
94 | *
95 | * @param ServerRequestInterface $request
96 | * @return ResponseInterface
97 | */
98 | public function handleRequest(ServerRequestInterface $request): ?ResponseInterface
99 | {
100 | // 1. middleware 到达结束
101 | $event = new GetResponseEvent($this, $request);
102 | $this->eventDispatcher->dispatch($event, KernelEvents::MIDDLEWARE);
103 | $request = $event->getRequest();
104 |
105 | if ($event->hasResponse()) {
106 | return $this->filterResponse($event->getResponse(), $request);
107 | }
108 |
109 | // 2. 获取控制器
110 | $controller = $this->controllerResolver->getController($request);
111 | $event = new FilterControllerEvent($this, $request, $controller);
112 | $this->eventDispatcher->dispatch($event, KernelEvents::CONTROLLER);
113 |
114 | $controller = $event->getController();
115 | $response = call_user_func_array($controller,
116 | $this->argumentResolver->getArguments($request, $controller)
117 | );
118 |
119 | // 3. 过滤响应体
120 | return $this->filterResponse($response, $request);
121 | }
122 |
123 | protected function filterResponse(ResponseInterface $response, ServerRequestInterface $request)
124 | {
125 | $event = new FilterResponseEvent($this, $request, $response);
126 | $this->eventDispatcher->dispatch($event, KernelEvents::RESPONSE);
127 | return $event->getResponse();
128 | }
129 | }
--------------------------------------------------------------------------------
/src/Jade/App.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade;
13 |
14 | use Jade\Routing\RouteCollector;
15 | use Psr\Container\ContainerInterface;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Psr\Http\Message\ServerRequestInterface;
18 | use Psr\Http\Server\MiddlewareInterface;
19 | use Psr\Http\Server\RequestHandlerInterface;
20 | use Jade\HttpKernel\HttpKernelProvider;
21 | use Jade\Routing\RouteCollection as RouteCollection;
22 | use Jade\Routing\Route;
23 | use Jade\Middleware\RouteMiddleware;
24 | use Zend\Diactoros\ServerRequestFactory;
25 |
26 | class App extends RouteCollector implements RequestHandlerInterface
27 | {
28 | /**
29 | * 是否已经初始化
30 | *
31 | * @var bool
32 | */
33 | protected $booted = false;
34 |
35 | /**
36 | * @var ContainerInterface
37 | */
38 | protected $container;
39 |
40 | /**
41 | * @var array
42 | */
43 | protected $providers;
44 |
45 | public function __construct(ContainerInterface $container = null)
46 | {
47 | if (null === $container) {
48 | $container = new Container();
49 | }
50 | // 注册核心服务
51 | $this->container = $container;
52 | $this->container['app'] = $this;
53 | $this->register(new CoreServiceProvider());
54 | parent::__construct();
55 | }
56 |
57 | /**
58 | * 初始化启动工作
59 | */
60 | public function boot()
61 | {
62 | if ($this->booted) {
63 | return;
64 | }
65 | $this->booted = true;
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | */
71 | public function handle(ServerRequestInterface $request): ResponseInterface
72 | {
73 | // 启动应用
74 | $this->boot();
75 | $this->register(new HttpKernelProvider());
76 | // 请求转交给 http kernel
77 | return $this->container->get('http_kernel')->handle($request);
78 | }
79 |
80 | /**
81 | * 代理http kernel
82 | *
83 | * @param ServerRequestInterface $request
84 | * @param ResponseInterface $response
85 | */
86 | public function terminate(ServerRequestInterface $request, ResponseInterface $response)
87 | {
88 | $this->container->get('http_kernel')->terminate($request, $response);
89 | }
90 |
91 | /**
92 | * 开启服务, 监听请求
93 | */
94 | public function serve()
95 | {
96 | // 1. 创建请求
97 | $request = ServerRequestFactory::fromGlobals();
98 | // 2. 处理请求
99 | $response = $this->handle($request);
100 | // 3. 输出响应
101 | $this->terminate($request, $response);
102 | }
103 |
104 | /**
105 | * 注册服务提供者
106 | *
107 | * @param object $provider
108 | * @param array $values
109 | */
110 | public function register($provider, array $values = [])
111 | {
112 | // 注册服务
113 | if ($provider instanceof ServiceProviderInterface) {
114 | $provider->register($this->container);
115 | }
116 | // 注册事件
117 | if ($provider instanceof EventProviderInterface) {
118 | $provider->subscribe($this->container->get('event_dispatcher'), $this->container);
119 | }
120 | $this->container->merge($values);
121 | $this->providers[] = $provider;
122 | }
123 |
124 | /**
125 | * 获取服务容器
126 | *
127 | * @return Container
128 | */
129 | public function getContainer(): Container
130 | {
131 | return $this->container;
132 | }
133 |
134 | /**
135 | * 添加一个 middleware
136 | *
137 | * @param string|MiddlewareInterface|callable $middleware
138 | * @param Route|null $route 绑定的路由
139 | */
140 | public function pipe($middleware, Route $route = null)
141 | {
142 | $middleware = $this->container->get('middleware_factory')->create($middleware);
143 | if (null !== $route) {
144 | $middleware = new RouteMiddleware($route, $middleware);
145 | }
146 | $this->container->get('middleware_pipeline')->pipe(
147 | $middleware
148 | );
149 | }
150 |
151 | /**
152 | * 返回全部的 provider
153 | *
154 | * @return array
155 | */
156 | public function getProviders()
157 | {
158 | return $this->providers;
159 | }
160 | }
--------------------------------------------------------------------------------
/src/Jade/Provider/DoctrineServiceProvider.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\Provider;
13 |
14 | use Doctrine\DBAL\DriverManager;
15 | use Doctrine\DBAL\Configuration;
16 | use Doctrine\Common\EventManager;
17 | use Jade\Container;
18 | use Jade\ContainerInterface;
19 | use Jade\ServiceProviderInterface;
20 | use Symfony\Bridge\Doctrine\Logger\DbalLogger;
21 |
22 | /**
23 | * Doctrine DBAL Provider.
24 | *
25 | * @author Fabien Potencier
26 | */
27 | class DoctrineServiceProvider implements ServiceProviderInterface
28 | {
29 | public function register(ContainerInterface $app)
30 | {
31 | $app['db.default_options'] = [
32 | 'driver' => 'pdo_mysql',
33 | 'dbname' => null,
34 | 'host' => 'localhost',
35 | 'user' => 'root',
36 | 'password' => null,
37 | ];
38 |
39 | $app['dbs.options.initializer'] = $app->protect(function () use ($app) {
40 | static $initialized = false;
41 |
42 | if ($initialized) {
43 | return;
44 | }
45 |
46 | $initialized = true;
47 |
48 | if (!isset($app['dbs.options'])) {
49 | $app['dbs.options'] = ['default' => isset($app['db.options']) ? $app['db.options'] : []];
50 | }
51 |
52 | $tmp = $app['dbs.options'];
53 | foreach ($tmp as $name => &$options) {
54 | $options = array_replace($app['db.default_options'], $options);
55 |
56 | if (!isset($app['dbs.default'])) {
57 | $app['dbs.default'] = $name;
58 | }
59 | }
60 | $app['dbs.options'] = $tmp;
61 | });
62 |
63 | $app['dbs'] = function ($app) {
64 | $app['dbs.options.initializer']();
65 |
66 | $dbs = new Container();
67 | foreach ($app['dbs.options'] as $name => $options) {
68 | if ($app['dbs.default'] === $name) {
69 | // we use shortcuts here in case the default has been overridden
70 | $config = $app['db.config'];
71 | $manager = $app['db.event_manager'];
72 | } else {
73 | $config = $app['dbs.config'][$name];
74 | $manager = $app['dbs.event_manager'][$name];
75 | }
76 |
77 | $dbs[$name] = function ($dbs) use ($options, $config, $manager) {
78 | return DriverManager::getConnection($options, $config, $manager);
79 | };
80 | }
81 |
82 | return $dbs;
83 | };
84 |
85 | $app['dbs.config'] = function ($app) {
86 | $app['dbs.options.initializer']();
87 |
88 | $configs = new Container();
89 | $addLogger = isset($app['logger']) && null !== $app['logger'] && class_exists('Symfony\Bridge\Doctrine\Logger\DbalLogger');
90 | foreach ($app['dbs.options'] as $name => $options) {
91 | $configs[$name] = new Configuration();
92 | if ($addLogger) {
93 | $configs[$name]->setSQLLogger(new DbalLogger($app['logger'], isset($app['stopwatch']) ? $app['stopwatch'] : null));
94 | }
95 | }
96 |
97 | return $configs;
98 | };
99 |
100 | $app['dbs.event_manager'] = function ($app) {
101 | $app['dbs.options.initializer']();
102 |
103 | $managers = new Container();
104 | foreach ($app['dbs.options'] as $name => $options) {
105 | $managers[$name] = new EventManager();
106 | }
107 |
108 | return $managers;
109 | };
110 |
111 | // shortcuts for the "first" DB
112 | $app['db'] = function ($app) {
113 | $dbs = $app['dbs'];
114 |
115 | return $dbs[$app['dbs.default']];
116 | };
117 |
118 | $app['db.config'] = function ($app) {
119 | $dbs = $app['dbs.config'];
120 |
121 | return $dbs[$app['dbs.default']];
122 | };
123 |
124 | $app['db.event_manager'] = function ($app) {
125 | $dbs = $app['dbs.event_manager'];
126 |
127 | return $dbs[$app['dbs.default']];
128 | };
129 | }
130 | }
--------------------------------------------------------------------------------
/src/Jade/Routing/Router.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\Routing;
13 |
14 | use FastRoute\Dispatcher;
15 | use FastRoute\RouteCollector;
16 | use FastRoute\RouteParser;
17 | use FastRoute\RouteParser\Std as StdParser;
18 | use Psr\Http\Message\ServerRequestInterface;
19 |
20 | class Router
21 | {
22 | /**
23 | * @var RouteCollection|Route[]
24 | */
25 | protected $routes;
26 |
27 | /**
28 | * @var
29 | */
30 | protected $routeParser;
31 |
32 | /**
33 | * @var Dispatcher
34 | */
35 | protected $dispatcher;
36 |
37 | /**
38 | * 缓存路由文件,默认不缓存
39 | *
40 | * @var string|False
41 | */
42 | protected $cacheFile = false;
43 |
44 | public function __construct(RouteCollection $routes, RouteParser $parser = null)
45 | {
46 | $this->routes = $routes;
47 | $this->routeParser = $parser ?: new StdParser();
48 | }
49 |
50 | /**
51 | * 设置路由集合
52 | *
53 | * @param RouteCollection $routes
54 | */
55 | public function setRoutes($routes): void
56 | {
57 | $this->routes = $routes;
58 | }
59 |
60 | /**
61 | * 返回路由集合
62 | *
63 | * @return RouteCollection
64 | */
65 | public function getRoutes(): RouteCollection
66 | {
67 | return $this->routes;
68 | }
69 |
70 | /**
71 | * 搜索路由
72 | *
73 | * @param string $name
74 | * @return Route
75 | */
76 | public function searchRoute($name)
77 | {
78 | return $this->routes->search($name);
79 | }
80 |
81 | /**
82 | * 调度请求
83 | *
84 | * @param ServerRequestInterface $request
85 | * @return array
86 | */
87 | public function dispatch(ServerRequestInterface $request)
88 | {
89 | $uri = '/' . ltrim($request->getUri()->getPath(), '/');
90 | return $this->getDispatcher()->dispatch(
91 | $request->getMethod(),
92 | $uri
93 | );
94 | }
95 |
96 | /**
97 | * 设置自定义路由调度器
98 | *
99 | * @param Dispatcher $dispatcher
100 | */
101 | public function setDispatcher(Dispatcher $dispatcher)
102 | {
103 | $this->dispatcher = $dispatcher;
104 | }
105 |
106 | /**
107 | * 获取路由调度器
108 | *
109 | * @return Dispatcher
110 | */
111 | public function getDispatcher()
112 | {
113 | if ($this->dispatcher) {
114 | return $this->dispatcher;
115 | }
116 | return $this->dispatcher = $this->createDispatcher();
117 | }
118 |
119 | /**
120 | * 设置路由缓存文件,为空表示禁用路由缓存
121 | *
122 | * @param string|false $cacheFile
123 | *
124 | * @return static
125 | *
126 | * @throws \InvalidArgumentException If cacheFile is not a string or not false
127 | * @throws \RuntimeException If cacheFile directory is not writable
128 | */
129 | public function setCacheFile($cacheFile)
130 | {
131 | if (!is_string($cacheFile) && $cacheFile !== false) {
132 | throw new \InvalidArgumentException('Router cache file must be a string or false');
133 | }
134 | if ($cacheFile && file_exists($cacheFile) && !is_readable($cacheFile)) {
135 | throw new \RuntimeException(
136 | sprintf('Router cache file `%s` is not readable', $cacheFile)
137 | );
138 | }
139 | if ($cacheFile && !file_exists($cacheFile) && !is_writable(dirname($cacheFile))) {
140 | throw new \RuntimeException(
141 | sprintf('Router cache file directory `%s` is not writable', dirname($cacheFile))
142 | );
143 | }
144 | $this->cacheFile = $cacheFile;
145 | return $this;
146 | }
147 |
148 | /**
149 | * 创建路由调度
150 | *
151 | * @return Dispatcher
152 | */
153 | protected function createDispatcher()
154 | {
155 | $routeDefinitionCallback = function (RouteCollector $r) {
156 | foreach ($this->routes as $route) {
157 | $r->addRoute($route->getMethods(), $route->getPattern(), $route->getName());
158 | }
159 | };
160 | if ($this->cacheFile) {
161 | $dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [
162 | 'routeParser' => $this->routeParser,
163 | 'cacheFile' => $this->cacheFile,
164 | ]);
165 | } else {
166 | $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [
167 | 'routeParser' => $this->routeParser,
168 | ]);
169 | }
170 | return $dispatcher;
171 | }
172 | }
--------------------------------------------------------------------------------
/src/Jade/Provider/DoctrineCacheServiceProvider.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\Provider;
13 |
14 | use Doctrine\Common\Cache\ArrayCache;
15 | use Doctrine\Common\Cache\RedisCache;
16 | use Doctrine\Common\Cache\ApcuCache;
17 | use Doctrine\Common\Cache\XcacheCache;
18 | use Doctrine\Common\Cache\MemcachedCache;
19 | use Doctrine\Common\Cache\FilesystemCache;
20 | use Doctrine\Common\Cache\MongoDBCache;
21 | use Jade\Container;
22 | use Jade\ContainerInterface;
23 | use Jade\ServiceProviderInterface;
24 |
25 | class DoctrineCacheServiceProvider implements ServiceProviderInterface
26 | {
27 | public function register(ContainerInterface $container)
28 | {
29 | $container['cache.default_options'] = [
30 | 'driver' => 'array',
31 | 'namespace' => null,
32 | ];
33 |
34 | $container['caches.options.initializer'] = $container->protect(function () use ($container) {
35 | static $initialized = false;
36 |
37 | if ($initialized) {
38 | return;
39 | }
40 |
41 | $initialized = true;
42 |
43 | if (!isset($container['caches.options'])) {
44 | $container['caches.options'] = [
45 | 'default' => $container['cache.options'] ?? [],
46 | ];
47 | }
48 |
49 | $container['caches.options'] = array_map(function ($options) use ($container) {
50 | return array_replace($container['cache.default_options'], is_array($options)
51 | ? $options
52 | : ['driver' => $options]
53 | );
54 | }, $container['caches.options']);
55 |
56 | if (!isset($container['caches.default'])) {
57 | $container['caches.default'] = array_keys(
58 | array_slice($container['caches.options'], 0, 1)
59 | )[0];
60 | }
61 | });
62 |
63 | $container['caches'] = function (ContainerInterface $container) {
64 | $container['caches.options.initializer']();
65 |
66 | $caches = new Container();
67 | foreach ($container['caches.options'] as $name => $options) {
68 | $caches[$name] = function () use ($container, $options) {
69 | $cache = $container['cache_factory']($options['driver'], $options);
70 |
71 | if (isset($options['namespace'])) {
72 | $cache->setNamespace($options['namespace']);
73 | }
74 |
75 | return $cache;
76 | };
77 | }
78 |
79 | return $caches;
80 | };
81 |
82 | $container['cache_factory.filesystem'] = $container->protect(function ($options) {
83 | if (empty($options['cache_dir'])
84 | || false === is_dir($options['cache_dir'])
85 | ) {
86 | throw new \InvalidArgumentException(
87 | 'You must specify "cache_dir" for Filesystem.'
88 | );
89 | }
90 |
91 | return new FilesystemCache($options['cache_dir']);
92 | });
93 |
94 | $container['cache_factory.array'] = $container->protect(function ($options) {
95 | return new ArrayCache();
96 | });
97 |
98 | $container['cache_factory.apcu'] = $container->protect(function ($options) {
99 | return new ApcuCache();
100 | });
101 |
102 | $container['cache_factory.mongodb'] = $container->protect(function ($options) {
103 | if (empty($options['server'])
104 | || empty($options['name'])
105 | || empty($options['collection'])
106 | ) {
107 | throw new \InvalidArgumentException(
108 | 'You must specify "server", "name" and "collection" for MongoDB.'
109 | );
110 | }
111 |
112 | $client = new \MongoClient($options['server']);
113 | $db = new \MongoDB($client, $options['name']);
114 | $collection = new \MongoCollection($db, $options['collection']);
115 |
116 | return new MongoDBCache($collection);
117 | });
118 |
119 | $container['cache_factory.redis'] = $container->protect(function ($options) {
120 | if (empty($options['host']) || empty($options['port'])) {
121 | throw new \InvalidArgumentException('You must specify "host" and "port" for Redis.');
122 | }
123 |
124 | $redis = new \Redis();
125 | $redis->connect($options['host'], $options['port']);
126 |
127 | if (isset($options['password'])) {
128 | $redis->auth($options['password']);
129 | }
130 |
131 | $cache = new RedisCache();
132 | $cache->setRedis($redis);
133 |
134 | return $cache;
135 | });
136 |
137 | $container['cache_factory.xcache'] = $container->protect(function ($options) {
138 |
139 | if (empty($options['host']) || empty($options['port'])) {
140 | throw new \InvalidArgumentException('You must specify "host" and "port" for Memcached.');
141 | }
142 |
143 | $memcached = new \Memcached();
144 | $memcached->addServer($options['host'], $options['port']);
145 |
146 | $cache = new MemcachedCache();
147 | $cache->setMemcached($memcached);
148 |
149 | return $cache;
150 | });
151 |
152 | $container['cache_factory.xcache'] = $container->protect(function ($options) {
153 | return new XcacheCache();
154 | });
155 |
156 | $container['cache_factory'] = $container->protect(function ($driver, $options) use ($container) {
157 | if (isset($container['cache_factory.' . $driver])) {
158 | return $container['cache_factory.' . $driver]($options);
159 | }
160 |
161 | throw new \RuntimeException();
162 | });
163 |
164 | // shortcuts for the "first" cache
165 | $container['cache'] = function (Container $container) {
166 | $caches = $container['caches'];
167 |
168 | return $caches[$container['caches.default']];
169 | };
170 | }
171 | }
--------------------------------------------------------------------------------
/src/Jade/Twig/Twig.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\Twig;
13 |
14 | use Psr\Http\Message\ResponseInterface;
15 |
16 | class Twig implements \ArrayAccess
17 | {
18 | /**
19 | * Twig loader
20 | *
21 | * @var \Twig\Loader\LoaderInterface
22 | */
23 | protected $loader;
24 | /**
25 | * Twig environment
26 | *
27 | * @var \Twig\Environment
28 | */
29 | protected $environment;
30 | /**
31 | * Default view variables
32 | *
33 | * @var array
34 | */
35 | protected $defaultVariables = [];
36 | /********************************************************************************
37 | * Constructors and service provider registration
38 | *******************************************************************************/
39 | /**
40 | * Create new Twig view
41 | *
42 | * @param string|array $path Path(s) to templates directory
43 | * @param array $settings Twig environment settings
44 | */
45 | public function __construct($path, $settings = [])
46 | {
47 | $this->loader = $this->createLoader(is_string($path) ? [$path] : $path);
48 | $this->environment = new \Twig\Environment($this->loader, $settings);
49 | }
50 | /********************************************************************************
51 | * Methods
52 | *******************************************************************************/
53 | /**
54 | * Proxy method to add an extension to the Twig environment
55 | *
56 | * @param \Twig\Extension\ExtensionInterface $extension A single extension instance or an array of instances
57 | */
58 | public function addExtension(\Twig\Extension\ExtensionInterface $extension)
59 | {
60 | $this->environment->addExtension($extension);
61 | }
62 | /**
63 | * Fetch rendered template
64 | *
65 | * @param string $template Template pathname relative to templates directory
66 | * @param array $data Associative array of template variables
67 | *
68 | * @throws \Twig\Error\LoaderError When the template cannot be found
69 | * @throws \Twig\Error\SyntaxError When an error occurred during compilation
70 | * @throws \Twig\Error\RuntimeError When an error occurred during rendering
71 | *
72 | * @return string
73 | */
74 | public function fetch($template, $data = [])
75 | {
76 | $data = array_merge($this->defaultVariables, $data);
77 | return $this->environment->render($template, $data);
78 | }
79 | /**
80 | * Fetch rendered block
81 | *
82 | * @param string $template Template pathname relative to templates directory
83 | * @param string $block Name of the block within the template
84 | * @param array $data Associative array of template variables
85 | *
86 | * @return string
87 | */
88 | public function fetchBlock($template, $block, $data = [])
89 | {
90 | $data = array_merge($this->defaultVariables, $data);
91 | return $this->environment->loadTemplate($template)->renderBlock($block, $data);
92 | }
93 | /**
94 | * Fetch rendered string
95 | *
96 | * @param string $string String
97 | * @param array $data Associative array of template variables
98 | *
99 | * @return string
100 | */
101 | public function fetchFromString($string ="", $data = [])
102 | {
103 | $data = array_merge($this->defaultVariables, $data);
104 | return $this->environment->createTemplate($string)->render($data);
105 | }
106 | /**
107 | * Output rendered template
108 | *
109 | * @param ResponseInterface $response
110 | * @param string $template Template pathname relative to templates directory
111 | * @param array $data Associative array of template variables
112 | * @return ResponseInterface
113 | */
114 | public function render(ResponseInterface $response, $template, $data = [])
115 | {
116 | $response->getBody()->write($this->fetch($template, $data));
117 | return $response;
118 | }
119 | /**
120 | * Create a loader with the given path
121 | *
122 | * @param array $paths
123 | * @return \Twig\Loader\FilesystemLoader
124 | */
125 | private function createLoader(array $paths)
126 | {
127 | $loader = new \Twig\Loader\FilesystemLoader();
128 | foreach ($paths as $namespace => $path) {
129 | if (is_string($namespace)) {
130 | $loader->setPaths($path, $namespace);
131 | } else {
132 | $loader->addPath($path);
133 | }
134 | }
135 | return $loader;
136 | }
137 | /********************************************************************************
138 | * Accessors
139 | *******************************************************************************/
140 | /**
141 | * Return Twig loader
142 | *
143 | * @return \Twig\Loader\LoaderInterface
144 | */
145 | public function getLoader()
146 | {
147 | return $this->loader;
148 | }
149 | /**
150 | * Return Twig environment
151 | *
152 | * @return \Twig\Environment
153 | */
154 | public function getEnvironment()
155 | {
156 | return $this->environment;
157 | }
158 | /********************************************************************************
159 | * ArrayAccess interface
160 | *******************************************************************************/
161 | /**
162 | * Does this collection have a given key?
163 | *
164 | * @param string $key The data key
165 | *
166 | * @return bool
167 | */
168 | public function offsetExists($key)
169 | {
170 | return array_key_exists($key, $this->defaultVariables);
171 | }
172 | /**
173 | * Get collection item for key
174 | *
175 | * @param string $key The data key
176 | *
177 | * @return mixed The key's value, or the default value
178 | */
179 | public function offsetGet($key)
180 | {
181 | return $this->defaultVariables[$key];
182 | }
183 | /**
184 | * Set collection item
185 | *
186 | * @param string $key The data key
187 | * @param mixed $value The data value
188 | */
189 | public function offsetSet($key, $value)
190 | {
191 | $this->defaultVariables[$key] = $value;
192 | }
193 | /**
194 | * Remove item from collection
195 | *
196 | * @param string $key The data key
197 | */
198 | public function offsetUnset($key)
199 | {
200 | unset($this->defaultVariables[$key]);
201 | }
202 | /********************************************************************************
203 | * Countable interface
204 | *******************************************************************************/
205 | /**
206 | * Get number of items in collection
207 | *
208 | * @return int
209 | */
210 | public function count()
211 | {
212 | return count($this->defaultVariables);
213 | }
214 | /********************************************************************************
215 | * IteratorAggregate interface
216 | *******************************************************************************/
217 | /**
218 | * Get collection iterator
219 | *
220 | * @return \ArrayIterator
221 | */
222 | public function getIterator()
223 | {
224 | return new \ArrayIterator($this->defaultVariables);
225 | }
226 | }
--------------------------------------------------------------------------------
/src/Jade/Provider/DoctrineOrmServiceProvider.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Jade\Provider;
13 |
14 | use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain;
15 | use Doctrine\ORM\Configuration;
16 | use Doctrine\ORM\EntityManager;
17 | use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver;
18 | use Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver;
19 | use Doctrine\ORM\Mapping\Driver\XmlDriver;
20 | use Doctrine\ORM\Mapping\Driver\YamlDriver;
21 | use Doctrine\ORM\Tools\Console\ConsoleRunner;
22 | use Doctrine\ORM\Tools\ResolveTargetEntityListener;
23 | use Jade\CommandProviderInterface;
24 | use Jade\Console\Application;
25 | use Jade\Container;
26 | use Jade\ContainerInterface;
27 | use Jade\ServiceProviderInterface;
28 |
29 | /**
30 | * Doctrine ORM Pimple Service Provider.
31 | *
32 | * @author Beau Simensen
33 | */
34 | class DoctrineOrmServiceProvider implements ServiceProviderInterface, CommandProviderInterface
35 | {
36 | public function provide(Application $app, ContainerInterface $container)
37 | {
38 | ConsoleRunner::addCommands($app);
39 | $helperSet = $app->getHelperSet();
40 | $doctrineHelperSet = ConsoleRunner::createHelperSet($container['orm']);
41 | foreach ($doctrineHelperSet as $alias =>$helper) {
42 | $helperSet->set($helper, $alias);
43 | }
44 | $app->setHelperSet($helperSet);
45 | }
46 |
47 | public function register(ContainerInterface $container)
48 | {
49 | if (!isset($container['dbs'])) {
50 | throw new \LogicException(
51 | 'You must register the DoctrineServiceProvider to use the DoctrineOrmServiceProvider.'
52 | );
53 | }
54 |
55 | if (!isset($container['caches'])) {
56 | throw new \LogicException(
57 | 'You must register the DoctrineCacheServiceProvider to use the DoctrineOrmServiceProvider.'
58 | );
59 | }
60 |
61 | // 初始值
62 | $container->add($this->getOrmDefaults());
63 |
64 | $container['ems.options.initializer'] = $container->protect(function () use ($container) {
65 | static $initialized = false;
66 |
67 | if ($initialized) {
68 | return;
69 | }
70 |
71 | $initialized = true;
72 |
73 | if (!isset($container['ems.options'])) {
74 | $container['ems.options'] = [
75 | 'default' => $container['orm.options'] ?? [],
76 | ];
77 | }
78 |
79 | $container['ems.options'] = array_map(function ($options) use ($container) {
80 | return array_replace($container['orm.default_options'], $options);
81 | }, $container['ems.options']);
82 |
83 | if (!isset($container['ems.default'])) {
84 | $container['ems.default'] = array_keys(
85 | array_slice($container['ems.options'], 0, 1)
86 | )[0];
87 | }
88 | });
89 |
90 | $container['ems'] = function (Container $container) {
91 | $container['ems.options.initializer']();
92 |
93 | $ems = new Container();
94 | foreach ($container['ems.options'] as $name => $options) {
95 | $config = $container['ems.default'] === $name
96 | ? $container['orm.config']
97 | : $container['ems.config'][$name];
98 |
99 | $connection = $container['dbs'][$options['connection']];
100 | $manager = $container['dbs.event_manager'][$options['connection']];
101 |
102 | if ($targetEntities = $options['resolve_target_entities'] ?? []) {
103 | $manager->addEventSubscriber(
104 | $container['orm.resolve_target_entity']($targetEntities)
105 | );
106 | }
107 |
108 | $ems[$name] = function () use ($connection, $config, $manager) {
109 | return EntityManager::create($connection, $config, $manager);
110 | };
111 | }
112 |
113 | return $ems;
114 | };
115 |
116 | $container['ems.config'] = function (Container $container) {
117 | $container['ems.options.initializer']();
118 |
119 | $configs = new Container();
120 | foreach ($container['ems.options'] as $name => $options) {
121 | $config = new Configuration();
122 | $config->setProxyDir($container['orm.proxy_dir']);
123 | $config->setProxyNamespace($container['orm.proxy_namespace']);
124 | $config->setAutoGenerateProxyClasses($container['orm.auto_generate_proxy_classes']);
125 | $config->setCustomStringFunctions($container['orm.custom_functions_string']);
126 | $config->setCustomNumericFunctions($container['orm.custom_functions_numeric']);
127 | $config->setCustomDatetimeFunctions($container['orm.custom_functions_datetime']);
128 | $config->setMetadataCacheImpl($container['orm.cache.factory']('metadata', $options));
129 | $config->setQueryCacheImpl($container['orm.cache.factory']('query', $options));
130 | $config->setResultCacheImpl($container['orm.cache.factory']('result', $options));
131 | $config->setMetadataDriverImpl($container['orm.mapping.chain']($config, $options['mappings']));
132 |
133 | $configs[$name] = $config;
134 | }
135 |
136 | return $configs;
137 | };
138 |
139 | $container['orm.cache.factory'] = $container->protect(function ($type, $options) use ($container) {
140 | $type = $type . '_cache_driver';
141 |
142 | $options[$type] = $options[$type] ?? 'array';
143 |
144 | if (!is_array($options[$type])) {
145 | $options[$type] = [
146 | 'driver' => $options[$type],
147 | ];
148 | }
149 |
150 | $driver = $options[$type]['driver'];
151 | $namespace = $options[$type]['namespace'] ?? null;
152 |
153 | $cache = $container['cache_factory']($driver, $options);
154 | $cache->setNamespace($namespace);
155 |
156 | return $cache;
157 | });
158 |
159 | $container['orm.mapping.chain'] = $container->protect(function (Configuration $config, array $mappings) {
160 | $chain = new MappingDriverChain();
161 |
162 | foreach ($mappings as $mapping) {
163 | if (!is_array($mapping)) {
164 | throw new \InvalidArgumentException();
165 | }
166 |
167 | $path = $mapping['path'];
168 | $namespace = $mapping['namespace'];
169 |
170 | switch ($mapping['type']) {
171 | case 'annotation':
172 | $annotationDriver = $config->newDefaultAnnotationDriver(
173 | $path,
174 | $mapping['use_simple_annotation_reader'] ?? true
175 | );
176 | $chain->addDriver($annotationDriver, $namespace);
177 | break;
178 | case 'yml':
179 | $chain->addDriver(new YamlDriver($path), $namespace);
180 | break;
181 | case 'simple_yml':
182 | $driver = new SimplifiedYamlDriver([$path => $namespace]);
183 | $chain->addDriver($driver, $namespace);
184 | break;
185 | case 'xml':
186 | $chain->addDriver(new XmlDriver($path), $namespace);
187 | break;
188 | case 'simple_xml':
189 | $driver = new SimplifiedXmlDriver([$path => $namespace]);
190 | $chain->addDriver($driver, $namespace);
191 | break;
192 | default:
193 | throw new \InvalidArgumentException();
194 | break;
195 | }
196 | }
197 |
198 | return $chain;
199 | });
200 |
201 | $container['orm.resolve_target_entity'] = $container->protect(function (array $targetEntities) {
202 | $rtel = new ResolveTargetEntityListener();
203 |
204 | foreach ($targetEntities as $originalEntity => $newEntity) {
205 | $rtel->addResolveTargetEntity($originalEntity, $newEntity, []);
206 | }
207 |
208 | return $rtel;
209 | });
210 |
211 | // shortcuts for the "first" ORM
212 | $container['orm'] = function (Container $container) {
213 | $ems = $container['ems'];
214 |
215 | return $ems[$container['ems.default']];
216 | };
217 |
218 | $container['orm.config'] = function (Container $container) {
219 | $ems = $container['ems.config'];
220 |
221 | return $ems[$container['ems.default']];
222 | };
223 | }
224 |
225 | protected function getOrmDefaults()
226 | {
227 | return [
228 | 'orm.proxy_dir' => null,
229 | 'orm.proxy_namespace' => 'Proxy',
230 | 'orm.auto_generate_proxy_classes' => true,
231 | 'orm.custom_functions_string' => [],
232 | 'orm.custom_functions_numeric' => [],
233 | 'orm.custom_functions_datetime' => [],
234 | 'orm.default_options' => [
235 | 'connection' => 'default',
236 | 'mappings' => [],
237 | ]
238 | ];
239 | }
240 | }
--------------------------------------------------------------------------------