├── .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 |
--------------------------------------------------------------------------------