├── .gitignore ├── app ├── src │ ├── DemoModule │ │ ├── config │ │ │ ├── services.php │ │ │ └── routing.php │ │ ├── DemoModule.php │ │ ├── Controller.php │ │ └── view │ │ │ └── index.php │ └── FrameworkModule │ │ ├── config │ │ ├── routing.php │ │ └── services.php │ │ ├── FrameworkModule.php │ │ ├── BaseController.php │ │ ├── BaseModule.php │ │ └── App.php ├── web │ └── style.css └── bootstrap.php ├── tests ├── Fixture │ ├── ChildService.php │ ├── SimpleServiceInterface.php │ ├── ComplexService.php │ ├── SimpleService.php │ ├── ParentService.php │ ├── Manager.php │ ├── TestController.php │ └── ServiceWithCall.php ├── DispatcherTest.php ├── RouterTest.php └── ContainerTest.php ├── src ├── Http │ ├── ResponseInterface.php │ ├── Response.php │ ├── RequestInterface.php │ └── Request.php ├── Dispatching │ ├── DispatcherInterface.php │ ├── RouterInterface.php │ ├── Dispatcher.php │ └── Router.php └── IoC │ ├── TaggedReference.php │ ├── Reference.php │ ├── ContainerInterface.php │ └── Container.php ├── phpunit.xml.dist ├── composer.json ├── LICENSE ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /composer.phar 3 | -------------------------------------------------------------------------------- /app/src/DemoModule/config/services.php: -------------------------------------------------------------------------------- 1 | attach('home', '/', 'DemoModule\Controller::index') 5 | ; 6 | -------------------------------------------------------------------------------- /app/src/FrameworkModule/FrameworkModule.php: -------------------------------------------------------------------------------- 1 | render('DemoModule:index'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Fixture/ParentService.php: -------------------------------------------------------------------------------- 1 | foo = $foo; 12 | } 13 | 14 | public function getFoo() 15 | { 16 | return $this->foo; 17 | } 18 | } -------------------------------------------------------------------------------- /tests/Fixture/Manager.php: -------------------------------------------------------------------------------- 1 | services[] = $service; 12 | } 13 | 14 | public function getServices() 15 | { 16 | return $this->services; 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/DemoModule/view/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fucking Small Framework 5 | 6 | 7 | 8 |
9 |

Welcome to "Fucking Small" Framework

10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Http/ResponseInterface.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | ./tests 9 | 10 | 11 | 12 | 13 | ./vendor/ 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Dispatching/DispatcherInterface.php: -------------------------------------------------------------------------------- 1 | tag = $tag; 15 | } 16 | 17 | /** 18 | * @return string 19 | */ 20 | public function getTag() 21 | { 22 | return $this->tag; 23 | } 24 | } -------------------------------------------------------------------------------- /src/IoC/Reference.php: -------------------------------------------------------------------------------- 1 | serviceIdentifier = $serviceIdentifier; 15 | } 16 | 17 | /** 18 | * @return string 19 | */ 20 | public function getServiceIdentifier() 21 | { 22 | return $this->serviceIdentifier; 23 | } 24 | } -------------------------------------------------------------------------------- /app/bootstrap.php: -------------------------------------------------------------------------------- 1 | bootstrap([ 18 | new \FrameworkModule\FrameworkModule(), 19 | new \DemoModule\DemoModule() 20 | ]); 21 | 22 | $app->run(); 23 | } 24 | -------------------------------------------------------------------------------- /src/IoC/ContainerInterface.php: -------------------------------------------------------------------------------- 1 | content; 18 | } 19 | 20 | /** 21 | * @param string $content 22 | */ 23 | public function setContent($content) 24 | { 25 | $this->content = $content; 26 | } 27 | 28 | /** 29 | * 30 | */ 31 | public function send() 32 | { 33 | echo $this->content; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Dispatching/RouterInterface.php: -------------------------------------------------------------------------------- 1 | getSomeText(); 15 | } 16 | 17 | public function payloadAction(SimpleService $ss, $id) 18 | { 19 | return $ss->getSomeText() . $id; 20 | } 21 | 22 | public function reversePayloadAction($id, SimpleService $ss) 23 | { 24 | return $ss->getSomeText() . $id; 25 | } 26 | 27 | public function defValueAction($foo = true) 28 | { 29 | return $foo; 30 | } 31 | } -------------------------------------------------------------------------------- /tests/Fixture/ServiceWithCall.php: -------------------------------------------------------------------------------- 1 | something = $something; 13 | } 14 | 15 | public function getSomething() 16 | { 17 | return $this->something; 18 | } 19 | 20 | public function setSomethingElse($a, $b) 21 | { 22 | $this->somethingElse = $a . $b; 23 | } 24 | 25 | public function getSomethingElse() 26 | { 27 | return $this->somethingElse; 28 | } 29 | 30 | public function set() 31 | { 32 | $this->something = 'has been set'; 33 | } 34 | 35 | public function get() 36 | { 37 | return $this->something; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Http/RequestInterface.php: -------------------------------------------------------------------------------- 1 | getParameters() as $param) { 28 | if (array_key_exists($param->name, $payload)) { 29 | $args[] = $payload[$param->name]; 30 | } elseif ($param->getClass() && $object = $container->resolve($param->getClass()->name)) { 31 | $args[] = $object; 32 | } elseif ($param->isDefaultValueAvailable()) { 33 | $args[] = $param->getDefaultValue(); 34 | } 35 | } 36 | 37 | if ($controller = $container->resolve($controllerClass)) { 38 | return $reflection->invokeArgs( 39 | $controller, 40 | $args 41 | ); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/FrameworkModule/BaseController.php: -------------------------------------------------------------------------------- 1 | appPath = $appPath; 27 | $this->response = $response; 28 | } 29 | 30 | /** 31 | * @param $name 32 | * @param array $vars 33 | * 34 | * @return Response 35 | */ 36 | protected function render($name, array $vars = []) 37 | { 38 | list($module, $name) = explode(':', $name); 39 | 40 | /** 41 | * TODO: The module path needs to be resolved some other way. Relying on the appPath is shite. 42 | */ 43 | $path = $this->appPath . '/src/' . $module . '/view/' . $name . '.php'; 44 | 45 | if (file_exists($path)) { 46 | extract($vars); 47 | ob_start(); 48 | include $path; 49 | $this->response->setContent(ob_get_clean()); 50 | 51 | return $this->response; 52 | } else { 53 | throw new \RuntimeException("view '$path' not found"); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/FrameworkModule/BaseModule.php: -------------------------------------------------------------------------------- 1 | getPath() . '/config/services.php')) { 26 | // ContainerInterface is used within services.php 27 | require_once $this->getPath() . '/config/services.php'; 28 | } 29 | } 30 | 31 | /** 32 | * @param ContainerInterface $container 33 | */ 34 | public function registerRoutes(ContainerInterface $container) 35 | { 36 | if (file_exists($this->getPath() . '/config/routing.php')) { 37 | // RouterInterface is used within routing.php 38 | $router = $container->resolve(RouterInterface::class); 39 | require_once $this->getPath() . '/config/routing.php'; 40 | } 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function getPath() 47 | { 48 | if (null === $this->path) { 49 | $reflected = new \ReflectionObject($this); 50 | $this->path = dirname($reflected->getFileName()); 51 | } 52 | 53 | return $this->path; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getName() 60 | { 61 | if (null !== $this->name) { 62 | return $this->name; 63 | } 64 | 65 | $name = get_class($this); 66 | $pos = strrpos($name, '\\'); 67 | 68 | return $this->name = false === $pos ? $name : substr($name, $pos + 1); 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/FrameworkModule/config/services.php: -------------------------------------------------------------------------------- 1 | alias(RequestInterface::class, Request::class, function () { 17 | /** 18 | * We want the Request object to be a singleton, so use a static variable to store it. 19 | */ 20 | static $request; 21 | 22 | if (null === $request) { 23 | $request = Request::createFromGlobals(); 24 | } 25 | 26 | return $request; 27 | }) 28 | /** 29 | * Alias the ResponseInterface to the Response concrete implementation 30 | */ 31 | ->alias(ResponseInterface::class, Response::class, function () { 32 | /** 33 | * We want the Response object to be a singleton, so use a static variable to store it. 34 | */ 35 | static $response; 36 | 37 | if (null === $response) { 38 | $response = new Response(); 39 | } 40 | 41 | return $response; 42 | }) 43 | /** 44 | * Alias the RouterInterface to the Router concrete implementation 45 | */ 46 | ->alias(RouterInterface::class, Router::class, function () { 47 | /** 48 | * We want the Router object to be a singleton, so use a static variable to store it. 49 | */ 50 | static $router; 51 | 52 | if (null === $router) { 53 | $router = new Router(); 54 | } 55 | 56 | return $router; 57 | }) 58 | ->alias(DispatcherInterface::class, Dispatcher::class); -------------------------------------------------------------------------------- /app/src/FrameworkModule/App.php: -------------------------------------------------------------------------------- 1 | container = $container; 35 | $this->container->template(BaseController::class, ['appPath' => $appPath]); 36 | } 37 | 38 | /** 39 | * 40 | */ 41 | public function run() 42 | { 43 | $router = $this->container->resolve(RouterInterface::class); 44 | $request = $this->container->resolve(RequestInterface::class); 45 | $dispatcher = $this->container->resolve(DispatcherInterface::class); 46 | 47 | $response = null; 48 | if ($payload = $router->resolve($request)) { 49 | $response = $dispatcher->dispatch($this->container, $payload); 50 | 51 | if (null === $response || !$response instanceof Response) { 52 | throw new \RuntimeException('Controllers must return a ResponseInterface implementation'); 53 | } 54 | 55 | $response->send(); 56 | } 57 | } 58 | 59 | /** 60 | * @return ContainerInterface 61 | */ 62 | public function getContainer() 63 | { 64 | return $this->container; 65 | } 66 | 67 | /** 68 | * @param array $modules 69 | */ 70 | public function bootstrap(array $modules) 71 | { 72 | foreach ($modules as $module) { 73 | $this->modules[$module->getName()] = $module; 74 | $module->registerServices($this->container); 75 | $module->registerRoutes($this->container); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Http/Request.php: -------------------------------------------------------------------------------- 1 | uri = $uri; 34 | $this->method = $method; 35 | $this->parameters = $parameters; 36 | } 37 | 38 | /** 39 | * @return static 40 | */ 41 | public static function createFromGlobals() 42 | { 43 | return new static( 44 | $_SERVER['REQUEST_URI'], 45 | $_SERVER['REQUEST_METHOD'], 46 | $_REQUEST 47 | ); 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getUri() 54 | { 55 | return $this->uri; 56 | } 57 | 58 | /** 59 | * @param string $uri 60 | */ 61 | public function setUri($uri) 62 | { 63 | $this->uri = $uri; 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getMethod() 70 | { 71 | return $this->method; 72 | } 73 | 74 | /** 75 | * @param string $method 76 | */ 77 | public function setMethod($method) 78 | { 79 | $this->method = $method; 80 | } 81 | 82 | /** 83 | * @param $key 84 | * @param null $default 85 | * 86 | * @return mixed 87 | */ 88 | public function getParameter($key, $default = null) 89 | { 90 | if (array_key_exists($key, $this->parameters)) { 91 | return $this->parameters[$key]; 92 | } 93 | 94 | return $default; 95 | } 96 | 97 | /** 98 | * @return array 99 | */ 100 | public function getParameters() 101 | { 102 | return $this->parameters; 103 | } 104 | 105 | /** 106 | * @param array $parameters 107 | */ 108 | public function setParameters($parameters) 109 | { 110 | $this->parameters = $parameters; 111 | } 112 | } -------------------------------------------------------------------------------- /tests/DispatcherTest.php: -------------------------------------------------------------------------------- 1 | TestController::class, 15 | '_method' => 'fooAction' 16 | ]; 17 | 18 | $dispatcher = new Dispatcher(); 19 | $result = $dispatcher->dispatch(new Container(), $payload); 20 | 21 | $this->assertEquals('this is foo', $result); 22 | } 23 | 24 | public function testCanDispatchRequestToMethodsWithDependencies() 25 | { 26 | $payload = [ 27 | '_controller' => TestController::class, 28 | '_method' => 'barAction' 29 | ]; 30 | 31 | $dispatcher = new Dispatcher(); 32 | $result = $dispatcher->dispatch(new Container(), $payload); 33 | 34 | $this->assertEquals('this is some text', $result); 35 | } 36 | 37 | public function testCanDispatchRequestToMethodsWithDependenciesAndPayload() 38 | { 39 | $payload = [ 40 | '_controller' => TestController::class, 41 | '_method' => 'payloadAction', 42 | 'id' => 88 43 | ]; 44 | 45 | $dispatcher = new Dispatcher(); 46 | $result = $dispatcher->dispatch(new Container(), $payload); 47 | 48 | $this->assertEquals('this is some text88', $result); 49 | } 50 | 51 | public function testPayloadOrderNotImportant() 52 | { 53 | $payload = [ 54 | '_controller' => TestController::class, 55 | '_method' => 'reversePayloadAction', 56 | 'id' => 88 57 | ]; 58 | 59 | $dispatcher = new Dispatcher(); 60 | $result = $dispatcher->dispatch(new Container(), $payload); 61 | 62 | $this->assertEquals('this is some text88', $result); 63 | } 64 | 65 | public function testDefaultValueWorks() 66 | { 67 | $payload = [ 68 | '_controller' => TestController::class, 69 | '_method' => 'defValueAction', 70 | ]; 71 | 72 | $dispatcher = new Dispatcher(); 73 | $result = $dispatcher->dispatch(new Container(), $payload); 74 | 75 | $this->assertTrue($result); 76 | } 77 | 78 | public function testDefaultValueOverwritten() 79 | { 80 | $payload = [ 81 | '_controller' => TestController::class, 82 | '_method' => 'defValueAction', 83 | 'foo' => false 84 | ]; 85 | 86 | $dispatcher = new Dispatcher(); 87 | $result = $dispatcher->dispatch(new Container(), $payload); 88 | 89 | $this->assertFalse($result); 90 | } 91 | } -------------------------------------------------------------------------------- /tests/RouterTest.php: -------------------------------------------------------------------------------- 1 | prophesize(Request::class); 13 | $request->getUri() 14 | ->shouldBeCalledTimes(3) 15 | ->willReturn('/bob'); 16 | 17 | $router = new Router(); 18 | 19 | $router->attach('foo', '/foo', 'SimpleController::indexAction'); 20 | $router->attach('bar', '/bar', 'SimpleController::indexAction'); 21 | $router->attach('bob', '/bob', 'SimpleController::indexAction'); 22 | 23 | $payload = $router->resolve($request->reveal()); 24 | 25 | $this->assertEquals('SimpleController', $payload['_controller']); 26 | $this->assertEquals('indexAction', $payload['_method']); 27 | } 28 | 29 | public function testRouteMatchingWithTokens() 30 | { 31 | $request = $this->prophesize(Request::class); 32 | $request->getUri() 33 | ->shouldBeCalledTimes(1) 34 | ->willReturn('/foo/14'); 35 | 36 | $router = new Router(); 37 | 38 | $router->attach('foo', '/foo/{id}', 'SimpleController::indexAction'); 39 | 40 | $payload = $router->resolve($request->reveal()); 41 | 42 | $this->assertEquals('SimpleController', $payload['_controller']); 43 | $this->assertEquals('indexAction', $payload['_method']); 44 | $this->assertEquals(14, $payload['id']); 45 | } 46 | 47 | public function testRouteMatchingWithOptional() 48 | { 49 | $request = $this->prophesize(Request::class); 50 | $request->getUri() 51 | ->shouldBeCalledTimes(1) 52 | ->willReturn('/foo'); 53 | 54 | $router = new Router(); 55 | 56 | $router->attach('foo', '/foo/{id}', 'SimpleController::indexAction', ['defaults' => ['id' => 14]]); 57 | 58 | $payload = $router->resolve($request->reveal()); 59 | 60 | $this->assertEquals('SimpleController', $payload['_controller']); 61 | $this->assertEquals('indexAction', $payload['_method']); 62 | $this->assertEquals(14, $payload['id']); 63 | } 64 | 65 | public function testRouteMatchingIntFilter() 66 | { 67 | $request = $this->prophesize(Request::class); 68 | $request->getUri() 69 | ->shouldBeCalledTimes(1) 70 | ->willReturn('/foo/14'); 71 | 72 | $router = new Router(); 73 | 74 | $router->attach('foo', '/foo/{id}', 'SimpleController::indexAction', ['filters' => ['id' => '{int}']]); 75 | 76 | $payload = $router->resolve($request->reveal()); 77 | 78 | $this->assertEquals('SimpleController', $payload['_controller']); 79 | $this->assertEquals('indexAction', $payload['_method']); 80 | $this->assertEquals(14, $payload['id']); 81 | } 82 | 83 | public function testRouteFailsToMatchIntFilter() 84 | { 85 | $request = $this->prophesize(Request::class); 86 | $request->getUri() 87 | ->shouldBeCalledTimes(2) 88 | ->willReturn('/foo/foo'); 89 | 90 | $router = new Router(); 91 | 92 | $router->attach('foo_int', '/foo/{id}', 'SimpleController::indexAction', ['filters' => ['id' => '{int}']]); 93 | $router->attach('foo_string', '/foo/{id}', 'SimpleController::indexAction'); 94 | 95 | $payload = $router->resolve($request->reveal()); 96 | 97 | $this->assertEquals('SimpleController', $payload['_controller']); 98 | $this->assertEquals('indexAction', $payload['_method']); 99 | $this->assertEquals('foo_string', $payload['_route']); 100 | $this->assertEquals('foo', $payload['id']); 101 | } 102 | 103 | public function testCanGenerateSimpleUrl() 104 | { 105 | $router = new Router(); 106 | 107 | $router->attach('foo', '/foo/bar', 'SimpleController::indexAction'); 108 | $url = $router->gentUrl('foo'); 109 | 110 | $this->assertEquals('/foo/bar', $url); 111 | } 112 | 113 | public function testCanGenerateUrlWithSimpleToken() 114 | { 115 | $router = new Router(); 116 | 117 | $router->attach('foo', '/foo/{id}', 'SimpleController::indexAction'); 118 | $url = $router->gentUrl('foo', ['id' => 14]); 119 | 120 | $this->assertEquals('/foo/14', $url); 121 | } 122 | 123 | public function testCanGenerateUrlWithDefaultToken() 124 | { 125 | $router = new Router(); 126 | 127 | $router->attach('foo', '/foo/{id}/{some_default}', 'SimpleController::indexAction', [ 128 | 'defaults' => ['some_default' => 'some-value']] 129 | ); 130 | $url = $router->gentUrl('foo', ['id' => 14]); 131 | 132 | $this->assertEquals('/foo/14/some-value', $url); 133 | } 134 | 135 | /** 136 | * @expectedException RuntimeException 137 | */ 138 | public function testThrowsWhenNotEnoughParamsPassedToUrl() 139 | { 140 | $router = new Router(); 141 | 142 | $router->attach('foo', '/foo/{id}', 'SimpleController::indexAction'); 143 | $router->gentUrl('foo'); 144 | } 145 | 146 | public function testCanMountSimple() 147 | { 148 | $request = $this->prophesize(Request::class); 149 | $request->getUri() 150 | ->shouldBeCalledTimes(3) 151 | ->willReturn('/foo/car'); 152 | 153 | $router = new Router(); 154 | 155 | $router->mount('/foo', [ 156 | ['a', '/aar', 'SimpleController::aarAction'], 157 | ['b', '/bar', 'SimpleController::barAction'], 158 | ['c', '/car', 'SimpleController::carAction'], 159 | ]); 160 | 161 | $payload = $router->resolve($request->reveal()); 162 | 163 | $this->assertEquals('SimpleController', $payload['_controller']); 164 | $this->assertEquals('carAction', $payload['_method']); 165 | } 166 | 167 | public function testCanMountMoreComplex() 168 | { 169 | $request = $this->prophesize(Request::class); 170 | $request->getUri() 171 | ->shouldBeCalledTimes(2) 172 | ->willReturn('/foo/bar/bob'); 173 | 174 | $router = new Router(); 175 | 176 | $router->mount('/foo', [ 177 | ['a', '/bar/{id}', 'SimpleController::barAction', ['filters' => ['id' => '{int}']]], 178 | ['b', '/bar/{id}', 'SimpleController::barAction'], 179 | ]); 180 | 181 | $payload = $router->resolve($request->reveal()); 182 | 183 | $this->assertEquals('SimpleController', $payload['_controller']); 184 | $this->assertEquals('barAction', $payload['_method']); 185 | $this->assertEquals('b', $payload['_route']); 186 | } 187 | } -------------------------------------------------------------------------------- /tests/ContainerTest.php: -------------------------------------------------------------------------------- 1 | attach('some_service', function() { 23 | $service = new \StdClass(); 24 | $service->foo = 'bar'; 25 | 26 | return $service; 27 | }); 28 | 29 | $service = $container->resolve('some_service'); 30 | 31 | $this->assertEquals('bar', $service->foo); 32 | } 33 | 34 | public function testCanAutoResolve() 35 | { 36 | $container = new Container(); 37 | 38 | $service = $container->resolve(SimpleService::class); 39 | 40 | $this->assertInstanceOf(SimpleService::class, $service); 41 | } 42 | 43 | public function testCanAutoResolveWithDependencies() 44 | { 45 | $container = new Container(); 46 | 47 | $service = $container->resolve(ComplexService::class); 48 | 49 | $this->assertInstanceOf(ComplexService::class, $service); 50 | } 51 | 52 | public function testCanStoreASingleton() 53 | { 54 | $container = new Container(); 55 | 56 | $container->attach('some_singleton', function() { 57 | static $object; 58 | 59 | if (null === $object) { 60 | $object = new \StdClass(); 61 | } 62 | 63 | return $object; 64 | }); 65 | 66 | $s1 = $container->resolve('some_singleton'); 67 | $s2 = $container->resolve('some_singleton'); 68 | 69 | $this->assertSame($s1, $s2); 70 | } 71 | 72 | public function testCanAutoResolveAlias() 73 | { 74 | $container = new Container(); 75 | 76 | $container->alias('foo', SimpleService::class); 77 | $service = $container->resolve('foo'); 78 | 79 | $this->assertInstanceOf(SimpleService::class, $service); 80 | } 81 | 82 | public function testCanAutoResolveInterfaceAlias() 83 | { 84 | $container = new Container(); 85 | 86 | $container->alias(simpleServiceInterface::class, SimpleService::class); 87 | $service = $container->resolve(SimpleServiceInterface::class); 88 | 89 | $this->assertInstanceOf(SimpleService::class, $service); 90 | } 91 | 92 | public function testCanResolveAliasFromContainer() 93 | { 94 | $container = new Container(); 95 | 96 | $container->alias('foo', 'bar', function () { 97 | return new \StdClass(); 98 | }); 99 | 100 | $foo = $container->resolve('foo'); 101 | $bar = $container->resolve('bar'); 102 | 103 | $this->assertInstanceOf(\StdClass::class, $foo); 104 | $this->assertInstanceOf(\StdClass::class, $bar); 105 | } 106 | 107 | public function testCanResolveParamsFromTemplate() 108 | { 109 | $container = new Container(); 110 | 111 | $container->template(ParentService::class, ['foo' => 'bar']); 112 | 113 | $child = $container->resolve(ChildService::class); 114 | 115 | $this->assertEquals('bar', $child->getFoo()); 116 | } 117 | 118 | public function testCanFindServicesByAttributes() 119 | { 120 | $container = new Container(); 121 | 122 | $container->attach('foo', function() {}, ['tags' => ['foo']]); 123 | $container->attach('bar', function() {}, ['tags' => ['foo']]); 124 | $container->attach('bob', function() {}, ['tags' => ['foo']]); 125 | 126 | $services = $container->findByAttribute('tags', 'foo'); 127 | 128 | $this->assertCount(3, $services); 129 | } 130 | 131 | public function testCanEditServiceAttributes() 132 | { 133 | $container = new Container(); 134 | 135 | $container->attach('foo', function() {}, ['tags' => ['foo']]); 136 | 137 | $tags = $container->getAttribute('foo', 'tags'); 138 | 139 | $this->assertEquals(['foo'], $tags); 140 | 141 | $tags[] = 'bar'; 142 | 143 | $container->setAttribute('foo', 'tags', $tags); 144 | 145 | $tags = $container->getAttribute('foo', 'tags'); 146 | 147 | $this->assertEquals(['foo', 'bar'], $tags); 148 | } 149 | 150 | public function testCallAttributeWithSingleArgument() 151 | { 152 | $container = new Container(); 153 | 154 | $container->attach(ServiceWithCall::class, function() { 155 | return new ServiceWithCall(); 156 | }, ['calls' => ['setSomething' => [['what']]]]); 157 | 158 | $service = $container->resolve(ServiceWithCall::class); 159 | 160 | $this->assertEquals('what', $service->getSomething()); 161 | } 162 | 163 | public function testCallAttributeWithMultipleArguments() 164 | { 165 | $container = new Container(); 166 | 167 | $container->attach(ServiceWithCall::class, function() { 168 | return new ServiceWithCall(); 169 | }, ['calls' => ['setSomethingElse' => [['what', 'the']]]]); 170 | 171 | $service = $container->resolve(ServiceWithCall::class); 172 | 173 | $this->assertEquals('whatthe', $service->getSomethingElse()); 174 | } 175 | 176 | public function testCallAttributeWithNoArguments() 177 | { 178 | $container = new Container(); 179 | 180 | $container->attach(ServiceWithCall::class, function() { 181 | return new ServiceWithCall(); 182 | }, ['calls' => ['set']]); 183 | 184 | $service = $container->resolve(ServiceWithCall::class); 185 | 186 | $this->assertEquals('has been set', $service->get()); 187 | } 188 | 189 | public function testCanReferenceTaggedServices() 190 | { 191 | $container = new Container(); 192 | 193 | $container->attach('foo', function() { return new \StdClass(); }, ['tags' => ['foo']]); 194 | $container->attach('bar', function() { return new \StdClass(); }, ['tags' => ['foo']]); 195 | $container->attach('bob', function() { return new \StdClass(); }, ['tags' => ['foo']]); 196 | 197 | $container->attach(Manager::class, function() { 198 | return new Manager(); 199 | }); 200 | 201 | $services = $container->findByAttribute('tags', 'foo'); 202 | 203 | $calls = []; 204 | foreach ($services as $service) { 205 | $calls[] = [new Reference($service)]; 206 | } 207 | 208 | $container->setAttribute(Manager::class, 'calls', ['addService' => $calls]); 209 | 210 | $manager = $container->resolve(Manager::class); 211 | 212 | $this->assertCount(3, $manager->getServices()); 213 | } 214 | 215 | public function testCanUseTaggedReferenceServices() 216 | { 217 | $container = new Container(); 218 | 219 | $container->attach('foo', function() { return new \StdClass(); }, ['tags' => ['foo']]); 220 | $container->attach('bar', function() { return new \StdClass(); }, ['tags' => ['foo']]); 221 | $container->attach('bob', function() { return new \StdClass(); }, ['tags' => ['foo']]); 222 | 223 | $container->attach(Manager::class, function() { 224 | return new Manager(); 225 | }, ['calls' => ['AddService' => new TaggedReference('foo')]]); 226 | 227 | $manager = $container->resolve(Manager::class); 228 | 229 | $this->assertCount(3, $manager->getServices()); 230 | } 231 | } -------------------------------------------------------------------------------- /src/Dispatching/Router.php: -------------------------------------------------------------------------------- 1 | defaultFilters = [ 30 | '{default}' => '[a-zA-Z0-9_\+\-%]+', 31 | '{gobble}' => '[a-zA-Z0-9_\+\-%\/]+', 32 | '{int}' => '[0-9]+', 33 | '{alpha}' => '[a-zA-Z]+', 34 | '{slug}' => '[a-zA-Z0-9_-]+' 35 | ]; 36 | } 37 | 38 | /** 39 | * @param string $name 40 | * @param string $rule 41 | * @param string $action 42 | * @param array $options 43 | * 44 | * @return $this 45 | */ 46 | public function attach($name, $rule, $action, array $options = []) 47 | { 48 | $this->routes[$name] = [ 49 | 'rule' => $rule, 50 | 'action' => $action, 51 | 'options' => $options 52 | ]; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Mount a collection of routes at a specific prefix 59 | * 60 | * @param $prefix 61 | * @param array $routes 62 | */ 63 | public function mount($prefix, array $routes = []) 64 | { 65 | foreach ($routes as $route) 66 | { 67 | $name = $route[0]; 68 | $rule = $route[1]; 69 | $action = $route[2]; 70 | $options = isset($route[3]) ? $route[3] : []; 71 | 72 | $this->attach($name, $prefix . $rule, $action, $options); 73 | } 74 | } 75 | 76 | /** 77 | * Attempt to resolve a route from a URL 78 | * 79 | * @param Request $request 80 | * 81 | * @return array|false 82 | */ 83 | public function resolve(Request $request) 84 | { 85 | foreach ($this->routes as $name => $route) { 86 | $options = $route['options']; 87 | $regex = $this->compileRegex($route['rule'], $options); 88 | $tokens = $this->compileTokens($route['rule']); 89 | 90 | $results = $this->compileResults($regex, $tokens, $request->getUri(), $options); 91 | 92 | if ($results !== false) { 93 | $payload = []; 94 | list($controller, $method) = explode('::', $route['action']); 95 | $payload['_controller'] = $controller; 96 | $payload['_method'] = $method; 97 | $payload['_route'] = $name; 98 | 99 | $payload = array_merge($results, $payload); 100 | 101 | return $payload; 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * @param $name 110 | * @param array $params 111 | * 112 | * @return string 113 | */ 114 | public function gentUrl($name, array $params = []) 115 | { 116 | if (array_key_exists($name, $this->routes)) { 117 | $route = $this->routes[$name]; 118 | $options = $route['options']; 119 | $tokens = $this->compileTokens($route['rule']); 120 | 121 | $results = []; 122 | foreach ($tokens as $index => $rawValue) { 123 | $value = str_replace(['{', '}'], '', $rawValue); 124 | 125 | // Save defaults 126 | if (array_key_exists('defaults', $options) && array_key_exists($value, $options['defaults'])) { 127 | $results[$rawValue] = $options['defaults'][$value]; 128 | } elseif (array_key_exists($value, $params)) { 129 | $results[$rawValue] = $params[$value]; 130 | } else { 131 | throw new \RuntimeException(sprintf( 132 | "No '%s' parameter passed to the url generation for '%s'", 133 | $value, 134 | $name 135 | )); 136 | } 137 | } 138 | 139 | return str_replace(array_keys($results), array_values($results), $route['rule']); 140 | } 141 | } 142 | 143 | /** 144 | * Build a regular expression from the given rule. 145 | * 146 | * @param string $rule 147 | * @param array $options 148 | * 149 | * @return string 150 | */ 151 | private function compileRegex($rule, array $options = []) 152 | { 153 | $regex = '^' . preg_replace_callback( 154 | '@\{[\w]+\}@', 155 | function ($matches) use ($options) { 156 | $optional = false; 157 | $key = str_replace(['{', '}'], '', $matches[0]); 158 | 159 | if (array_key_exists('defaults', $options) && array_key_exists($key, $options['defaults'])) { 160 | $optional = true; 161 | } 162 | 163 | if (array_key_exists('filters', $options) && array_key_exists($key, $options['filters'])) { 164 | if (array_key_exists($options['filters'][$key], $this->defaultFilters)) { 165 | return ($optional ? '?' : '') . '(' . $this->defaultFilters[$options['filters'][$key]] . ')' . ($optional ? '?' : ''); 166 | } else { 167 | return ($optional ? '?' : '') . '(' . $options['filters'][$key] . ')' . ($optional ? '?' : ''); 168 | } 169 | } else { 170 | return ($optional ? '?' : '') . '(' . $this->defaultFilters['{default}'] . ')' . ($optional ? '?' : ''); 171 | } 172 | }, 173 | $rule 174 | ) . '$'; 175 | 176 | return $regex; 177 | } 178 | 179 | /** 180 | * Find tokens within given rule. 181 | * 182 | * @param string $rule 183 | */ 184 | private function compileTokens($rule) 185 | { 186 | $tokens = []; 187 | preg_match_all('@\{([\w]+)\}@', $rule, $tokens, PREG_PATTERN_ORDER); 188 | 189 | return $tokens[0]; 190 | } 191 | 192 | /** 193 | * Match a regular expression against a given *haystack* string. Returning the resulting matches indexed 194 | * by the values of the given tokens. 195 | * 196 | * @param string $regex 197 | * @param array $tokens 198 | * @param string $haystack 199 | * @param array $options 200 | * 201 | * @return array|false 202 | */ 203 | private function compileResults($regex, $tokens, $haystack, array $options = []) 204 | { 205 | $results = []; 206 | 207 | // Test the regular expression against the supplied *haystack* string. 208 | if (preg_match('@' . $regex . '@', $haystack, $values)) { 209 | // Discard *all* matches index. 210 | array_shift($values); 211 | 212 | // Match tokens to values. 213 | foreach ($tokens as $index => $value) { 214 | $value = str_replace(['{', '}'], '', $value); 215 | 216 | // Save defaults 217 | if (array_key_exists('defaults', $options)) { 218 | $defaults = $options['defaults']; 219 | if (array_key_exists($value, $defaults)) { 220 | $results[$value] = $defaults[$value]; 221 | } 222 | } 223 | 224 | // Save parsed values, overriding defaults if necessary 225 | if (array_key_exists($index, $values)) { 226 | $results[$value] = $values[$index]; 227 | } 228 | } 229 | 230 | return $results; 231 | } 232 | 233 | return false; 234 | } 235 | } -------------------------------------------------------------------------------- /src/IoC/Container.php: -------------------------------------------------------------------------------- 1 | services[$name] = $callback; 41 | $this->attributes[$name] = $attributes; 42 | 43 | return $this; 44 | } 45 | 46 | /** 47 | * @param string $alias 48 | * @param string $concrete 49 | * @param callable $callback 50 | * @param array $attributes 51 | * 52 | * @return $this 53 | */ 54 | public function alias($alias, $concrete, callable $callback = null, array $attributes = []) 55 | { 56 | $this->aliases[$alias] = $concrete; 57 | 58 | if (null !== $callback) { 59 | $this->attach($concrete, $callback, $attributes); 60 | } 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @param $template 67 | * @param array $parameters 68 | */ 69 | public function template($template, array $parameters) 70 | { 71 | $this->templates[$template] = $parameters; 72 | } 73 | 74 | /** 75 | * Attempt to resolve a service, firstly from the container itself, then using reflection 76 | * 77 | * @param $name 78 | * 79 | * @return object|null 80 | */ 81 | public function resolve($name) 82 | { 83 | $object = null; 84 | 85 | if (array_key_exists($name, $this->services)) { 86 | $object = call_user_func($this->services[$name]); 87 | } else if (array_key_exists($name, $this->aliases) && array_key_exists($this->aliases[$name], $this->services)) { 88 | $object = call_user_func($this->services[$this->aliases[$name]]); 89 | } else { 90 | try { 91 | if ($result = $this->autoResolve($name)) { 92 | $object = $result; 93 | } else{ 94 | $object = $this->autoResolveAlias($name); 95 | } 96 | } catch (\ReflectionException $e) { 97 | $object = $this->autoResolveAlias($name); 98 | } 99 | } 100 | 101 | $object = $this->handleCallAttributes($name, $object); 102 | 103 | return $object; 104 | } 105 | 106 | /** 107 | * @param $attribute 108 | * @param $key 109 | * 110 | * @return array 111 | */ 112 | public function findByAttribute($attribute, $key) 113 | { 114 | $services = []; 115 | foreach ($this->attributes as $serviceIdentifier => $serviceAttributes) { 116 | if (array_key_exists($attribute, $serviceAttributes)) { 117 | if (in_array($key, $serviceAttributes[$attribute])) { 118 | $services[] = $serviceIdentifier; 119 | } 120 | } 121 | } 122 | 123 | return $services; 124 | } 125 | 126 | /** 127 | * @param $serviceIdentifier 128 | * @param $attribute 129 | * 130 | * @return bool 131 | */ 132 | public function hasAttribute($serviceIdentifier, $attribute) 133 | { 134 | if (array_key_exists($serviceIdentifier, $this->attributes)) { 135 | return array_key_exists($attribute, $this->attributes[$serviceIdentifier]); 136 | } 137 | 138 | return false; 139 | } 140 | 141 | /** 142 | * @param $serviceIdentifier 143 | * @param $attribute 144 | * 145 | * @return mixed 146 | */ 147 | public function getAttribute($serviceIdentifier, $attribute) 148 | { 149 | if ($this->hasAttribute($serviceIdentifier, $attribute)) { 150 | return $this->attributes[$serviceIdentifier][$attribute]; 151 | } 152 | } 153 | 154 | /** 155 | * @param $serviceIdentifier 156 | * @param $attribute 157 | * @param $value 158 | * 159 | * @return mixed 160 | */ 161 | public function setAttribute($serviceIdentifier, $attribute, $value) 162 | { 163 | return $this->attributes[$serviceIdentifier][$attribute] = $value; 164 | } 165 | 166 | /** 167 | * @param $serviceIdentifier 168 | * @param $object 169 | */ 170 | private function handleCallAttributes($serviceIdentifier, $object) 171 | { 172 | if ($this->hasAttribute($serviceIdentifier, 'calls')) { 173 | foreach ($this->getAttribute($serviceIdentifier, 'calls') as $method => $calls) { 174 | if (is_array($calls)) { 175 | foreach ($calls as $arguments) { 176 | if (is_array($arguments)) { 177 | 178 | // See if we have any references 179 | for ($i = 0; $i < count($arguments); $i++) { 180 | if ($arguments[$i] instanceof Reference) { 181 | $arguments[$i] = $this->resolve($arguments[$i]->getServiceIdentifier()); 182 | } 183 | } 184 | 185 | call_user_func_array([$object, $method], $arguments); 186 | 187 | } else { 188 | call_user_func([$object, $calls]); 189 | } 190 | } 191 | } elseif ($calls instanceof TaggedReference) { 192 | $services = $this->findByAttribute('tags', $calls->getTag()); 193 | foreach ($services as $service) { 194 | call_user_func([$object, $method], $this->resolve($service)); 195 | } 196 | } else { 197 | call_user_func([$object, $calls]); 198 | } 199 | } 200 | } 201 | 202 | return $object; 203 | } 204 | 205 | /** 206 | * A simple helper to resolve dependencies. 207 | * 208 | * @param \ReflectionClass $class 209 | * @param $parameters 210 | * 211 | * @return array 212 | */ 213 | private function getDependencies($class, $parameters) 214 | { 215 | $defaults = []; 216 | if ($parent = $class->getParentClass()) { 217 | if (array_key_exists($parent->name, $this->templates)) { 218 | $defaults = $this->templates[$parent->name]; 219 | } 220 | } 221 | 222 | $args = []; 223 | foreach ($parameters as $parameter) { 224 | /** 225 | * Is the dependency available via a parent class template? 226 | */ 227 | if (!empty($defaults) && array_key_exists($parameter->name, $defaults)) { 228 | $args[] = $defaults[$parameter->name]; 229 | /** 230 | * Is the dependency another object? 231 | */ 232 | } elseif ($parameter->getClass() && $object = $this->resolve($parameter->getClass()->name)) { 233 | $args[] = $object; 234 | /** 235 | * Do we have a default value? 236 | */ 237 | } elseif ($parameter->isDefaultValueAvailable()) { 238 | $args[] = $parameter->getDefaultValue(); 239 | } 240 | } 241 | 242 | return $args; 243 | } 244 | 245 | /** 246 | * @param $name 247 | * 248 | * @return object 249 | */ 250 | private function autoResolve($name) 251 | { 252 | $object = null; 253 | $class = new \ReflectionClass($name); 254 | 255 | if ($class->isInstantiable()) { 256 | $construct = $class->getConstructor(); 257 | if ($construct === null) { 258 | $object = new $name; 259 | } else { 260 | $dependencies = $this->getDependencies($class, $construct->getParameters()); 261 | $object = $class->newInstanceArgs($dependencies); 262 | } 263 | } 264 | 265 | return $object; 266 | } 267 | 268 | /** 269 | * @param $name 270 | * 271 | * @return object 272 | */ 273 | private function autoResolveAlias($name) 274 | { 275 | if (array_key_exists($name, $this->aliases)) { 276 | return $this->autoResolve($this->aliases[$name]); 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "1e688a2bd3ca49aa2edd06629f9ddcb8", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.0.5", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 21 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3,<8.0-DEV" 26 | }, 27 | "require-dev": { 28 | "athletic/athletic": "~0.1.8", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpunit/phpunit": "~4.0", 32 | "squizlabs/php_codesniffer": "~2.0" 33 | }, 34 | "type": "library", 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "1.0.x-dev" 38 | } 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "MIT" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Marco Pivetta", 52 | "email": "ocramius@gmail.com", 53 | "homepage": "http://ocramius.github.com/" 54 | } 55 | ], 56 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 57 | "homepage": "https://github.com/doctrine/instantiator", 58 | "keywords": [ 59 | "constructor", 60 | "instantiate" 61 | ], 62 | "time": "2015-06-14 21:17:01" 63 | }, 64 | { 65 | "name": "phpdocumentor/reflection-docblock", 66 | "version": "2.0.4", 67 | "source": { 68 | "type": "git", 69 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 70 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" 71 | }, 72 | "dist": { 73 | "type": "zip", 74 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", 75 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", 76 | "shasum": "" 77 | }, 78 | "require": { 79 | "php": ">=5.3.3" 80 | }, 81 | "require-dev": { 82 | "phpunit/phpunit": "~4.0" 83 | }, 84 | "suggest": { 85 | "dflydev/markdown": "~1.0", 86 | "erusev/parsedown": "~1.0" 87 | }, 88 | "type": "library", 89 | "extra": { 90 | "branch-alias": { 91 | "dev-master": "2.0.x-dev" 92 | } 93 | }, 94 | "autoload": { 95 | "psr-0": { 96 | "phpDocumentor": [ 97 | "src/" 98 | ] 99 | } 100 | }, 101 | "notification-url": "https://packagist.org/downloads/", 102 | "license": [ 103 | "MIT" 104 | ], 105 | "authors": [ 106 | { 107 | "name": "Mike van Riel", 108 | "email": "mike.vanriel@naenius.com" 109 | } 110 | ], 111 | "time": "2015-02-03 12:10:50" 112 | }, 113 | { 114 | "name": "phpspec/prophecy", 115 | "version": "v1.4.1", 116 | "source": { 117 | "type": "git", 118 | "url": "https://github.com/phpspec/prophecy.git", 119 | "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373" 120 | }, 121 | "dist": { 122 | "type": "zip", 123 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373", 124 | "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373", 125 | "shasum": "" 126 | }, 127 | "require": { 128 | "doctrine/instantiator": "^1.0.2", 129 | "phpdocumentor/reflection-docblock": "~2.0", 130 | "sebastian/comparator": "~1.1" 131 | }, 132 | "require-dev": { 133 | "phpspec/phpspec": "~2.0" 134 | }, 135 | "type": "library", 136 | "extra": { 137 | "branch-alias": { 138 | "dev-master": "1.4.x-dev" 139 | } 140 | }, 141 | "autoload": { 142 | "psr-0": { 143 | "Prophecy\\": "src/" 144 | } 145 | }, 146 | "notification-url": "https://packagist.org/downloads/", 147 | "license": [ 148 | "MIT" 149 | ], 150 | "authors": [ 151 | { 152 | "name": "Konstantin Kudryashov", 153 | "email": "ever.zet@gmail.com", 154 | "homepage": "http://everzet.com" 155 | }, 156 | { 157 | "name": "Marcello Duarte", 158 | "email": "marcello.duarte@gmail.com" 159 | } 160 | ], 161 | "description": "Highly opinionated mocking framework for PHP 5.3+", 162 | "homepage": "https://github.com/phpspec/prophecy", 163 | "keywords": [ 164 | "Double", 165 | "Dummy", 166 | "fake", 167 | "mock", 168 | "spy", 169 | "stub" 170 | ], 171 | "time": "2015-04-27 22:15:08" 172 | }, 173 | { 174 | "name": "phpunit/php-code-coverage", 175 | "version": "2.1.9", 176 | "source": { 177 | "type": "git", 178 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 179 | "reference": "5bd48b86cd282da411bb80baac1398ce3fefac41" 180 | }, 181 | "dist": { 182 | "type": "zip", 183 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5bd48b86cd282da411bb80baac1398ce3fefac41", 184 | "reference": "5bd48b86cd282da411bb80baac1398ce3fefac41", 185 | "shasum": "" 186 | }, 187 | "require": { 188 | "php": ">=5.3.3", 189 | "phpunit/php-file-iterator": "~1.3", 190 | "phpunit/php-text-template": "~1.2", 191 | "phpunit/php-token-stream": "~1.3", 192 | "sebastian/environment": "~1.0", 193 | "sebastian/version": "~1.0" 194 | }, 195 | "require-dev": { 196 | "ext-xdebug": ">=2.1.4", 197 | "phpunit/phpunit": "~4" 198 | }, 199 | "suggest": { 200 | "ext-dom": "*", 201 | "ext-xdebug": ">=2.2.1", 202 | "ext-xmlwriter": "*" 203 | }, 204 | "type": "library", 205 | "extra": { 206 | "branch-alias": { 207 | "dev-master": "2.1.x-dev" 208 | } 209 | }, 210 | "autoload": { 211 | "classmap": [ 212 | "src/" 213 | ] 214 | }, 215 | "notification-url": "https://packagist.org/downloads/", 216 | "license": [ 217 | "BSD-3-Clause" 218 | ], 219 | "authors": [ 220 | { 221 | "name": "Sebastian Bergmann", 222 | "email": "sb@sebastian-bergmann.de", 223 | "role": "lead" 224 | } 225 | ], 226 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 227 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 228 | "keywords": [ 229 | "coverage", 230 | "testing", 231 | "xunit" 232 | ], 233 | "time": "2015-07-26 12:54:47" 234 | }, 235 | { 236 | "name": "phpunit/php-file-iterator", 237 | "version": "1.4.1", 238 | "source": { 239 | "type": "git", 240 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 241 | "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" 242 | }, 243 | "dist": { 244 | "type": "zip", 245 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", 246 | "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", 247 | "shasum": "" 248 | }, 249 | "require": { 250 | "php": ">=5.3.3" 251 | }, 252 | "type": "library", 253 | "extra": { 254 | "branch-alias": { 255 | "dev-master": "1.4.x-dev" 256 | } 257 | }, 258 | "autoload": { 259 | "classmap": [ 260 | "src/" 261 | ] 262 | }, 263 | "notification-url": "https://packagist.org/downloads/", 264 | "license": [ 265 | "BSD-3-Clause" 266 | ], 267 | "authors": [ 268 | { 269 | "name": "Sebastian Bergmann", 270 | "email": "sb@sebastian-bergmann.de", 271 | "role": "lead" 272 | } 273 | ], 274 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 275 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 276 | "keywords": [ 277 | "filesystem", 278 | "iterator" 279 | ], 280 | "time": "2015-06-21 13:08:43" 281 | }, 282 | { 283 | "name": "phpunit/php-text-template", 284 | "version": "1.2.1", 285 | "source": { 286 | "type": "git", 287 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 288 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 289 | }, 290 | "dist": { 291 | "type": "zip", 292 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 293 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 294 | "shasum": "" 295 | }, 296 | "require": { 297 | "php": ">=5.3.3" 298 | }, 299 | "type": "library", 300 | "autoload": { 301 | "classmap": [ 302 | "src/" 303 | ] 304 | }, 305 | "notification-url": "https://packagist.org/downloads/", 306 | "license": [ 307 | "BSD-3-Clause" 308 | ], 309 | "authors": [ 310 | { 311 | "name": "Sebastian Bergmann", 312 | "email": "sebastian@phpunit.de", 313 | "role": "lead" 314 | } 315 | ], 316 | "description": "Simple template engine.", 317 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 318 | "keywords": [ 319 | "template" 320 | ], 321 | "time": "2015-06-21 13:50:34" 322 | }, 323 | { 324 | "name": "phpunit/php-timer", 325 | "version": "1.0.7", 326 | "source": { 327 | "type": "git", 328 | "url": "https://github.com/sebastianbergmann/php-timer.git", 329 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" 330 | }, 331 | "dist": { 332 | "type": "zip", 333 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", 334 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", 335 | "shasum": "" 336 | }, 337 | "require": { 338 | "php": ">=5.3.3" 339 | }, 340 | "type": "library", 341 | "autoload": { 342 | "classmap": [ 343 | "src/" 344 | ] 345 | }, 346 | "notification-url": "https://packagist.org/downloads/", 347 | "license": [ 348 | "BSD-3-Clause" 349 | ], 350 | "authors": [ 351 | { 352 | "name": "Sebastian Bergmann", 353 | "email": "sb@sebastian-bergmann.de", 354 | "role": "lead" 355 | } 356 | ], 357 | "description": "Utility class for timing", 358 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 359 | "keywords": [ 360 | "timer" 361 | ], 362 | "time": "2015-06-21 08:01:12" 363 | }, 364 | { 365 | "name": "phpunit/php-token-stream", 366 | "version": "1.4.3", 367 | "source": { 368 | "type": "git", 369 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 370 | "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9" 371 | }, 372 | "dist": { 373 | "type": "zip", 374 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/7a9b0969488c3c54fd62b4d504b3ec758fd005d9", 375 | "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9", 376 | "shasum": "" 377 | }, 378 | "require": { 379 | "ext-tokenizer": "*", 380 | "php": ">=5.3.3" 381 | }, 382 | "require-dev": { 383 | "phpunit/phpunit": "~4.2" 384 | }, 385 | "type": "library", 386 | "extra": { 387 | "branch-alias": { 388 | "dev-master": "1.4-dev" 389 | } 390 | }, 391 | "autoload": { 392 | "classmap": [ 393 | "src/" 394 | ] 395 | }, 396 | "notification-url": "https://packagist.org/downloads/", 397 | "license": [ 398 | "BSD-3-Clause" 399 | ], 400 | "authors": [ 401 | { 402 | "name": "Sebastian Bergmann", 403 | "email": "sebastian@phpunit.de" 404 | } 405 | ], 406 | "description": "Wrapper around PHP's tokenizer extension.", 407 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 408 | "keywords": [ 409 | "tokenizer" 410 | ], 411 | "time": "2015-06-19 03:43:16" 412 | }, 413 | { 414 | "name": "phpunit/phpunit", 415 | "version": "4.7.7", 416 | "source": { 417 | "type": "git", 418 | "url": "https://github.com/sebastianbergmann/phpunit.git", 419 | "reference": "9b97f9d807b862c2de2a36e86690000801c85724" 420 | }, 421 | "dist": { 422 | "type": "zip", 423 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b97f9d807b862c2de2a36e86690000801c85724", 424 | "reference": "9b97f9d807b862c2de2a36e86690000801c85724", 425 | "shasum": "" 426 | }, 427 | "require": { 428 | "ext-dom": "*", 429 | "ext-json": "*", 430 | "ext-pcre": "*", 431 | "ext-reflection": "*", 432 | "ext-spl": "*", 433 | "php": ">=5.3.3", 434 | "phpspec/prophecy": "~1.3,>=1.3.1", 435 | "phpunit/php-code-coverage": "~2.1", 436 | "phpunit/php-file-iterator": "~1.4", 437 | "phpunit/php-text-template": "~1.2", 438 | "phpunit/php-timer": ">=1.0.6", 439 | "phpunit/phpunit-mock-objects": "~2.3", 440 | "sebastian/comparator": "~1.1", 441 | "sebastian/diff": "~1.2", 442 | "sebastian/environment": "~1.2", 443 | "sebastian/exporter": "~1.2", 444 | "sebastian/global-state": "~1.0", 445 | "sebastian/version": "~1.0", 446 | "symfony/yaml": "~2.1|~3.0" 447 | }, 448 | "suggest": { 449 | "phpunit/php-invoker": "~1.1" 450 | }, 451 | "bin": [ 452 | "phpunit" 453 | ], 454 | "type": "library", 455 | "extra": { 456 | "branch-alias": { 457 | "dev-master": "4.7.x-dev" 458 | } 459 | }, 460 | "autoload": { 461 | "classmap": [ 462 | "src/" 463 | ] 464 | }, 465 | "notification-url": "https://packagist.org/downloads/", 466 | "license": [ 467 | "BSD-3-Clause" 468 | ], 469 | "authors": [ 470 | { 471 | "name": "Sebastian Bergmann", 472 | "email": "sebastian@phpunit.de", 473 | "role": "lead" 474 | } 475 | ], 476 | "description": "The PHP Unit Testing framework.", 477 | "homepage": "https://phpunit.de/", 478 | "keywords": [ 479 | "phpunit", 480 | "testing", 481 | "xunit" 482 | ], 483 | "time": "2015-07-13 11:28:34" 484 | }, 485 | { 486 | "name": "phpunit/phpunit-mock-objects", 487 | "version": "2.3.6", 488 | "source": { 489 | "type": "git", 490 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 491 | "reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42" 492 | }, 493 | "dist": { 494 | "type": "zip", 495 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/18dfbcb81d05e2296c0bcddd4db96cade75e6f42", 496 | "reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42", 497 | "shasum": "" 498 | }, 499 | "require": { 500 | "doctrine/instantiator": "~1.0,>=1.0.2", 501 | "php": ">=5.3.3", 502 | "phpunit/php-text-template": "~1.2", 503 | "sebastian/exporter": "~1.2" 504 | }, 505 | "require-dev": { 506 | "phpunit/phpunit": "~4.4" 507 | }, 508 | "suggest": { 509 | "ext-soap": "*" 510 | }, 511 | "type": "library", 512 | "extra": { 513 | "branch-alias": { 514 | "dev-master": "2.3.x-dev" 515 | } 516 | }, 517 | "autoload": { 518 | "classmap": [ 519 | "src/" 520 | ] 521 | }, 522 | "notification-url": "https://packagist.org/downloads/", 523 | "license": [ 524 | "BSD-3-Clause" 525 | ], 526 | "authors": [ 527 | { 528 | "name": "Sebastian Bergmann", 529 | "email": "sb@sebastian-bergmann.de", 530 | "role": "lead" 531 | } 532 | ], 533 | "description": "Mock Object library for PHPUnit", 534 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 535 | "keywords": [ 536 | "mock", 537 | "xunit" 538 | ], 539 | "time": "2015-07-10 06:54:24" 540 | }, 541 | { 542 | "name": "sebastian/comparator", 543 | "version": "1.2.0", 544 | "source": { 545 | "type": "git", 546 | "url": "https://github.com/sebastianbergmann/comparator.git", 547 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" 548 | }, 549 | "dist": { 550 | "type": "zip", 551 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", 552 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", 553 | "shasum": "" 554 | }, 555 | "require": { 556 | "php": ">=5.3.3", 557 | "sebastian/diff": "~1.2", 558 | "sebastian/exporter": "~1.2" 559 | }, 560 | "require-dev": { 561 | "phpunit/phpunit": "~4.4" 562 | }, 563 | "type": "library", 564 | "extra": { 565 | "branch-alias": { 566 | "dev-master": "1.2.x-dev" 567 | } 568 | }, 569 | "autoload": { 570 | "classmap": [ 571 | "src/" 572 | ] 573 | }, 574 | "notification-url": "https://packagist.org/downloads/", 575 | "license": [ 576 | "BSD-3-Clause" 577 | ], 578 | "authors": [ 579 | { 580 | "name": "Jeff Welch", 581 | "email": "whatthejeff@gmail.com" 582 | }, 583 | { 584 | "name": "Volker Dusch", 585 | "email": "github@wallbash.com" 586 | }, 587 | { 588 | "name": "Bernhard Schussek", 589 | "email": "bschussek@2bepublished.at" 590 | }, 591 | { 592 | "name": "Sebastian Bergmann", 593 | "email": "sebastian@phpunit.de" 594 | } 595 | ], 596 | "description": "Provides the functionality to compare PHP values for equality", 597 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 598 | "keywords": [ 599 | "comparator", 600 | "compare", 601 | "equality" 602 | ], 603 | "time": "2015-07-26 15:48:44" 604 | }, 605 | { 606 | "name": "sebastian/diff", 607 | "version": "1.3.0", 608 | "source": { 609 | "type": "git", 610 | "url": "https://github.com/sebastianbergmann/diff.git", 611 | "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3" 612 | }, 613 | "dist": { 614 | "type": "zip", 615 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3", 616 | "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3", 617 | "shasum": "" 618 | }, 619 | "require": { 620 | "php": ">=5.3.3" 621 | }, 622 | "require-dev": { 623 | "phpunit/phpunit": "~4.2" 624 | }, 625 | "type": "library", 626 | "extra": { 627 | "branch-alias": { 628 | "dev-master": "1.3-dev" 629 | } 630 | }, 631 | "autoload": { 632 | "classmap": [ 633 | "src/" 634 | ] 635 | }, 636 | "notification-url": "https://packagist.org/downloads/", 637 | "license": [ 638 | "BSD-3-Clause" 639 | ], 640 | "authors": [ 641 | { 642 | "name": "Kore Nordmann", 643 | "email": "mail@kore-nordmann.de" 644 | }, 645 | { 646 | "name": "Sebastian Bergmann", 647 | "email": "sebastian@phpunit.de" 648 | } 649 | ], 650 | "description": "Diff implementation", 651 | "homepage": "http://www.github.com/sebastianbergmann/diff", 652 | "keywords": [ 653 | "diff" 654 | ], 655 | "time": "2015-02-22 15:13:53" 656 | }, 657 | { 658 | "name": "sebastian/environment", 659 | "version": "1.3.0", 660 | "source": { 661 | "type": "git", 662 | "url": "https://github.com/sebastianbergmann/environment.git", 663 | "reference": "4fe0a44cddd8cc19583a024bdc7374eb2fef0b87" 664 | }, 665 | "dist": { 666 | "type": "zip", 667 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4fe0a44cddd8cc19583a024bdc7374eb2fef0b87", 668 | "reference": "4fe0a44cddd8cc19583a024bdc7374eb2fef0b87", 669 | "shasum": "" 670 | }, 671 | "require": { 672 | "php": ">=5.3.3" 673 | }, 674 | "require-dev": { 675 | "phpunit/phpunit": "~4.4" 676 | }, 677 | "type": "library", 678 | "extra": { 679 | "branch-alias": { 680 | "dev-master": "1.3.x-dev" 681 | } 682 | }, 683 | "autoload": { 684 | "classmap": [ 685 | "src/" 686 | ] 687 | }, 688 | "notification-url": "https://packagist.org/downloads/", 689 | "license": [ 690 | "BSD-3-Clause" 691 | ], 692 | "authors": [ 693 | { 694 | "name": "Sebastian Bergmann", 695 | "email": "sebastian@phpunit.de" 696 | } 697 | ], 698 | "description": "Provides functionality to handle HHVM/PHP environments", 699 | "homepage": "http://www.github.com/sebastianbergmann/environment", 700 | "keywords": [ 701 | "Xdebug", 702 | "environment", 703 | "hhvm" 704 | ], 705 | "time": "2015-07-26 06:42:57" 706 | }, 707 | { 708 | "name": "sebastian/exporter", 709 | "version": "1.2.1", 710 | "source": { 711 | "type": "git", 712 | "url": "https://github.com/sebastianbergmann/exporter.git", 713 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e" 714 | }, 715 | "dist": { 716 | "type": "zip", 717 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", 718 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e", 719 | "shasum": "" 720 | }, 721 | "require": { 722 | "php": ">=5.3.3", 723 | "sebastian/recursion-context": "~1.0" 724 | }, 725 | "require-dev": { 726 | "phpunit/phpunit": "~4.4" 727 | }, 728 | "type": "library", 729 | "extra": { 730 | "branch-alias": { 731 | "dev-master": "1.2.x-dev" 732 | } 733 | }, 734 | "autoload": { 735 | "classmap": [ 736 | "src/" 737 | ] 738 | }, 739 | "notification-url": "https://packagist.org/downloads/", 740 | "license": [ 741 | "BSD-3-Clause" 742 | ], 743 | "authors": [ 744 | { 745 | "name": "Jeff Welch", 746 | "email": "whatthejeff@gmail.com" 747 | }, 748 | { 749 | "name": "Volker Dusch", 750 | "email": "github@wallbash.com" 751 | }, 752 | { 753 | "name": "Bernhard Schussek", 754 | "email": "bschussek@2bepublished.at" 755 | }, 756 | { 757 | "name": "Sebastian Bergmann", 758 | "email": "sebastian@phpunit.de" 759 | }, 760 | { 761 | "name": "Adam Harvey", 762 | "email": "aharvey@php.net" 763 | } 764 | ], 765 | "description": "Provides the functionality to export PHP variables for visualization", 766 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 767 | "keywords": [ 768 | "export", 769 | "exporter" 770 | ], 771 | "time": "2015-06-21 07:55:53" 772 | }, 773 | { 774 | "name": "sebastian/global-state", 775 | "version": "1.0.0", 776 | "source": { 777 | "type": "git", 778 | "url": "https://github.com/sebastianbergmann/global-state.git", 779 | "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01" 780 | }, 781 | "dist": { 782 | "type": "zip", 783 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01", 784 | "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01", 785 | "shasum": "" 786 | }, 787 | "require": { 788 | "php": ">=5.3.3" 789 | }, 790 | "require-dev": { 791 | "phpunit/phpunit": "~4.2" 792 | }, 793 | "suggest": { 794 | "ext-uopz": "*" 795 | }, 796 | "type": "library", 797 | "extra": { 798 | "branch-alias": { 799 | "dev-master": "1.0-dev" 800 | } 801 | }, 802 | "autoload": { 803 | "classmap": [ 804 | "src/" 805 | ] 806 | }, 807 | "notification-url": "https://packagist.org/downloads/", 808 | "license": [ 809 | "BSD-3-Clause" 810 | ], 811 | "authors": [ 812 | { 813 | "name": "Sebastian Bergmann", 814 | "email": "sebastian@phpunit.de" 815 | } 816 | ], 817 | "description": "Snapshotting of global state", 818 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 819 | "keywords": [ 820 | "global state" 821 | ], 822 | "time": "2014-10-06 09:23:50" 823 | }, 824 | { 825 | "name": "sebastian/recursion-context", 826 | "version": "1.0.1", 827 | "source": { 828 | "type": "git", 829 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 830 | "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" 831 | }, 832 | "dist": { 833 | "type": "zip", 834 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", 835 | "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", 836 | "shasum": "" 837 | }, 838 | "require": { 839 | "php": ">=5.3.3" 840 | }, 841 | "require-dev": { 842 | "phpunit/phpunit": "~4.4" 843 | }, 844 | "type": "library", 845 | "extra": { 846 | "branch-alias": { 847 | "dev-master": "1.0.x-dev" 848 | } 849 | }, 850 | "autoload": { 851 | "classmap": [ 852 | "src/" 853 | ] 854 | }, 855 | "notification-url": "https://packagist.org/downloads/", 856 | "license": [ 857 | "BSD-3-Clause" 858 | ], 859 | "authors": [ 860 | { 861 | "name": "Jeff Welch", 862 | "email": "whatthejeff@gmail.com" 863 | }, 864 | { 865 | "name": "Sebastian Bergmann", 866 | "email": "sebastian@phpunit.de" 867 | }, 868 | { 869 | "name": "Adam Harvey", 870 | "email": "aharvey@php.net" 871 | } 872 | ], 873 | "description": "Provides functionality to recursively process PHP variables", 874 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 875 | "time": "2015-06-21 08:04:50" 876 | }, 877 | { 878 | "name": "sebastian/version", 879 | "version": "1.0.6", 880 | "source": { 881 | "type": "git", 882 | "url": "https://github.com/sebastianbergmann/version.git", 883 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" 884 | }, 885 | "dist": { 886 | "type": "zip", 887 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 888 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 889 | "shasum": "" 890 | }, 891 | "type": "library", 892 | "autoload": { 893 | "classmap": [ 894 | "src/" 895 | ] 896 | }, 897 | "notification-url": "https://packagist.org/downloads/", 898 | "license": [ 899 | "BSD-3-Clause" 900 | ], 901 | "authors": [ 902 | { 903 | "name": "Sebastian Bergmann", 904 | "email": "sebastian@phpunit.de", 905 | "role": "lead" 906 | } 907 | ], 908 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 909 | "homepage": "https://github.com/sebastianbergmann/version", 910 | "time": "2015-06-21 13:59:46" 911 | }, 912 | { 913 | "name": "symfony/yaml", 914 | "version": "v2.7.2", 915 | "source": { 916 | "type": "git", 917 | "url": "https://github.com/symfony/Yaml.git", 918 | "reference": "4bfbe0ed3909bfddd75b70c094391ec1f142f860" 919 | }, 920 | "dist": { 921 | "type": "zip", 922 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/4bfbe0ed3909bfddd75b70c094391ec1f142f860", 923 | "reference": "4bfbe0ed3909bfddd75b70c094391ec1f142f860", 924 | "shasum": "" 925 | }, 926 | "require": { 927 | "php": ">=5.3.9" 928 | }, 929 | "require-dev": { 930 | "symfony/phpunit-bridge": "~2.7" 931 | }, 932 | "type": "library", 933 | "extra": { 934 | "branch-alias": { 935 | "dev-master": "2.7-dev" 936 | } 937 | }, 938 | "autoload": { 939 | "psr-4": { 940 | "Symfony\\Component\\Yaml\\": "" 941 | } 942 | }, 943 | "notification-url": "https://packagist.org/downloads/", 944 | "license": [ 945 | "MIT" 946 | ], 947 | "authors": [ 948 | { 949 | "name": "Fabien Potencier", 950 | "email": "fabien@symfony.com" 951 | }, 952 | { 953 | "name": "Symfony Community", 954 | "homepage": "https://symfony.com/contributors" 955 | } 956 | ], 957 | "description": "Symfony Yaml Component", 958 | "homepage": "https://symfony.com", 959 | "time": "2015-07-01 11:25:50" 960 | } 961 | ], 962 | "aliases": [], 963 | "minimum-stability": "stable", 964 | "stability-flags": [], 965 | "prefer-stable": false, 966 | "prefer-lowest": false, 967 | "platform": { 968 | "php": "^5.5" 969 | }, 970 | "platform-dev": [] 971 | } 972 | --------------------------------------------------------------------------------