├── infra
├── php
│ ├── php-fpm.conf
│ └── php.ini
├── docker-compose.ci.override.yml
└── nginx
│ ├── Dockerfile
│ └── nginx.conf
├── .gitignore
├── phpstan.neon
├── .env.example
├── src
├── Http
│ ├── RouteHandler.php
│ └── RouteHandler
│ │ ├── Home.php
│ │ ├── ToDoRead.php
│ │ ├── ToDoCreate.php
│ │ ├── ToDoDelete.php
│ │ ├── ToDoUpdate.php
│ │ └── ToDoList.php
├── Config
│ ├── PdoFactory.php
│ ├── LoggerFactory.php
│ ├── DependencyInjection.php
│ └── RouterFactory.php
├── Model
│ ├── InvalidDataException.php
│ ├── ToDo.php
│ └── ToDoDataMapper.php
├── App.php
└── _functions.php
├── phpunit.xml.dist
├── index.php
├── .php_cs
├── docker-compose.yml
├── bin
└── cli
├── composer.json
├── test
├── AppUnitTest.php
├── Http
│ └── RouteHandler
│ │ ├── ToDoCreateUnitTest.php
│ │ ├── ToDoDeleteUnitTest.php
│ │ ├── ToDoReadUnitTest.php
│ │ ├── ToDoUpdateUnitTest.php
│ │ └── ToDoListUnitTest.php
├── AppIntegrationTest.php
└── Model
│ ├── ToDoDataMapperIntegrationTest.php
│ └── ToDoUnitTest.php
├── .gitlab-ci.yml
├── Dockerfile
├── README.md
├── docs
└── todos.openapi.yml
└── composer.lock
/infra/php/php-fpm.conf:
--------------------------------------------------------------------------------
1 | [www]
2 | pm.status_path = /status
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.env
2 | /var
3 | /vendor
4 | *.cache
5 | docker-compose.override.yml
6 |
--------------------------------------------------------------------------------
/infra/php/php.ini:
--------------------------------------------------------------------------------
1 | date.timezone = UTC
2 | memory_limit = -1
3 | log_errors = 1
4 | error_reporting = E_ALL
5 | upload_max_filesize = 32M
6 | post_max_size = 32M
7 | expose_php = off
8 |
--------------------------------------------------------------------------------
/infra/docker-compose.ci.override.yml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 |
3 | services:
4 |
5 | app:
6 | image: ${APP_DEV_IMAGE}:${CI_COMMIT_REF_SLUG}
7 |
8 | reverse-proxy:
9 | image: ${HTTP_PROXY_IMAGE}:${CI_COMMIT_REF_SLUG}
10 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: max
3 | ignoreErrors:
4 | - message: "#factory expects callable#"
5 | path: src/Config/DependencyInjection.php
6 | includes:
7 | - /opt/composer/vendor/phpstan/phpstan-beberlei-assert/extension.neon
8 | - /opt/composer/vendor/phpstan/phpstan-phpunit/extension.neon
9 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # app configuration
2 |
3 | # enables/disables verbose stack traces on errors
4 | DEBUG=true
5 |
6 | # enables/disables the DI container cache
7 | CACHE=false
8 |
9 | # each key-value pair provides credentials of an authenticated user
10 | AUTH_USERS='{ "user": "password" }'
11 |
12 | # PostgreSQL credentials
13 | DB_USER=app
14 | DB_PASSWORD=password
15 | DB_NAME=app
16 | DB_HOST=database
17 | DB_PORT=5432
18 |
--------------------------------------------------------------------------------
/src/Http/RouteHandler.php:
--------------------------------------------------------------------------------
1 | /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;' || cat /etc/nginx/conf.d/default.conf" ]
13 |
14 |
--------------------------------------------------------------------------------
/src/Config/PdoFactory.php:
--------------------------------------------------------------------------------
1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
23 |
24 | return $pdo;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 | test
11 |
12 |
13 | test
14 |
15 |
16 |
17 |
18 |
19 | src
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Http/RouteHandler/Home.php:
--------------------------------------------------------------------------------
1 | [
22 | 'todos' => $request->getUri() . 'todos',
23 | 'docs' => $request->getUri() . 'docs',
24 | ],
25 | ]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | handle($request);
20 | } catch (Throwable $e) {
21 | logger()->error($e, ['exception' => $e, 'request' => $request ?? null]);
22 | $response = new JsonResponse([ 'error' => exception_to_array($e) ], 500);
23 | }
24 |
25 | (new SapiStreamEmitter())->emit($response);
26 | })();
27 |
--------------------------------------------------------------------------------
/src/Config/LoggerFactory.php:
--------------------------------------------------------------------------------
1 | includeStacktraces();
17 |
18 | $streamHandler = new MonologStreamHandler('php://stderr', env('DEBUG') ? Monolog::DEBUG : Monolog::INFO);
19 | $streamHandler->setFormatter($formatter);
20 | $logger->pushHandler($streamHandler);
21 |
22 | return $logger;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | in([
5 | __DIR__.'/src',
6 | __DIR__.'/test',
7 | __DIR__.'/bin',
8 | ])
9 | ->append([
10 | __FILE__,
11 | __DIR__.'/index.php',
12 | ])
13 | ;
14 |
15 | return PhpCsFixer\Config::create()
16 | ->setRiskyAllowed(true)
17 | ->setFinder($finder)
18 | ->setRules([
19 | '@PSR2' => true,
20 | '@PHP71Migration' => true,
21 | '@PHP71Migration:risky' => true,
22 | '@PHP73Migration' => true,
23 | 'heredoc_indentation' => false,
24 | 'array_syntax' => ['syntax' => 'short'],
25 | 'class_attributes_separation' => ['elements' => ['method', 'property']],
26 | 'phpdoc_summary' => false,
27 | 'yoda_style' => null,
28 | 'no_unused_imports' => true,
29 | 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
30 | ])
31 | ;
32 |
--------------------------------------------------------------------------------
/src/Model/InvalidDataException.php:
--------------------------------------------------------------------------------
1 | getErrorExceptions() as $error) {
21 | $new->details[$error->getPropertyPath()] = $error->getMessage();
22 | }
23 |
24 | return $new;
25 | }
26 |
27 | public static function fromAssertInvalidArgumentException(InvalidArgumentException $e): self
28 | {
29 | $new = new static('Invalid data', 0, $e);
30 | $new->details[$e->getPropertyPath()] = $e->getMessage();
31 | return $new;
32 | }
33 |
34 | /**
35 | * @return string[]
36 | */
37 | public function getDetails(): array
38 | {
39 | return $this->details;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/infra/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 | root /usr/share/nginx/html;
5 | error_page 500 502 503 504 /50x.html;
6 |
7 | client_max_body_size 100m;
8 |
9 | location / {
10 | try_files $uri /index.php$is_args$args;
11 | }
12 |
13 | location ~ \.php$ {
14 | fastcgi_split_path_info ^(.+\.php)(/.+)$;
15 | fastcgi_pass ${FASTCGI_TARGET};
16 | fastcgi_index index.php;
17 | include fastcgi_params;
18 | fastcgi_param DOCUMENT_ROOT /app;
19 | fastcgi_param SCRIPT_FILENAME /app/$fastcgi_script_name;
20 | }
21 |
22 | location /docs/ {
23 | proxy_pass ${DOCS_TARGET};
24 | }
25 |
26 | # add trailing slash
27 | location = /docs {
28 | return 302 $scheme://$host$request_uri/;
29 | }
30 |
31 | location ~* ^/(robots\.txt|favicon\.ico)$ {
32 | access_log off;
33 | log_not_found off;
34 | return 404;
35 | }
36 |
37 | location = /nginx_status {
38 | stub_status on;
39 | access_log off;
40 | allow 127.0.0.1;
41 | deny all;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 | services:
3 |
4 | http-proxy:
5 | build:
6 | context: infra/nginx
7 | restart: unless-stopped
8 | depends_on:
9 | - app
10 | - docs
11 | ports:
12 | - "80:80"
13 |
14 | app:
15 | build:
16 | context: .
17 | target: dev
18 | restart: unless-stopped
19 | volumes:
20 | - .:/app
21 | depends_on:
22 | - database
23 | environment:
24 | - COMPOSER_CACHE_DIR=/app/var/composer
25 | env_file:
26 | - .env.example
27 |
28 | docs:
29 | image: swaggerapi/swagger-ui
30 | volumes:
31 | - ./docs/todos.openapi.yml:/app/todos.openapi.yml
32 | environment:
33 | - LAYOUT=BaseLayout
34 | - SWAGGER_JSON=/app/todos.openapi.yml
35 |
36 | database:
37 | image: postgres:11-alpine
38 | restart: unless-stopped
39 | ports:
40 | - "5432:5432"
41 | environment:
42 | LC_ALL: C.UTF-8
43 | POSTGRES_USER: app
44 | POSTGRES_PASSWORD: password
45 | POSTGRES_DB: app
46 |
47 |
--------------------------------------------------------------------------------
/bin/cli:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | get(ToDoDataMapper::class)->initSchema();
20 | echo 'Schema created', PHP_EOL;
21 | break;
22 | case $command === 'drop-schema':
23 | $app->get(ToDoDataMapper::class)->dropSchema();
24 | echo 'Schema dropped', PHP_EOL;
25 | break;
26 | case $command === 'prune-cache':
27 | system('rm -rfv ' . App::CACHE_DIR);
28 | echo 'Cache pruned', PHP_EOL;
29 | break;
30 | default:
31 | echo
32 | <<todoDataMapper = $todoDataMapper;
23 | }
24 |
25 | /**
26 | * @param Request $request
27 | * @param string[] $args
28 | *
29 | * @return Response
30 | *
31 | * @throws Http\Exception\BadRequestException
32 | * @throws Http\Exception\NotFoundException
33 | */
34 | public function __invoke(Request $request, array $args): Response
35 | {
36 | if (! Uuid::isValid($args['id'])) {
37 | throw new Http\Exception\BadRequestException('Invalid UUID');
38 | }
39 |
40 | $item = $this->todoDataMapper->find($args['id']);
41 |
42 | if ($item === null) {
43 | throw new Http\Exception\NotFoundException('Resource not found');
44 | }
45 |
46 | return new JsonResponse($item);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Http/RouteHandler/ToDoCreate.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $todoDataMapper;
24 | }
25 |
26 | /**
27 | * @param Request $request
28 | * @param string[] $args
29 | *
30 | * @return Response
31 | *
32 | * @throws BadRequestException
33 | * @throws InvalidDataException
34 | */
35 | public function __invoke(Request $request, array $args): Response
36 | {
37 | $requestBody = $request->getParsedBody();
38 |
39 | if (! is_array($requestBody)) {
40 | throw new BadRequestException('Invalid request body');
41 | }
42 |
43 | $item = ToDo::createFromArray($requestBody);
44 |
45 | $this->todoDataMapper->insert($item);
46 |
47 | return new JsonResponse($item, 201);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Http/RouteHandler/ToDoDelete.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $todoDataMapper;
23 | }
24 |
25 | /**
26 | * @param Request $request
27 | * @param string[] $args
28 | *
29 | * @return Response
30 | *
31 | * @throws Http\Exception\BadRequestException
32 | * @throws Http\Exception\NotFoundException
33 | */
34 | public function __invoke(Request $request, array $args): Response
35 | {
36 | if (! Uuid::isValid($args['id'])) {
37 | throw new Http\Exception\BadRequestException('Invalid UUID');
38 | }
39 |
40 | $item = $this->todoDataMapper->find($args['id']);
41 |
42 | if ($item === null) {
43 | throw new Http\Exception\NotFoundException('Resource not found');
44 | }
45 |
46 | $this->todoDataMapper->delete($args['id']);
47 |
48 | return new EmptyResponse();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Config/DependencyInjection.php:
--------------------------------------------------------------------------------
1 | addDefinitions(
22 | [
23 | LoggerInterface::class => DI\factory('\Acme\ToDo\logger'),
24 | Router::class => DI\factory(RouterFactory::class),
25 | PDO::class => DI\factory(PdoFactory::class),
26 | ToDoDataMapper::class => DI\autowire()->lazy(),
27 | BasicAuthentication::class => DI\create()->constructor(
28 | json_decode(env('AUTH_USERS'), true, 512, JSON_THROW_ON_ERROR)
29 | ),
30 | ContentType::class => DI\create()
31 | ->constructor(
32 | array_filter(
33 | ContentType::getDefaultFormats(),
34 | function ($key) {
35 | return $key === 'json';
36 | },
37 | ARRAY_FILTER_USE_KEY
38 | )
39 | )
40 | ->method('useDefault', false),
41 | ]
42 | );
43 |
44 | if (env('CACHE')) {
45 | $builder->enableCompilation(App::CACHE_DIR);
46 | }
47 |
48 | return $builder->build();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/App.php:
--------------------------------------------------------------------------------
1 | get(__CLASS__);
33 | $app->container = $container;
34 |
35 | return $app;
36 | }
37 |
38 | public function __construct(Router $router)
39 | {
40 | $this->router = $router;
41 | }
42 |
43 | public function handle(Request $request): Response
44 | {
45 | try {
46 | $response = $this->router->dispatch($request);
47 | } catch (InvalidDataException $e) {
48 | return new JsonResponse([ 'error' => exception_to_array($e) ], 400);
49 | } catch (HttpException $e) {
50 | return new JsonResponse([ 'error' => exception_to_array($e) ], $e->getStatusCode());
51 | }
52 |
53 | return $response;
54 | }
55 |
56 | public function get($id)
57 | {
58 | return $this->container->get($id);
59 | }
60 |
61 | public function has($id)
62 | {
63 | return $this->container->has($id);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Http/RouteHandler/ToDoUpdate.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $todoDataMapper;
25 | }
26 |
27 | /**
28 | * @param Request $request
29 | * @param string[] $args
30 | *
31 | * @return Response
32 | *
33 | * @throws BadRequestException
34 | * @throws InvalidDataException
35 | * @throws NotFoundException
36 | */
37 | public function __invoke(Request $request, array $args): Response
38 | {
39 | if (! Uuid::isValid($args['id'])) {
40 | throw new BadRequestException('Invalid UUID');
41 | }
42 |
43 | $item = $this->todoDataMapper->find($args['id']);
44 |
45 | if ($item === null) {
46 | throw new NotFoundException('Resource not found');
47 | }
48 |
49 | $requestBody = $request->getParsedBody();
50 |
51 | if (! is_array($requestBody)) {
52 | throw new BadRequestException('Invalid request body');
53 | }
54 |
55 | $item->updateFromArray($requestBody);
56 |
57 | $this->todoDataMapper->update($item);
58 |
59 | return new JsonResponse($item, 200);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Config/RouterFactory.php:
--------------------------------------------------------------------------------
1 | setContainer($container);
22 | $router = new LeagueRouter();
23 | $router->setStrategy($strategy);
24 |
25 | $authMiddleware = $container->get(BasicAuthentication::class);
26 | $contentNegotiationMiddleware = $container->get(ContentType::class);
27 |
28 | Assert::thatAll([ $authMiddleware, $contentNegotiationMiddleware ])->isInstanceOf(Middleware::class);
29 |
30 | $router->middleware($contentNegotiationMiddleware);
31 | $router->middleware(new JsonPayload());
32 |
33 | $router->map('GET', '/', RouteHandler\Home::class);
34 |
35 | $router->group('/todos', function (RouteGroup $route) use ($authMiddleware): void {
36 | $route->map('GET', '/', RouteHandler\ToDoList::class);
37 | $route->map('POST', '/', RouteHandler\ToDoCreate::class)->middleware($authMiddleware);
38 | $route->map('GET', '/{id}', RouteHandler\ToDoRead::class);
39 | $route->map('PATCH', '/{id}', RouteHandler\ToDoUpdate::class)->middleware($authMiddleware);
40 | $route->map('DELETE', '/{id}', RouteHandler\ToDoDelete::class)->middleware($authMiddleware);
41 | });
42 |
43 | return $router;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/AppUnitTest.php:
--------------------------------------------------------------------------------
1 | router = $this->createMock(Router::class);
29 | $this->SUT = new App($this->router);
30 | }
31 |
32 | /**
33 | * @dataProvider exceptionProvider
34 | */
35 | public function testItHandlesHttpAndDomainExceptions(Exception $exception): void
36 | {
37 | $request = new ServerRequest();
38 |
39 | $this->router
40 | ->method('dispatch')
41 | ->willThrowException($exception)
42 | ;
43 |
44 | $response = $this->SUT->handle($request);
45 | $expectedStatusCode = $exception instanceof HttpException ? $exception->getStatusCode(): 400;
46 | assertSame($expectedStatusCode, $response->getStatusCode());
47 | assertInstanceOf(JsonResponse::class, $response); /** @var JsonResponse $response */
48 | $payload = $response->getPayload();
49 | assertArrayHasKey('error', $payload);
50 | assertEquals(exception_to_array($exception), $payload['error']);
51 | }
52 |
53 | /**
54 | * @return array[]
55 | */
56 | public function exceptionProvider(): array
57 | {
58 | return [
59 | [ new HttpException(401, 'foo') ],
60 | [ new InvalidDataException() ],
61 | ];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Http/RouteHandler/ToDoList.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $todoDataMapper;
24 | }
25 |
26 | /**
27 | * @param Request $request
28 | * @param string[] $args
29 | *
30 | * @return Response
31 | *
32 | * @throws BadRequestException
33 | */
34 | public function __invoke(Request $request, array $args): Response
35 | {
36 | $query = $request->getQueryParams();
37 | $search = $query['search'] ?? '';
38 |
39 | $page = (int) ($query['page'] ?? 1);
40 | if ($page < 1) {
41 | throw new BadRequestException('Page must be greater than 0');
42 | }
43 |
44 | $pageSize = (int) ($query['pageSize'] ?? ToDoDataMapper::DEFAULT_PAGE_SIZE);
45 |
46 | if ($pageSize < 1 || $pageSize > self::MAX_PAGE_SIZE) {
47 | throw new BadRequestException(sprintf('Page size must be between 1 and %s', self::MAX_PAGE_SIZE));
48 | }
49 |
50 | $totalPages = $this->todoDataMapper->countPages($search, $pageSize);
51 | $items = $this->todoDataMapper->getAll($search, $page, $pageSize);
52 |
53 | $prev = $page > 1 ? min($page - 1, $totalPages) : null;
54 | $next = $page < $totalPages ? $page + 1 : null;
55 |
56 | return new JsonResponse(compact('items', 'totalPages', 'prev', 'next'));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/test/Http/RouteHandler/ToDoCreateUnitTest.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $this->createMock(ToDoDataMapper::class);
30 | $this->SUT = new ToDoCreate($this->todoDataMapper);
31 | }
32 |
33 | public function testSuccess(): void
34 | {
35 | $data = [
36 | 'name' => 'foo',
37 | 'dueFor' => (new DateTimeImmutable())->format(DATE_FORMAT),
38 | 'doneAt' => (new DateTimeImmutable())->format(DATE_FORMAT),
39 | ];
40 | $request = (new ServerRequest())->withParsedBody($data);
41 |
42 | $this->todoDataMapper->expects(once())->method('insert');
43 |
44 | $response = $this->SUT->__invoke($request, []);
45 |
46 | assertSame(201, $response->getStatusCode());
47 | assertInstanceOf(JsonResponse::class, $response); /** @var JsonResponse $response */
48 | assertInstanceOf(ToDo::class, $response->getPayload());
49 | /** @var ToDo $item */
50 | $item = $response->getPayload();
51 | assertSame($data['name'], $item->getName());
52 | assertSame($data['dueFor'], $item->getDueForAsString());
53 | assertSame($data['doneAt'], $item->getDoneAtAsString());
54 | }
55 |
56 | public function testInvalidRequestBody(): void
57 | {
58 | $request = new ServerRequest();
59 |
60 | $this->expectException(BadRequestException::class);
61 | $this->expectExceptionMessage('Invalid request body');
62 |
63 | $this->SUT->__invoke($request, []);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/test/Http/RouteHandler/ToDoDeleteUnitTest.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $this->createMock(ToDoDataMapper::class);
29 | $this->SUT = new ToDoDelete($this->todoDataMapper);
30 | }
31 |
32 | public function testSuccess(): void
33 | {
34 | $item = new ToDo('foo');
35 | $request = new ServerRequest();
36 | $args = ['id' => Uuid::NIL];
37 |
38 | $this->todoDataMapper
39 | ->method('find')
40 | ->with($args['id'])
41 | ->willReturn($item)
42 | ;
43 |
44 | $this->todoDataMapper->expects(once())->method('delete')->with($args['id']);
45 |
46 | $response = $this->SUT->__invoke($request, $args);
47 |
48 | assertSame(204, $response->getStatusCode());
49 | }
50 |
51 | public function testInvalidUUID(): void
52 | {
53 | $request = new ServerRequest();
54 | $args = ['id' => 'foo'];
55 |
56 | $this->expectException(BadRequestException::class);
57 | $this->expectExceptionMessage('Invalid UUID');
58 |
59 | $this->SUT->__invoke($request, $args);
60 | }
61 |
62 | public function testNotFound(): void
63 | {
64 | $request = new ServerRequest();
65 | $args = ['id' => Uuid::NIL];
66 |
67 | $this->todoDataMapper
68 | ->method('find')
69 | ->with($args['id'])
70 | ->willReturn(null)
71 | ;
72 |
73 | $this->expectException(NotFoundException::class);
74 |
75 | $this->SUT->__invoke($request, $args);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/test/Http/RouteHandler/ToDoReadUnitTest.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $this->createMock(ToDoDataMapper::class);
30 | $this->SUT = new ToDoRead($this->todoDataMapper);
31 | }
32 |
33 | public function testSuccess(): void
34 | {
35 | $item = new ToDo('foo');
36 | $request = new ServerRequest();
37 | $args = ['id' => Uuid::NIL];
38 |
39 | $this->todoDataMapper
40 | ->method('find')
41 | ->with($args['id'])
42 | ->willReturn($item)
43 | ;
44 |
45 | $response = $this->SUT->__invoke($request, $args);
46 |
47 | assertSame(200, $response->getStatusCode());
48 | assertInstanceOf(JsonResponse::class, $response); /** @var JsonResponse $response */
49 | assertEquals($item, $response->getPayload());
50 | }
51 |
52 | public function testInvalidUUID(): void
53 | {
54 | $request = new ServerRequest();
55 | $args = ['id' => 'foo'];
56 |
57 | $this->expectException(BadRequestException::class);
58 | $this->expectExceptionMessage('Invalid UUID');
59 |
60 | $this->SUT->__invoke($request, $args);
61 | }
62 |
63 | public function testNotFound(): void
64 | {
65 | $request = new ServerRequest();
66 | $args = ['id' => Uuid::NIL];
67 |
68 | $this->todoDataMapper
69 | ->method('find')
70 | ->with($args['id'])
71 | ->willReturn(null)
72 | ;
73 |
74 | $this->expectException(NotFoundException::class);
75 |
76 | $this->SUT->__invoke($request, $args);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/test/AppIntegrationTest.php:
--------------------------------------------------------------------------------
1 | expectNotToPerformAssertions();
15 |
16 | App::bootstrap();
17 | }
18 |
19 | public function testRootRequest(): void
20 | {
21 | $app = App::bootstrap();
22 | $request = (new ServerRequestFactory())->createServerRequest('GET', '/')->withHeader('accept', '*/*');
23 | $response = $app->handle($request);
24 | assertSame(200, $response->getStatusCode());
25 | }
26 |
27 | public function testContentNegotiation(): void
28 | {
29 | $app = App::bootstrap();
30 | $request = (new ServerRequestFactory())->createServerRequest('GET', '/')->withHeader('accept', 'text/html');
31 | $response = $app->handle($request);
32 | assertSame(406, $response->getStatusCode());
33 | }
34 |
35 | /**
36 | * @dataProvider authRequiredRequestProvider
37 | */
38 | public function testAuthenticationFailure(Request $request): void
39 | {
40 | $app = App::bootstrap();
41 | $response = $app->handle($request);
42 | assertSame(401, $response->getStatusCode());
43 | }
44 |
45 | /**
46 | * @dataProvider authRequiredRequestProvider
47 | */
48 | public function testAuthenticationSuccess(Request $request): void
49 | {
50 | Env::$options |= Env::USE_ENV_ARRAY;
51 | $_ENV['AUTH_USERS'] = '{ "john": "doe" }';
52 | $app = App::bootstrap();
53 | $request = $request->withHeader('Authorization', 'Basic ' . base64_encode('john:doe'));
54 | $response = $app->handle($request);
55 | assertNotSame(401, $response->getStatusCode());
56 | }
57 |
58 | /**
59 | * @return array[]
60 | */
61 | public function authRequiredRequestProvider(): array
62 | {
63 | $serverRequestFactory = new ServerRequestFactory();
64 |
65 | return [
66 | [ $serverRequestFactory->createServerRequest('POST', '/todos')
67 | ->withHeader('accept', '*/*') ],
68 | [ $serverRequestFactory->createServerRequest('PATCH', '/todos/foo')
69 | ->withHeader('accept', '*/*') ],
70 | [ $serverRequestFactory->createServerRequest('DELETE', '/todos/foo')
71 | ->withHeader('accept', '*/*') ],
72 | ];
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/_functions.php:
--------------------------------------------------------------------------------
1 | $exception->getMessage(),
22 | 'code' => $exception->getCode(),
23 | ];
24 |
25 | if ($exception instanceof InvalidDataException) {
26 | $output['details'] = $exception->getDetails();
27 | }
28 |
29 | if (env('DEBUG') === true) {
30 | $output = array_merge($output, [
31 | 'type' => get_class($exception),
32 | 'file' => $exception->getFile(),
33 | 'line' => $exception->getLine(),
34 | 'trace' => explode("\n", $exception->getTraceAsString()),
35 | 'previous' => [],
36 | ]);
37 | }
38 |
39 | return $output;
40 | };
41 |
42 | $result = $singleToArray($exception);
43 | $last = $exception;
44 |
45 | while ($last = $last->getPrevious()) {
46 | $result['previous'][] = $singleToArray($last);
47 | }
48 |
49 | return $result;
50 | }
51 |
52 | function php_error_handler(int $errno, string $errstr, string $errfile, int $errline): void
53 | {
54 | if (! (error_reporting() & $errno)) {
55 | return; // error_reporting does not include this error
56 | }
57 |
58 | throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
59 | }
60 |
61 | function logger(Logger $newLogger = null): Logger
62 | {
63 | static $logger;
64 |
65 | if ($newLogger) {
66 | $logger = $newLogger;
67 | }
68 |
69 | if (! $logger) {
70 | $logger = (new Config\LoggerFactory())();
71 | }
72 |
73 | return $logger;
74 | }
75 |
76 | function now(Clock $newClock = null): DateTimeImmutable
77 | {
78 | static $clock;
79 |
80 | if ($newClock) {
81 | $clock = $newClock;
82 | }
83 |
84 | if (! $clock) {
85 | $clock = new SystemClock();
86 | }
87 |
88 | return $clock->now();
89 | }
90 |
91 | function datetime_from_string(string $dateTime): DateTimeImmutable
92 | {
93 | $dateTime = DateTimeImmutable::createFromFormat(DATE_FORMAT, $dateTime);
94 |
95 | Assert::that($dateTime)->notSame(false);
96 |
97 | return $dateTime;
98 | }
99 |
100 | const DATE_FORMAT = "Y-m-d\TH:i:s.uO"; // ISO8601 with milliseconds
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | stages:
2 | - build
3 | - test
4 | - deploy
5 |
6 | image:
7 | name: docker/compose:1.24.0
8 | entrypoint: [""]
9 |
10 | services:
11 | - docker:18.09-dind
12 |
13 | variables:
14 | DOCKER_HOST: tcp://docker:2375
15 | APP_IMAGE: $CI_REGISTRY_IMAGE
16 | APP_DEV_IMAGE: ${CI_REGISTRY_IMAGE}/dev
17 | APP_BASE_IMAGE: ${CI_REGISTRY_IMAGE}/base
18 | HTTP_PROXY_IMAGE: ${CI_REGISTRY_IMAGE}/http-proxy
19 |
20 |
21 | ### BUILD
22 |
23 | build:
24 | stage: build
25 | image: docker:18.09
26 | before_script:
27 | - docker info
28 | - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
29 | - apk add --no-cache parallel
30 | script:
31 | - "parallel docker pull ::: \
32 | ${APP_BASE_IMAGE}:${CI_COMMIT_REF_SLUG} \
33 | ${APP_DEV_IMAGE}:${CI_COMMIT_REF_SLUG} \
34 | ${APP_IMAGE}:${CI_COMMIT_REF_SLUG} \
35 | ${HTTP_PROXY_IMAGE}:${CI_COMMIT_REF_SLUG} \
36 | || true"
37 | - docker build . -t $APP_BASE_IMAGE:$CI_COMMIT_REF_SLUG --target base
38 | --cache-from $APP_BASE_IMAGE:$CI_COMMIT_REF_SLUG
39 | - docker build . -t $APP_DEV_IMAGE:$CI_COMMIT_REF_SLUG --target dev
40 | --cache-from $APP_BASE_IMAGE:$CI_COMMIT_REF_SLUG
41 | --cache-from $APP_DEV_IMAGE:$CI_COMMIT_REF_SLUG
42 | - docker build . -t $APP_IMAGE:$CI_COMMIT_REF_SLUG
43 | --cache-from $APP_BASE_IMAGE:$CI_COMMIT_REF_SLUG
44 | --cache-from $APP_DEV_IMAGE:$CI_COMMIT_REF_SLUG
45 | --cache-from $APP_IMAGE:$CI_COMMIT_REF_SLUG
46 | - docker build infra/nginx -t $HTTP_PROXY_IMAGE:$CI_COMMIT_REF_SLUG
47 | - "parallel docker push ::: \
48 | ${APP_IMAGE}:${CI_COMMIT_REF_SLUG} \
49 | ${APP_DEV_IMAGE}:${CI_COMMIT_REF_SLUG} \
50 | ${APP_BASE_IMAGE}:${CI_COMMIT_REF_SLUG} \
51 | ${HTTP_PROXY_IMAGE}:${CI_COMMIT_REF_SLUG} \
52 | "
53 |
54 | ### TEST
55 |
56 | .template: &test
57 | stage: test
58 | before_script:
59 | - cp infra/docker-compose.ci.override.yml docker-compose.override.yml
60 | - cp .env.example .env
61 |
62 | # we need to resort to yaml processing, because, well... yaml.
63 | - apk add --no-cache curl
64 | - curl -sSL https://github.com/mikefarah/yq/releases/download/2.2.1/yq_linux_amd64 -o /usr/local/bin/yq
65 | && chmod +x /usr/local/bin/yq
66 | - yq d -i docker-compose.yml services.app.volumes
67 |
68 | - docker info
69 | - docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
70 | - docker-compose config
71 |
72 | test:
73 | <<: *test
74 | script:
75 | - docker-compose pull
76 | - docker-compose up -d || (docker-compose ps && docker-compose logs app && exit 1)
77 | - docker-compose exec -T app sh -c 'wait-for $DB_HOST:$DB_PORT -t 60 -- phpunit'
78 |
79 | coding style:
80 | <<: *test
81 | script:
82 | - docker-compose pull app
83 | - docker-compose run --rm --no-deps -T app php-cs-fixer fix -v --dry-run
84 | allow_failure: true
85 |
86 | static analysis:
87 | <<: *test
88 | script:
89 | - docker-compose pull app
90 | - docker-compose run --rm --no-deps -T app phpstan analyse src test
91 |
--------------------------------------------------------------------------------
/test/Http/RouteHandler/ToDoUpdateUnitTest.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $this->createMock(ToDoDataMapper::class);
32 | $this->SUT = new ToDoUpdate($this->todoDataMapper);
33 | }
34 |
35 | public function testSuccess(): void
36 | {
37 | $item = new ToDo('foo');
38 | $data = [
39 | 'name' => 'bar',
40 | 'dueFor' => (new DateTimeImmutable())->format(DATE_FORMAT),
41 | 'doneAt' => (new DateTimeImmutable())->format(DATE_FORMAT),
42 | ];
43 | $request = (new ServerRequest())->withParsedBody($data);
44 | $args = ['id' => Uuid::NIL];
45 |
46 | $this->todoDataMapper
47 | ->method('find')
48 | ->with($args['id'])
49 | ->willReturn($item)
50 | ;
51 |
52 | $this->todoDataMapper
53 | ->expects(once())
54 | ->method('update')
55 | ->with($item)
56 | ;
57 |
58 | $response = $this->SUT->__invoke($request, $args);
59 |
60 | assertSame(200, $response->getStatusCode());
61 | assertInstanceOf(JsonResponse::class, $response); /** @var JsonResponse $response */
62 | assertEquals($item, $response->getPayload());
63 | assertSame($data['name'], $item->getName());
64 | assertSame($data['dueFor'], $item->getDueForAsString());
65 | assertSame($data['doneAt'], $item->getDoneAtAsString());
66 | }
67 |
68 | public function testInvalidUUID(): void
69 | {
70 | $request = new ServerRequest();
71 | $args = ['id' => 'foo'];
72 |
73 | $this->expectException(BadRequestException::class);
74 | $this->expectExceptionMessage('Invalid UUID');
75 |
76 | $this->SUT->__invoke($request, $args);
77 | }
78 |
79 | public function testNotFound(): void
80 | {
81 | $request = new ServerRequest();
82 | $args = ['id' => Uuid::NIL];
83 |
84 | $this->todoDataMapper
85 | ->method('find')
86 | ->with($args['id'])
87 | ->willReturn(null)
88 | ;
89 |
90 | $this->expectException(NotFoundException::class);
91 |
92 | $this->SUT->__invoke($request, $args);
93 | }
94 |
95 | public function testInvalidRequestBody(): void
96 | {
97 | $item = new ToDo('foo');
98 | $request = new ServerRequest();
99 | $args = ['id' => Uuid::NIL];
100 |
101 | $this->todoDataMapper
102 | ->method('find')
103 | ->with($args['id'])
104 | ->willReturn($item)
105 | ;
106 |
107 | $this->expectException(BadRequestException::class);
108 | $this->expectExceptionMessage('Invalid request body');
109 |
110 | $this->SUT->__invoke($request, $args);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/test/Model/ToDoDataMapperIntegrationTest.php:
--------------------------------------------------------------------------------
1 | SUT = $app->get(ToDoDataMapper::class);
19 | $this->SUT->dropSchema();
20 | $this->SUT->initSchema();
21 | }
22 |
23 | public function testAddAndFind(): void
24 | {
25 | $item = new ToDo('foo');
26 |
27 | assertNull($this->SUT->find($item->getId()));
28 |
29 | $this->SUT->insert($item);
30 |
31 | assertEquals($item, $this->SUT->find($item->getId()));
32 | }
33 |
34 | public function testAddAndGetall(): void
35 | {
36 | $item1 = new ToDo('foo');
37 | $item2 = new ToDo('bar');
38 |
39 | $this->SUT->insert($item1);
40 | $this->SUT->insert($item2);
41 |
42 | $items = $this->SUT->getAll();
43 | assertEquals([ $item1, $item2 ], $items);
44 | }
45 |
46 | public function testAddUpdateAndFind(): void
47 | {
48 | $item = new ToDo('foo');
49 |
50 | $this->SUT->insert($item);
51 |
52 | $item->updateFromArray([
53 | 'name' => 'bar',
54 | 'prep_time_mins' => 6,
55 | 'difficulty' => 2,
56 | 'vegetarian' => true
57 | ]);
58 |
59 | $this->SUT->update($item);
60 |
61 | assertEquals($item, $this->SUT->find($item->getId()));
62 | }
63 |
64 | public function testAddAndDelete(): void
65 | {
66 | $item = new ToDo('foo');
67 |
68 | $this->SUT->insert($item);
69 |
70 | assertEquals($item, $this->SUT->find($item->getId()));
71 |
72 | $this->SUT->delete($item->getId());
73 |
74 | assertNull($this->SUT->find($item->getId()));
75 | }
76 |
77 | public function testFullTextSearch(): void
78 | {
79 | $item1 = new ToDo('foo');
80 | $item2 = new ToDo('bar');
81 | $item3 = new ToDo('bar baz');
82 |
83 | $this->SUT->insert($item1);
84 | $this->SUT->insert($item2);
85 | $this->SUT->insert($item3);
86 |
87 | assertEquals([$item1], $this->SUT->getAll('foo'));
88 | assertEquals([$item2, $item3], $this->SUT->getAll('bar'));
89 | assertEquals([$item3], $this->SUT->getAll('baz'));
90 | }
91 |
92 | public function testPagination(): void
93 | {
94 | $item1 = new ToDo('foo');
95 | $item2 = new ToDo('bar');
96 |
97 | $this->SUT->insert($item1);
98 | $this->SUT->insert($item2);
99 |
100 | assertEquals([$item1, $item2], $this->SUT->getAll());
101 | assertEquals([$item2], $this->SUT->getAll('', $page=2, $pageSize=1));
102 | assertEquals([], $this->SUT->getAll('', $page=2));
103 | }
104 |
105 | public function testCountPages(): void
106 | {
107 | $item1 = new ToDo('foo');
108 | $item2 = new ToDo('bar');
109 | $item3 = new ToDo('bar baz');
110 | $item4 = new ToDo('bat');
111 |
112 | $this->SUT->insert($item1);
113 | $this->SUT->insert($item2);
114 | $this->SUT->insert($item3);
115 | $this->SUT->insert($item4);
116 |
117 | assertSame(1, $this->SUT->countPages());
118 | assertSame(2, $this->SUT->countPages('bar', $pageSize=1));
119 | assertSame(2, $this->SUT->countPages('', $pageSize=2));
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/test/Http/RouteHandler/ToDoListUnitTest.php:
--------------------------------------------------------------------------------
1 | todoDataMapper = $this->createMock(ToDoDataMapper::class);
28 | $this->SUT = new ToDoList($this->todoDataMapper);
29 | }
30 |
31 | public function testSuccess(): void
32 | {
33 | $item1 = new ToDo('foo');
34 | $item2 = new ToDo('bar');
35 | $request = new ServerRequest();
36 |
37 | $records = [ $item1, $item2 ];
38 |
39 | $this->todoDataMapper
40 | ->expects(once())
41 | ->method('getAll')
42 | ->willReturn($records)
43 | ;
44 |
45 | $response = $this->SUT->__invoke($request, []);
46 |
47 | assertSame(200, $response->getStatusCode());
48 | assertInstanceOf(JsonResponse::class, $response); /** @var JsonResponse $response */
49 | $payload = $response->getPayload();
50 | assertArrayHasKey('items', $payload);
51 | assertEquals($records, $payload['items']);
52 | }
53 |
54 | public function testPagination(): void
55 | {
56 | $query = [
57 | 'search' => 'foo',
58 | 'page' => 2,
59 | 'pageSize' => 10,
60 | ];
61 | $request = (new ServerRequest())->withQueryParams($query);
62 |
63 | $this->todoDataMapper
64 | ->expects(once())
65 | ->method('getAll')
66 | ->with($query['search'], $query['page'], $query['pageSize'])
67 | ;
68 |
69 | $this->todoDataMapper
70 | ->expects(once())
71 | ->method('countPages')
72 | ->with($query['search'], $query['pageSize'])
73 | ->willReturn(3)
74 | ;
75 |
76 | $response = $this->SUT->__invoke($request, []);
77 | assertSame(200, $response->getStatusCode());
78 | assertInstanceOf(JsonResponse::class, $response); /** @var JsonResponse $response */
79 | $payload = $response->getPayload();
80 | assertArrayHasKey('prev', $payload);
81 | assertArrayHasKey('next', $payload);
82 | assertArrayHasKey('totalPages', $payload);
83 | assertSame(1, $payload['prev']);
84 | assertSame(3, $payload['next']);
85 | assertSame(3, $payload['totalPages']);
86 | }
87 |
88 | public function testInvalidPageNumber(): void
89 | {
90 | $request = (new ServerRequest())->withQueryParams([ 'page' => 0 ]);
91 |
92 | $this->expectException(BadRequestException::class);
93 | $this->expectExceptionMessage('Page must be greater than 0');
94 |
95 | $this->SUT->__invoke($request, []);
96 | }
97 |
98 | public function testInvalidPageSize(): void
99 | {
100 | $request = (new ServerRequest())->withQueryParams([ 'pageSize' => 0 ]);
101 |
102 | $this->expectException(BadRequestException::class);
103 | $this->expectExceptionMessage('Page size must be between 1 and 100');
104 |
105 | $this->SUT->__invoke($request, []);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG COMPOSER_FLAGS="--no-interaction --no-suggest --no-progress --ansi"
2 |
3 | ###### base stage ######
4 | FROM php:7.4-fpm-alpine as base
5 |
6 | ARG COMPOSER_FLAGS
7 | ARG COMPOSER_VERSION="1.10.5"
8 | ARG PHP_FPM_HEALTHCHECK_VERSION="v0.5.0"
9 | ARG WAIT_FOR_IT_VERSION="c096cface5fbd9f2d6b037391dfecae6fde1362e"
10 |
11 | # global dependencies
12 | RUN apk add --no-cache bash fcgi postgresql-dev
13 |
14 | # php extensions
15 | RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS \
16 | && docker-php-ext-install -j$(getconf _NPROCESSORS_ONLN) pdo_pgsql \
17 | && apk del .phpize-deps
18 |
19 | # local dependencies
20 | RUN curl -fsSL https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --version=$COMPOSER_VERSION && \
21 | curl -fsSL https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/$PHP_FPM_HEALTHCHECK_VERSION/php-fpm-healthcheck \
22 | -o /usr/local/bin/php-fpm-healthcheck && chmod +x /usr/local/bin/php-fpm-healthcheck && \
23 | curl -fsSL https://raw.githubusercontent.com/vishnubob/wait-for-it/$WAIT_FOR_IT_VERSION/wait-for-it.sh \
24 | -o /usr/local/bin/wait-for && chmod +x /usr/local/bin/wait-for
25 |
26 | # composer environment
27 | ENV COMPOSER_HOME=/opt/composer
28 | ENV COMPOSER_ALLOW_SUPERUSER=1
29 | ENV PATH=${PATH}:${COMPOSER_HOME}/vendor/bin:/app/vendor/bin:/app/bin
30 |
31 | # global composer dependencies
32 | RUN composer global require hirak/prestissimo $COMPOSER_FLAGS
33 |
34 | # custom php config
35 | COPY infra/php/php.ini /usr/local/etc/php/
36 | COPY infra/php/php-fpm.conf /usr/local/etc/php-fpm.d/zz-custom.conf
37 |
38 | WORKDIR /app
39 |
40 | ###### dev stage ######
41 | FROM base as dev
42 |
43 | ARG COMPOSER_FLAGS
44 | ARG PHP_CS_FIXER_VERSION="v2.16.3"
45 | ARG PHPSTAN_VERSION="0.12.19"
46 | ARG COMPOSER_REQUIRE_CHECKER_VERSION="2.1.0"
47 | ARG XDEBUG_ENABLER_VERSION="facd52cdc1a09fe7e82d6188bb575ed54ab2bc72"
48 | ARG XDEBUG_VERSION="2.9.5"
49 |
50 | # php extensions
51 | RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS \
52 | && pecl install xdebug-$XDEBUG_VERSION \
53 | && apk del .phpize-deps
54 |
55 | # global development deps
56 | RUN apk add --no-cache postgresql-client && \
57 | curl -fsSL https://gist.githubusercontent.com/stefanotorresi/9f48f8c476b17c44d68535630522a2be/raw/$XDEBUG_ENABLER_VERSION/xdebug \
58 | -o /usr/local/bin/xdebug && chmod +x /usr/local/bin/xdebug
59 |
60 | # global composer dependencies
61 | RUN composer global require \
62 | friendsofphp/php-cs-fixer:$PHP_CS_FIXER_VERSION \
63 | phpstan/phpstan:$PHPSTAN_VERSION \
64 | phpstan/phpstan-beberlei-assert \
65 | phpstan/phpstan-phpunit \
66 | maglnet/composer-require-checker:$COMPOSER_REQUIRE_CHECKER_VERSION
67 |
68 | # project composer dependencies
69 | COPY composer.* ./
70 | RUN composer install $COMPOSER_FLAGS --no-scripts --no-autoloader
71 |
72 | # copy project sources
73 | COPY . ./
74 |
75 | # rerun composer to trigger scripts and dump the autoloader
76 | RUN composer install $COMPOSER_FLAGS
77 |
78 |
79 | ###### production stage ######
80 | FROM base
81 |
82 | ARG COMPOSER_FLAGS
83 |
84 | # project composer dependencies
85 | COPY composer.* ./
86 | RUN composer install $COMPOSER_FLAGS --no-scripts --no-autoloader --no-dev
87 |
88 | # copy project sources cherry picking only production files
89 | COPY index.php ./
90 | COPY src ./src
91 |
92 | # rerun composer to trigger scripts and dump the autoloader
93 | RUN composer install $COMPOSER_FLAGS --no-dev --optimize-autoloader
94 |
95 | HEALTHCHECK --interval=30s --timeout=2s CMD php-fpm-healthcheck
96 |
97 | RUN addgroup -S app && adduser -D -G app -S app && chown app:app .
98 | USER app
99 | ENV HOME=/home/app
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ToDo API
2 |
3 | ## Table of Contents
4 |
5 | - [Design](#design)
6 | - [Infrastructure](#infrastructure)
7 | - [First run](#first-run)
8 | - [Dev tools](#dev-tools)
9 | - [Configuration](#configuration)
10 |
11 | ## Design
12 |
13 | The project is a PHP framework-less web application based on [PSR-7](https://www.php-fig.org/psr/psr-7/), [PSR-11](https://www.php-fig.org/psr/psr-11/) and [PSR-15](https://www.php-fig.org/psr/psr-15/) standard interfaces.
14 |
15 | The main third party libraries used as building blocks are:
16 | - [`zendframweork/zend-diactoros`](https://github.com/zendframework/zend-diactoros) for the PSR-7 HTTP messages;
17 | - [`league/route`](https://github.com/thephpleague/route) for a PSR-15 HTTP router;
18 | - [`php-di/php-di`](https://github.com/PHP-DI/PHP-DI) for a PSR-11 Dependency Injection container.
19 |
20 | This allows for a very clean architecture with a straightforward lifecycle:
21 | 1. bootstrap the application
22 | 2. instantiate a request
23 | 3. the application processes the request, producing a response
24 | 4. the response is emitted
25 |
26 | The application composes a PSR-15 middleware pipeline, the main middleware being the router, which forwards the request to the appropriate HTTP handler (see [src/Http/RouteHandler](src/Http/RouteHandler)).
27 |
28 | The handlers themselves interact with the core domain model, which is a simple DBAL for the CRUD operations.
29 |
30 | The resulting overall design is much simpler than a run-of-the-mill MVC framework application.
31 |
32 | The storage backend is abstracted by a very simple DataMapper implementation built upon the native PDO extension.
33 |
34 | The API itself is specified with the OpenAPI 3.0 standard (see [todos.openapi.yml](docs/todos.openapi.yml)), and user readable docs are generated on the fly.
35 |
36 | Additional features like HTTP authentication and content negotiation are implemented via middleware.
37 |
38 | A small CLI is provided to remove some toil.
39 |
40 | ### Infrastructure
41 |
42 | The container setup has its main entry point in the reverse proxy service (NGINX).
43 |
44 | Requests are forwarded to the `index.php` file by explicitly setting FASTCGI parameters, and this allows to get entirely rid of the document root in the main application container.
45 |
46 | The main [Dockerfile](Dockerfile) produces a production-ready, multi-stage image, and the provided `docker-compose.yml` file uses the `dev` stage, which includes development tools like `phpunit`, `php-cs-fixer`, `phpstan` and others.
47 |
48 | The HTTP reverse proxy also forwards requests in the `/docs` path to a `swagger-ui` instance.
49 |
50 | HTTPS traffic is not provided, as something like this would probably live behind a TLS terminating LB.
51 |
52 | A GitLab CI configuration has been included, because I'm very used to CI and I quickly get bothered to run all the checks manually... ;)
53 |
54 |
55 | ## First run
56 |
57 | Simply spin it up with `docker-compose`, wait for the database to be ready, and and create a database schema with the provided CLI:
58 |
59 | ```
60 | docker-compose up -d
61 | docker-compose exec app wait-for database:5432
62 | docker-compose exec app cli create-schema
63 | ```
64 |
65 | The application will be exposed at [http://localhost](http://localhost).
66 | API docs can be reached at [http://localhost/docs](http://localhost/docs).
67 |
68 | ### Dev tools
69 |
70 | There are two suites of tests provided, plus configurations for coding style enforcement and static analysis.
71 |
72 | ```
73 | docker-compose exec app phpunit --testsuite unit
74 | docker-compose exec app phpunit --testsuite integration
75 | docker-compose exec app phpstan analyse src test
76 | docker-compose exec app php-cs-fixer fix
77 | ```
78 |
79 | Note: the integration tests will wipe out the database!
80 |
81 | ### Configuration
82 |
83 | Configuration is performed via the environment variables, which should be loaded into the app container with `docker-compose`.
84 |
85 | [Defaults](.env.example) are provided so that everything should work out of the box.
86 |
87 | Further customization can be done by providing a `docker-compose.override.yml` file, which is ignored by the VCS.
88 |
89 | For example:
90 | ```yaml
91 | # docker-compose.override.yml
92 | version: '3.7'
93 | services:
94 | app:
95 | environment:
96 | - DEBUG=false
97 | ```
98 |
--------------------------------------------------------------------------------
/test/Model/ToDoUnitTest.php:
--------------------------------------------------------------------------------
1 | getCreatedAt());
26 | }
27 |
28 | /**
29 | * @dataProvider typeSafeInvalidDataProvider
30 | *
31 | * @param mixed $name
32 | * @param mixed $dueFor
33 | * @param mixed $doneAt
34 | * @param string[] $invalidProperties
35 | */
36 | public function testValidation($name, $dueFor, $doneAt, array $invalidProperties): void
37 | {
38 | try {
39 | new ToDo($name, $dueFor, $doneAt);
40 | } catch (InvalidDataException $e) {
41 | }
42 |
43 | assertTrue(isset($e));
44 | assertEquals($invalidProperties, array_keys($e->getDetails()), 'Expected invalid properties don\'t match');
45 | }
46 |
47 | /**
48 | * @dataProvider invalidDataProvider
49 | *
50 | * @param mixed $name
51 | * @param mixed $dueFor
52 | * @param mixed $doneAt
53 | * @param string[] $invalidProperties
54 | */
55 | public function testValidationFromArray($name, $dueFor, $doneAt, array $invalidProperties): void
56 | {
57 | try {
58 | ToDo::createFromArray(compact('name', 'dueFor', 'doneAt'));
59 | } catch (InvalidDataException $e) {
60 | }
61 |
62 | assertTrue(isset($e));
63 | assertEquals($invalidProperties, array_keys($e->getDetails()), 'Expected invalid properties don\'t match');
64 | }
65 |
66 | /**
67 | * @dataProvider invalidDataProvider
68 | *
69 | * @param mixed $name
70 | * @param mixed $dueFor
71 | * @param mixed $doneAt
72 | * @param string[] $invalidProperties
73 | */
74 | public function testValidationOnUpdate($name, $dueFor, $doneAt, array $invalidProperties): void
75 | {
76 | $item = new ToDo('foo');
77 |
78 | try {
79 | $item->updateFromArray(compact('name', 'dueFor', 'doneAt'));
80 | } catch (InvalidDataException $e) {
81 | }
82 |
83 | assertTrue(isset($e));
84 | assertEquals($invalidProperties, array_keys($e->getDetails()), 'Expected invalid properties don\'t match');
85 | }
86 |
87 | /**
88 | * @return array[]
89 | */
90 | public function invalidDataProvider(): array
91 | {
92 | return [
93 | [ '', '', '', [ 'name', 'dueFor', 'doneAt' ] ],
94 | [ '', $this->createDateTimeString(), $this->createDateTimeString(), [ 'name' ] ],
95 | [ 'foo', '', $this->createDateTimeString(), [ 'dueFor' ] ],
96 | [ 'foo', '1970-01-01', $this->createDateTimeString(), [ 'dueFor' ] ],
97 | [ 'foo', $this->createDateTimeString(), '', [ 'doneAt' ] ],
98 | [ 'foo', $this->createDateTimeString(), '1970-01-01', [ 'doneAt' ] ],
99 | ];
100 | }
101 |
102 | /**
103 | * @return array[]
104 | */
105 | public function typeSafeInvalidDataProvider(): array
106 | {
107 | return [
108 | [ '', new DateTimeImmutable('@0'), new DateTimeImmutable('@0'), [ 'name'] ],
109 | [ ' ', new DateTimeImmutable('@0'), new DateTimeImmutable('@0'), [ 'name'] ],
110 | ];
111 | }
112 |
113 | public function testMarkDone(): void
114 | {
115 | $createdAt = new DateTimeImmutable('@0');
116 | now(new FrozenClock($createdAt));
117 | $item = new ToDo('foo');
118 |
119 | $doneAt = new DateTimeImmutable('@1');
120 | now(new FrozenClock($doneAt));
121 | $item->markDone();
122 | assertEquals($doneAt, $item->getDoneAt());
123 | }
124 |
125 | private function createDateTimeString(int $timestamp = 0): string
126 | {
127 | return (new DateTimeImmutable("@$timestamp"))->format(DATE_FORMAT);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/Model/ToDo.php:
--------------------------------------------------------------------------------
1 | id = Uuid::uuid4();
34 | $this->createdAt = now();
35 |
36 | $this->validate(compact('name'));
37 |
38 | $this->name = $name;
39 | $this->dueFor = $dueFor;
40 | $this->doneAt = $doneAt;
41 | }
42 |
43 | public function getId(): string
44 | {
45 | return $this->id->toString();
46 | }
47 |
48 | public function withId(string $id): self
49 | {
50 | $new = clone $this;
51 | $new->id = Uuid::fromString($id);
52 |
53 | return $new;
54 | }
55 |
56 | public function withCreatedAt(string $createdAt): self
57 | {
58 | $new = clone $this;
59 | $new->createdAt = datetime_from_string($createdAt);
60 |
61 | return $new;
62 | }
63 |
64 | public function getName(): string
65 | {
66 | return $this->name;
67 | }
68 |
69 | public function getCreatedAt(): DateTimeImmutable
70 | {
71 | return $this->createdAt;
72 | }
73 |
74 | public function getCreatedAtAsString(): string
75 | {
76 | return $this->createdAt->format(DATE_FORMAT);
77 | }
78 |
79 | public function getDueFor(): ?DateTimeImmutable
80 | {
81 | return $this->dueFor;
82 | }
83 |
84 | public function getDueForAsString(): string
85 | {
86 | return $this->dueFor ? $this->dueFor->format(DATE_FORMAT) : '';
87 | }
88 |
89 | public function markDone(): void
90 | {
91 | $this->doneAt = now();
92 | }
93 |
94 | public function getDoneAt(): ?DateTimeImmutable
95 | {
96 | return $this->doneAt;
97 | }
98 |
99 | public function getDoneAtAsString(): string
100 | {
101 | return $this->doneAt ? $this->doneAt->format(DATE_FORMAT): '';
102 | }
103 |
104 | public function isDone(): bool
105 | {
106 | return $this->doneAt !== null;
107 | }
108 |
109 | /**
110 | * @return mixed[]
111 | */
112 | public function jsonSerialize(): array
113 | {
114 | return [
115 | 'id' => $this->getId(),
116 | 'name' => $this->name,
117 | 'createdAt' => $this->getCreatedAtAsString(),
118 | 'dueFor' => $this->getDueForAsString() ?: null,
119 | 'doneAt' => $this->getDoneAtAsString() ?: null,
120 | 'isDone' => $this->isDone(),
121 | ];
122 | }
123 |
124 | /**
125 | * This method is intended as a type-unsafe alternative to the constructor
126 | *
127 | * @param mixed[] $data
128 | *
129 | * @throws InvalidDataException
130 | */
131 | public static function createFromArray(array $data): self
132 | {
133 | /**
134 | * we use this to avoid double validation.
135 | * @var self $new
136 | */
137 | $new = (new Instantiator)->instantiate(__CLASS__);
138 |
139 | $new->id = Uuid::uuid4();
140 | $new->createdAt = now();
141 | $new->dueFor = null;
142 | $new->doneAt = null;
143 | $new->updateFromArray($data);
144 |
145 | return $new;
146 | }
147 |
148 | /**
149 | * @param mixed[] $data
150 | * @throws InvalidDataException
151 | */
152 | public function updateFromArray(array $data): void
153 | {
154 | $this->validate($data);
155 |
156 | $this->name = $data['name'] ?? $this->name;
157 | $this->dueFor = isset($data['dueFor']) ? datetime_from_string($data['dueFor']) : $this->dueFor;
158 | $this->doneAt = isset($data['doneAt']) ? datetime_from_string($data['doneAt']) : $this->doneAt;
159 | }
160 |
161 | /**
162 | * @param mixed[] $data
163 | *
164 | * @throws InvalidDataException
165 | */
166 | private function validate(array $data): void
167 | {
168 | $assert = Assert::lazy()->tryAll();
169 |
170 | if (isset($data['name'])) {
171 | $assert->that($data['name'], 'name')->string()->notBlank();
172 | }
173 |
174 | if (isset($data['dueFor'])) {
175 | $assert->that($data['dueFor'], 'dueFor')->nullOr()->date(DATE_FORMAT);
176 | }
177 |
178 | if (isset($data['doneAt'])) {
179 | $assert->that($data['doneAt'], 'doneAt')->nullOr()->date(DATE_FORMAT);
180 | }
181 |
182 | try {
183 | $assert->verifyNow();
184 | } catch (LazyAssertionException $e) {
185 | throw InvalidDataException::fromLazyAssertionException($e);
186 | }
187 | }
188 |
189 |
190 | }
191 |
--------------------------------------------------------------------------------
/docs/todos.openapi.yml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.2
2 | info:
3 | title: ToDos API
4 | description: A simple CRUD API
5 | version: 0.0.1
6 |
7 | tags:
8 | - name: ToDos
9 | description: the ToDo resource
10 |
11 | paths:
12 | /todos:
13 | get:
14 | tags: [ ToDos ]
15 | summary: List all ToDos
16 | operationId: listToDos
17 | parameters:
18 | - $ref: '#/components/parameters/search'
19 | - $ref: '#/components/parameters/page'
20 | - $ref: '#/components/parameters/pageSize'
21 | responses:
22 | '200':
23 | $ref: '#/components/responses/ToDoList'
24 | post:
25 | tags: [ ToDos ]
26 | summary: Add a new ToDo
27 | operationId: createToDo
28 | requestBody:
29 | $ref: '#/components/requestBodies/ToDo'
30 | responses:
31 | '201':
32 | $ref: '#/components/responses/ToDo'
33 | '400':
34 | $ref: '#/components/responses/BadRequest'
35 | '401':
36 | $ref: '#/components/responses/Unauthorized'
37 | security:
38 | - basicAuth: []
39 | /todos/{id}:
40 | parameters:
41 | - $ref: '#/components/parameters/id'
42 | get:
43 | tags: [ ToDos ]
44 | summary: Get a ToDo
45 | operationId: getToDo
46 | responses:
47 | '200':
48 | $ref: '#/components/responses/ToDo'
49 | '404':
50 | $ref: '#/components/responses/NotFound'
51 | '400':
52 | $ref: '#/components/responses/BadRequest'
53 | patch:
54 | tags: [ ToDos ]
55 | summary: Update a ToDo
56 | operationId: patchToDo
57 | requestBody:
58 | $ref: '#/components/requestBodies/ToDo'
59 | responses:
60 | '200':
61 | $ref: '#/components/responses/ToDo'
62 | '404':
63 | $ref: '#/components/responses/NotFound'
64 | '400':
65 | $ref: '#/components/responses/BadRequest'
66 | '401':
67 | $ref: '#/components/responses/Unauthorized'
68 | security:
69 | - basicAuth: []
70 | delete:
71 | tags: [ ToDos ]
72 | summary: Delete a ToDo
73 | operationId: deleteToDo
74 | responses:
75 | '204':
76 | description: Empty response
77 | '404':
78 | $ref: '#/components/responses/NotFound'
79 | '400':
80 | $ref: '#/components/responses/BadRequest'
81 | '401':
82 | $ref: '#/components/responses/Unauthorized'
83 | security:
84 | - basicAuth: []
85 |
86 | components:
87 | schemas:
88 | ToDo:
89 | properties:
90 | id:
91 | type: string
92 | readOnly: true
93 | example: "fd1a8610-8aa2-485c-880a-90e43ec189c3"
94 | createdAt:
95 | type: string
96 | readOnly: true
97 | example: "4020-04-20T16:20:00.000000+0000"
98 | format: date-time
99 | name:
100 | type: string
101 | example: "Pay the bills"
102 | dueFor:
103 | type: string
104 | example: "4020-04-20T16:20:00.000000+0000"
105 | format: date-time
106 | doneAt:
107 | type: string
108 | example: "4020-04-20T16:20:00.000000+0000"
109 | format: date-time
110 | isDone:
111 | type: bool
112 | readOnly: true
113 | required:
114 | - id
115 | - name
116 | GenericError:
117 | properties:
118 | error:
119 | type: object
120 | properties:
121 | message:
122 | type: string
123 | code:
124 | type: integer
125 | required:
126 | - message
127 | - code
128 | InvalidDataError:
129 | properties:
130 | error:
131 | type: object
132 | properties:
133 | message:
134 | type: string
135 | code:
136 | type: integer
137 | details:
138 | type: object
139 | example:
140 | messageIdentifier: detailed message
141 | required:
142 | - message
143 | - code
144 |
145 | requestBodies:
146 | ToDo:
147 | required: true
148 | content:
149 | application/json:
150 | schema:
151 | $ref: '#/components/schemas/ToDo'
152 | ToDoRating:
153 | required: true
154 | content:
155 | application/json:
156 | schema:
157 | $ref: '#/components/schemas/ToDoRating'
158 |
159 | responses:
160 | ToDo:
161 | description: A single ToDo
162 | content:
163 | application/json:
164 | schema:
165 | $ref: '#/components/schemas/ToDo'
166 | ToDoList:
167 | description: A list of ToDos
168 | content:
169 | application/json:
170 | schema:
171 | properties:
172 | items:
173 | type: array
174 | items:
175 | $ref: '#/components/schemas/ToDo'
176 | nextPage:
177 | type: integer
178 | example: 2
179 | prevPage:
180 | type: integer
181 | example: null
182 | totalPages:
183 | type: integer
184 | example: 2
185 | NotFound:
186 | description: Resource not found
187 | content:
188 | application/json:
189 | schema:
190 | $ref: '#/components/schemas/GenericError'
191 | BadRequest:
192 | description: Invalid request
193 | content:
194 | application/json:
195 | schema:
196 | $ref: '#/components/schemas/InvalidDataError'
197 | Unauthorized:
198 | description: Authentication failed
199 | content: {}
200 | headers:
201 | WWW-Authenticate:
202 | schema:
203 | type: string
204 | description: Supported authentication schema
205 |
206 | parameters:
207 | id:
208 | in: path
209 | name: id
210 | description: A string representation of a UUID
211 | required: true
212 | schema:
213 | type: string
214 | format: uuid
215 | example: 'fd1a8610-8aa2-485c-880a-90e43ec189c3'
216 | search:
217 | name: search
218 | in: query
219 | schema:
220 | type: string
221 | example: 'pay'
222 | description: Full text search phrase
223 | page:
224 | name: page
225 | in: query
226 | schema:
227 | type: integer
228 | default: 1
229 | example: 1
230 | description: The page number
231 | pageSize:
232 | name: pageSize
233 | in: query
234 | schema:
235 | type: integer
236 | default: 20
237 | example: 20
238 | description: The maximum page size
239 | securitySchemes:
240 | basicAuth:
241 | type: http
242 | scheme: basic
243 |
244 | security:
245 | - basicAuth: []
246 |
--------------------------------------------------------------------------------
/src/Model/ToDoDataMapper.php:
--------------------------------------------------------------------------------
1 | pdo = $pdo;
23 | }
24 |
25 | public function insert(ToDo $item): void
26 | {
27 | $stmt = $this->pdo->prepare(
28 | <<execute();
45 |
46 | if ($result === false) {
47 | throw new RuntimeException('PDO failed to execute a statement');
48 | }
49 | }
50 |
51 | public function update(ToDo $item): void
52 | {
53 | $stmt = $this->pdo->prepare(
54 | <<execute();
69 |
70 | if ($result === false) {
71 | throw new RuntimeException('PDO failed to execute a statement');
72 | }
73 | }
74 |
75 | /**
76 | * @return ToDo[]
77 | */
78 | public function getAll(string $search = '', int $page = 1, int $pageSize = self::DEFAULT_PAGE_SIZE): array
79 | {
80 | Assert::that($page)->greaterThan(0);
81 | Assert::that($pageSize)->greaterThan(0);
82 | $isSearch = $search !== '';
83 | $where = $isSearch ? 'WHERE "searchVector" @@ plainto_tsquery(:search_query)' : '';
84 |
85 | $offset = ($page - 1) * $pageSize;
86 | $limit = $pageSize;
87 |
88 | $stmt = $this->pdo->prepare(
89 | <<bindValue('search_query', $search);
104 | }
105 |
106 | $result = $stmt->execute();
107 |
108 | if ($result === false) {
109 | throw new RuntimeException('PDO failed to execute a statement');
110 | }
111 |
112 | $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
113 |
114 | if ($rows === false) {
115 | throw new RuntimeException('PDO failed to fetch rows');
116 | }
117 |
118 | $toDoFactory = Closure::fromCallable([ $this, 'createToDoFromRow' ]);
119 |
120 | return array_map($toDoFactory, $rows);
121 | }
122 |
123 | public function countPages(string $search = '', int $pageSize = self::DEFAULT_PAGE_SIZE): int
124 | {
125 | Assert::that($pageSize)->greaterThan(0);
126 |
127 | $isSearch = $search !== '';
128 | $where = $isSearch ? 'WHERE "searchVector" @@ plainto_tsquery(:search_query)' : '';
129 |
130 | $stmt = $this->pdo->prepare(
131 | <<bindValue('search_query', $search);
138 | }
139 |
140 | $result = $stmt->execute();
141 |
142 | if ($result === false) {
143 | throw new RuntimeException('PDO failed to execute a statement');
144 | }
145 |
146 | $count = $stmt->fetchColumn();
147 |
148 | if ($count === false) {
149 | throw new RuntimeException('PDO failed to fetch a row');
150 | }
151 |
152 | if ($count <= $pageSize) {
153 | return 1;
154 | }
155 |
156 | return (int) ceil($count / $pageSize);
157 | }
158 |
159 | public function find(string $id): ?ToDo
160 | {
161 | $stmt = $this->pdo->prepare(
162 | <<execute(compact('id'));
174 |
175 | if ($result === false) {
176 | throw new RuntimeException('PDO failed to execute a statement');
177 | }
178 |
179 | $row = $stmt->fetch(PDO::FETCH_ASSOC);
180 |
181 | if ($row === false) {
182 | return null;
183 | }
184 |
185 | return $this->createToDoFromRow($row);
186 | }
187 |
188 | public function delete(string $id): void
189 | {
190 | $stmt = $this->pdo->prepare('DELETE FROM "todos" WHERE "id" = :id;');
191 | $result = $stmt->execute(compact('id'));
192 |
193 | if ($result === false) {
194 | throw new RuntimeException('PDO failed to execute a statement');
195 | }
196 | }
197 |
198 | public function initSchema(): void
199 | {
200 | $this->pdo->exec(static::getSchema());
201 | }
202 |
203 | public function dropSchema(): void
204 | {
205 | $this->pdo->exec(sprintf('DROP TABLE IF EXISTS "%s";', 'todos'));
206 | }
207 |
208 | /**
209 | * @param ToDo $item
210 | * @param PDOStatement $stmt
211 | */
212 | private static function bindParams(ToDo $item, PDOStatement $stmt): void
213 | {
214 | $stmt->bindValue('id', $item->getId());
215 | $stmt->bindValue('name', $item->getName());
216 | $stmt->bindValue('createdAt', $item->getCreatedAtAsString());
217 | $stmt->bindValue('dueFor', $item->getDoneAtAsString() ?: null);
218 | $stmt->bindValue('doneAt', $item->getDoneAtAsString() ?: null);
219 | $stmt->bindValue('searchVector', $item->getName());
220 | }
221 |
222 | private static function getSchema(): string
223 | {
224 | return <<withId($row['id'])->withCreatedAt($row['createdAt']);
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/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#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "ca353917733a2401503ee95e1d508297",
8 | "packages": [
9 | {
10 | "name": "beberlei/assert",
11 | "version": "v3.2.7",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/beberlei/assert.git",
15 | "reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/beberlei/assert/zipball/d63a6943fc4fd1a2aedb65994e3548715105abcf",
20 | "reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "ext-ctype": "*",
25 | "ext-json": "*",
26 | "ext-mbstring": "*",
27 | "ext-simplexml": "*",
28 | "php": "^7"
29 | },
30 | "require-dev": {
31 | "friendsofphp/php-cs-fixer": "*",
32 | "phpstan/phpstan-shim": "*",
33 | "phpunit/phpunit": ">=6.0.0 <8"
34 | },
35 | "suggest": {
36 | "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles"
37 | },
38 | "type": "library",
39 | "autoload": {
40 | "psr-4": {
41 | "Assert\\": "lib/Assert"
42 | },
43 | "files": [
44 | "lib/Assert/functions.php"
45 | ]
46 | },
47 | "notification-url": "https://packagist.org/downloads/",
48 | "license": [
49 | "BSD-2-Clause"
50 | ],
51 | "authors": [
52 | {
53 | "name": "Benjamin Eberlei",
54 | "email": "kontakt@beberlei.de",
55 | "role": "Lead Developer"
56 | },
57 | {
58 | "name": "Richard Quadling",
59 | "email": "rquadling@gmail.com",
60 | "role": "Collaborator"
61 | }
62 | ],
63 | "description": "Thin assertion library for input validation in business models.",
64 | "keywords": [
65 | "assert",
66 | "assertion",
67 | "validation"
68 | ],
69 | "time": "2019-12-19T17:51:41+00:00"
70 | },
71 | {
72 | "name": "doctrine/instantiator",
73 | "version": "1.3.0",
74 | "source": {
75 | "type": "git",
76 | "url": "https://github.com/doctrine/instantiator.git",
77 | "reference": "ae466f726242e637cebdd526a7d991b9433bacf1"
78 | },
79 | "dist": {
80 | "type": "zip",
81 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1",
82 | "reference": "ae466f726242e637cebdd526a7d991b9433bacf1",
83 | "shasum": ""
84 | },
85 | "require": {
86 | "php": "^7.1"
87 | },
88 | "require-dev": {
89 | "doctrine/coding-standard": "^6.0",
90 | "ext-pdo": "*",
91 | "ext-phar": "*",
92 | "phpbench/phpbench": "^0.13",
93 | "phpstan/phpstan-phpunit": "^0.11",
94 | "phpstan/phpstan-shim": "^0.11",
95 | "phpunit/phpunit": "^7.0"
96 | },
97 | "type": "library",
98 | "extra": {
99 | "branch-alias": {
100 | "dev-master": "1.2.x-dev"
101 | }
102 | },
103 | "autoload": {
104 | "psr-4": {
105 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
106 | }
107 | },
108 | "notification-url": "https://packagist.org/downloads/",
109 | "license": [
110 | "MIT"
111 | ],
112 | "authors": [
113 | {
114 | "name": "Marco Pivetta",
115 | "email": "ocramius@gmail.com",
116 | "homepage": "http://ocramius.github.com/"
117 | }
118 | ],
119 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
120 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
121 | "keywords": [
122 | "constructor",
123 | "instantiate"
124 | ],
125 | "time": "2019-10-21T16:45:58+00:00"
126 | },
127 | {
128 | "name": "jeremeamia/superclosure",
129 | "version": "2.4.0",
130 | "source": {
131 | "type": "git",
132 | "url": "https://github.com/jeremeamia/super_closure.git",
133 | "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9"
134 | },
135 | "dist": {
136 | "type": "zip",
137 | "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/5707d5821b30b9a07acfb4d76949784aaa0e9ce9",
138 | "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9",
139 | "shasum": ""
140 | },
141 | "require": {
142 | "nikic/php-parser": "^1.2|^2.0|^3.0|^4.0",
143 | "php": ">=5.4",
144 | "symfony/polyfill-php56": "^1.0"
145 | },
146 | "require-dev": {
147 | "phpunit/phpunit": "^4.0|^5.0"
148 | },
149 | "type": "library",
150 | "extra": {
151 | "branch-alias": {
152 | "dev-master": "2.4-dev"
153 | }
154 | },
155 | "autoload": {
156 | "psr-4": {
157 | "SuperClosure\\": "src/"
158 | }
159 | },
160 | "notification-url": "https://packagist.org/downloads/",
161 | "license": [
162 | "MIT"
163 | ],
164 | "authors": [
165 | {
166 | "name": "Jeremy Lindblom",
167 | "email": "jeremeamia@gmail.com",
168 | "homepage": "https://github.com/jeremeamia",
169 | "role": "Developer"
170 | }
171 | ],
172 | "description": "Serialize Closure objects, including their context and binding",
173 | "homepage": "https://github.com/jeremeamia/super_closure",
174 | "keywords": [
175 | "closure",
176 | "function",
177 | "lambda",
178 | "parser",
179 | "serializable",
180 | "serialize",
181 | "tokenizer"
182 | ],
183 | "abandoned": "opis/closure",
184 | "time": "2018-03-21T22:21:57+00:00"
185 | },
186 | {
187 | "name": "laminas/laminas-code",
188 | "version": "3.4.1",
189 | "source": {
190 | "type": "git",
191 | "url": "https://github.com/laminas/laminas-code.git",
192 | "reference": "1cb8f203389ab1482bf89c0e70a04849bacd7766"
193 | },
194 | "dist": {
195 | "type": "zip",
196 | "url": "https://api.github.com/repos/laminas/laminas-code/zipball/1cb8f203389ab1482bf89c0e70a04849bacd7766",
197 | "reference": "1cb8f203389ab1482bf89c0e70a04849bacd7766",
198 | "shasum": ""
199 | },
200 | "require": {
201 | "laminas/laminas-eventmanager": "^2.6 || ^3.0",
202 | "laminas/laminas-zendframework-bridge": "^1.0",
203 | "php": "^7.1"
204 | },
205 | "conflict": {
206 | "phpspec/prophecy": "<1.9.0"
207 | },
208 | "replace": {
209 | "zendframework/zend-code": "self.version"
210 | },
211 | "require-dev": {
212 | "doctrine/annotations": "^1.7",
213 | "ext-phar": "*",
214 | "laminas/laminas-coding-standard": "^1.0",
215 | "laminas/laminas-stdlib": "^2.7 || ^3.0",
216 | "phpunit/phpunit": "^7.5.16 || ^8.4"
217 | },
218 | "suggest": {
219 | "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features",
220 | "laminas/laminas-stdlib": "Laminas\\Stdlib component"
221 | },
222 | "type": "library",
223 | "extra": {
224 | "branch-alias": {
225 | "dev-master": "3.4.x-dev",
226 | "dev-develop": "3.5.x-dev",
227 | "dev-dev-4.0": "4.0.x-dev"
228 | }
229 | },
230 | "autoload": {
231 | "psr-4": {
232 | "Laminas\\Code\\": "src/"
233 | }
234 | },
235 | "notification-url": "https://packagist.org/downloads/",
236 | "license": [
237 | "BSD-3-Clause"
238 | ],
239 | "description": "Extensions to the PHP Reflection API, static code scanning, and code generation",
240 | "homepage": "https://laminas.dev",
241 | "keywords": [
242 | "code",
243 | "laminas"
244 | ],
245 | "time": "2019-12-31T16:28:24+00:00"
246 | },
247 | {
248 | "name": "laminas/laminas-diactoros",
249 | "version": "2.3.0",
250 | "source": {
251 | "type": "git",
252 | "url": "https://github.com/laminas/laminas-diactoros.git",
253 | "reference": "5ab185dba63ec655a2380c97711b09adc7061f89"
254 | },
255 | "dist": {
256 | "type": "zip",
257 | "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/5ab185dba63ec655a2380c97711b09adc7061f89",
258 | "reference": "5ab185dba63ec655a2380c97711b09adc7061f89",
259 | "shasum": ""
260 | },
261 | "require": {
262 | "laminas/laminas-zendframework-bridge": "^1.0",
263 | "php": "^7.1",
264 | "psr/http-factory": "^1.0",
265 | "psr/http-message": "^1.0"
266 | },
267 | "conflict": {
268 | "phpspec/prophecy": "<1.9.0"
269 | },
270 | "provide": {
271 | "psr/http-factory-implementation": "1.0",
272 | "psr/http-message-implementation": "1.0"
273 | },
274 | "replace": {
275 | "zendframework/zend-diactoros": "^2.2.1"
276 | },
277 | "require-dev": {
278 | "ext-curl": "*",
279 | "ext-dom": "*",
280 | "ext-libxml": "*",
281 | "http-interop/http-factory-tests": "^0.5.0",
282 | "laminas/laminas-coding-standard": "~1.0.0",
283 | "php-http/psr7-integration-tests": "^1.0",
284 | "phpunit/phpunit": "^7.5.18"
285 | },
286 | "type": "library",
287 | "extra": {
288 | "branch-alias": {
289 | "dev-master": "2.3.x-dev",
290 | "dev-develop": "2.4.x-dev"
291 | },
292 | "laminas": {
293 | "config-provider": "Laminas\\Diactoros\\ConfigProvider",
294 | "module": "Laminas\\Diactoros"
295 | }
296 | },
297 | "autoload": {
298 | "files": [
299 | "src/functions/create_uploaded_file.php",
300 | "src/functions/marshal_headers_from_sapi.php",
301 | "src/functions/marshal_method_from_sapi.php",
302 | "src/functions/marshal_protocol_version_from_sapi.php",
303 | "src/functions/marshal_uri_from_sapi.php",
304 | "src/functions/normalize_server.php",
305 | "src/functions/normalize_uploaded_files.php",
306 | "src/functions/parse_cookie_header.php",
307 | "src/functions/create_uploaded_file.legacy.php",
308 | "src/functions/marshal_headers_from_sapi.legacy.php",
309 | "src/functions/marshal_method_from_sapi.legacy.php",
310 | "src/functions/marshal_protocol_version_from_sapi.legacy.php",
311 | "src/functions/marshal_uri_from_sapi.legacy.php",
312 | "src/functions/normalize_server.legacy.php",
313 | "src/functions/normalize_uploaded_files.legacy.php",
314 | "src/functions/parse_cookie_header.legacy.php"
315 | ],
316 | "psr-4": {
317 | "Laminas\\Diactoros\\": "src/"
318 | }
319 | },
320 | "notification-url": "https://packagist.org/downloads/",
321 | "license": [
322 | "BSD-3-Clause"
323 | ],
324 | "description": "PSR HTTP Message implementations",
325 | "homepage": "https://laminas.dev",
326 | "keywords": [
327 | "http",
328 | "laminas",
329 | "psr",
330 | "psr-7"
331 | ],
332 | "funding": [
333 | {
334 | "url": "https://funding.communitybridge.org/projects/laminas-project",
335 | "type": "community_bridge"
336 | }
337 | ],
338 | "time": "2020-04-27T17:07:01+00:00"
339 | },
340 | {
341 | "name": "laminas/laminas-eventmanager",
342 | "version": "3.2.1",
343 | "source": {
344 | "type": "git",
345 | "url": "https://github.com/laminas/laminas-eventmanager.git",
346 | "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748"
347 | },
348 | "dist": {
349 | "type": "zip",
350 | "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/ce4dc0bdf3b14b7f9815775af9dfee80a63b4748",
351 | "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748",
352 | "shasum": ""
353 | },
354 | "require": {
355 | "laminas/laminas-zendframework-bridge": "^1.0",
356 | "php": "^5.6 || ^7.0"
357 | },
358 | "replace": {
359 | "zendframework/zend-eventmanager": "self.version"
360 | },
361 | "require-dev": {
362 | "athletic/athletic": "^0.1",
363 | "container-interop/container-interop": "^1.1.0",
364 | "laminas/laminas-coding-standard": "~1.0.0",
365 | "laminas/laminas-stdlib": "^2.7.3 || ^3.0",
366 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2"
367 | },
368 | "suggest": {
369 | "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature",
370 | "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature"
371 | },
372 | "type": "library",
373 | "extra": {
374 | "branch-alias": {
375 | "dev-master": "3.2-dev",
376 | "dev-develop": "3.3-dev"
377 | }
378 | },
379 | "autoload": {
380 | "psr-4": {
381 | "Laminas\\EventManager\\": "src/"
382 | }
383 | },
384 | "notification-url": "https://packagist.org/downloads/",
385 | "license": [
386 | "BSD-3-Clause"
387 | ],
388 | "description": "Trigger and listen to events within a PHP application",
389 | "homepage": "https://laminas.dev",
390 | "keywords": [
391 | "event",
392 | "eventmanager",
393 | "events",
394 | "laminas"
395 | ],
396 | "time": "2019-12-31T16:44:52+00:00"
397 | },
398 | {
399 | "name": "laminas/laminas-httphandlerrunner",
400 | "version": "1.1.0",
401 | "source": {
402 | "type": "git",
403 | "url": "https://github.com/laminas/laminas-httphandlerrunner.git",
404 | "reference": "296f5ff35074dd981d1570a66b95596c81808087"
405 | },
406 | "dist": {
407 | "type": "zip",
408 | "url": "https://api.github.com/repos/laminas/laminas-httphandlerrunner/zipball/296f5ff35074dd981d1570a66b95596c81808087",
409 | "reference": "296f5ff35074dd981d1570a66b95596c81808087",
410 | "shasum": ""
411 | },
412 | "require": {
413 | "laminas/laminas-zendframework-bridge": "^1.0",
414 | "php": "^7.1",
415 | "psr/http-message": "^1.0",
416 | "psr/http-message-implementation": "^1.0",
417 | "psr/http-server-handler": "^1.0"
418 | },
419 | "replace": {
420 | "zendframework/zend-httphandlerrunner": "self.version"
421 | },
422 | "require-dev": {
423 | "laminas/laminas-coding-standard": "~1.0.0",
424 | "laminas/laminas-diactoros": "^1.7 || ^2.1.1",
425 | "phpunit/phpunit": "^7.0.2"
426 | },
427 | "type": "library",
428 | "extra": {
429 | "branch-alias": {
430 | "dev-master": "1.1.x-dev",
431 | "dev-develop": "1.2.x-dev"
432 | },
433 | "laminas": {
434 | "config-provider": "Laminas\\HttpHandlerRunner\\ConfigProvider"
435 | }
436 | },
437 | "autoload": {
438 | "psr-4": {
439 | "Laminas\\HttpHandlerRunner\\": "src/"
440 | }
441 | },
442 | "notification-url": "https://packagist.org/downloads/",
443 | "license": [
444 | "BSD-3-Clause"
445 | ],
446 | "description": "Execute PSR-15 RequestHandlerInterface instances and emit responses they generate.",
447 | "homepage": "https://laminas.dev",
448 | "keywords": [
449 | "components",
450 | "laminas",
451 | "mezzio",
452 | "psr-15",
453 | "psr-7"
454 | ],
455 | "time": "2019-12-31T17:06:16+00:00"
456 | },
457 | {
458 | "name": "laminas/laminas-zendframework-bridge",
459 | "version": "1.0.3",
460 | "source": {
461 | "type": "git",
462 | "url": "https://github.com/laminas/laminas-zendframework-bridge.git",
463 | "reference": "bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9"
464 | },
465 | "dist": {
466 | "type": "zip",
467 | "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9",
468 | "reference": "bfbbdb6c998d50dbf69d2187cb78a5f1fa36e1e9",
469 | "shasum": ""
470 | },
471 | "require": {
472 | "php": "^5.6 || ^7.0"
473 | },
474 | "require-dev": {
475 | "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1",
476 | "squizlabs/php_codesniffer": "^3.5"
477 | },
478 | "type": "library",
479 | "extra": {
480 | "branch-alias": {
481 | "dev-master": "1.0.x-dev",
482 | "dev-develop": "1.1.x-dev"
483 | },
484 | "laminas": {
485 | "module": "Laminas\\ZendFrameworkBridge"
486 | }
487 | },
488 | "autoload": {
489 | "files": [
490 | "src/autoload.php"
491 | ],
492 | "psr-4": {
493 | "Laminas\\ZendFrameworkBridge\\": "src//"
494 | }
495 | },
496 | "notification-url": "https://packagist.org/downloads/",
497 | "license": [
498 | "BSD-3-Clause"
499 | ],
500 | "description": "Alias legacy ZF class names to Laminas Project equivalents.",
501 | "keywords": [
502 | "ZendFramework",
503 | "autoloading",
504 | "laminas",
505 | "zf"
506 | ],
507 | "time": "2020-04-03T16:01:00+00:00"
508 | },
509 | {
510 | "name": "lcobucci/clock",
511 | "version": "1.3.0",
512 | "source": {
513 | "type": "git",
514 | "url": "https://github.com/lcobucci/clock.git",
515 | "reference": "70f25bddfb21a9cbf8d45cc53571de8ab45f5b69"
516 | },
517 | "dist": {
518 | "type": "zip",
519 | "url": "https://api.github.com/repos/lcobucci/clock/zipball/70f25bddfb21a9cbf8d45cc53571de8ab45f5b69",
520 | "reference": "70f25bddfb21a9cbf8d45cc53571de8ab45f5b69",
521 | "shasum": ""
522 | },
523 | "require": {
524 | "php": "^7.4"
525 | },
526 | "require-dev": {
527 | "infection/infection": "^0.15",
528 | "lcobucci/coding-standard": "^4.0",
529 | "phpstan/extension-installer": "^1.0",
530 | "phpstan/phpstan": "^0.12",
531 | "phpstan/phpstan-deprecation-rules": "^0.12",
532 | "phpstan/phpstan-phpunit": "^0.12",
533 | "phpstan/phpstan-strict-rules": "^0.12",
534 | "phpunit/phpunit": "^9.0"
535 | },
536 | "type": "library",
537 | "extra": {
538 | "branch-alias": {
539 | "dev-master": "1.3-dev"
540 | }
541 | },
542 | "autoload": {
543 | "psr-4": {
544 | "Lcobucci\\Clock\\": "src"
545 | }
546 | },
547 | "notification-url": "https://packagist.org/downloads/",
548 | "license": [
549 | "MIT"
550 | ],
551 | "authors": [
552 | {
553 | "name": "Luís Cobucci",
554 | "email": "lcobucci@gmail.com"
555 | }
556 | ],
557 | "description": "Yet another clock abstraction",
558 | "time": "2020-02-26T20:20:12+00:00"
559 | },
560 | {
561 | "name": "league/route",
562 | "version": "4.3.1",
563 | "source": {
564 | "type": "git",
565 | "url": "https://github.com/thephpleague/route.git",
566 | "reference": "e9ca722dc52d6652057e6fc448572a765b294f24"
567 | },
568 | "dist": {
569 | "type": "zip",
570 | "url": "https://api.github.com/repos/thephpleague/route/zipball/e9ca722dc52d6652057e6fc448572a765b294f24",
571 | "reference": "e9ca722dc52d6652057e6fc448572a765b294f24",
572 | "shasum": ""
573 | },
574 | "require": {
575 | "nikic/fast-route": "^1.0",
576 | "php": ">=7.1",
577 | "psr/container": "^1.0",
578 | "psr/http-factory": "^1.0",
579 | "psr/http-message": "^1.0",
580 | "psr/http-server-handler": "^1.0",
581 | "psr/http-server-middleware": "^1.0"
582 | },
583 | "replace": {
584 | "orno/http": "~1.0",
585 | "orno/route": "~1.0"
586 | },
587 | "require-dev": {
588 | "phpstan/phpstan": "^0.10.3",
589 | "phpstan/phpstan-phpunit": "^0.10.0",
590 | "phpunit/phpunit": "^7.0",
591 | "squizlabs/php_codesniffer": "^3.3"
592 | },
593 | "type": "library",
594 | "extra": {
595 | "branch-alias": {
596 | "dev-4.x": "4.x-dev",
597 | "dev-3.x": "3.x-dev",
598 | "dev-2.x": "2.x-dev",
599 | "dev-1.x": "1.x-dev"
600 | }
601 | },
602 | "autoload": {
603 | "psr-4": {
604 | "League\\Route\\": "src"
605 | }
606 | },
607 | "notification-url": "https://packagist.org/downloads/",
608 | "license": [
609 | "MIT"
610 | ],
611 | "authors": [
612 | {
613 | "name": "Phil Bennett",
614 | "email": "philipobenito@gmail.com",
615 | "role": "Developer"
616 | }
617 | ],
618 | "description": "Fast routing and dispatch component including PSR-15 middleware, built on top of FastRoute.",
619 | "homepage": "https://github.com/thephpleague/route",
620 | "keywords": [
621 | "dispatcher",
622 | "league",
623 | "psr-15",
624 | "psr-7",
625 | "psr15",
626 | "psr7",
627 | "route",
628 | "router"
629 | ],
630 | "time": "2019-07-01T19:36:07+00:00"
631 | },
632 | {
633 | "name": "middlewares/http-authentication",
634 | "version": "v1.1.0",
635 | "source": {
636 | "type": "git",
637 | "url": "https://github.com/middlewares/http-authentication.git",
638 | "reference": "4f719e5205c9d0fb96bf7971379caf5fbbc6b89f"
639 | },
640 | "dist": {
641 | "type": "zip",
642 | "url": "https://api.github.com/repos/middlewares/http-authentication/zipball/4f719e5205c9d0fb96bf7971379caf5fbbc6b89f",
643 | "reference": "4f719e5205c9d0fb96bf7971379caf5fbbc6b89f",
644 | "shasum": ""
645 | },
646 | "require": {
647 | "middlewares/utils": "^2.1",
648 | "php": "^7.0",
649 | "psr/http-server-middleware": "^1.0"
650 | },
651 | "require-dev": {
652 | "friendsofphp/php-cs-fixer": "^2.0",
653 | "phpunit/phpunit": "^6.0|^7.0",
654 | "squizlabs/php_codesniffer": "^3.0",
655 | "zendframework/zend-diactoros": "^1.3"
656 | },
657 | "type": "library",
658 | "autoload": {
659 | "psr-4": {
660 | "Middlewares\\": "src/"
661 | }
662 | },
663 | "notification-url": "https://packagist.org/downloads/",
664 | "license": [
665 | "MIT"
666 | ],
667 | "description": "Middleware to implement Basic and Digest Http authentication",
668 | "homepage": "https://github.com/middlewares/http-authentication",
669 | "keywords": [
670 | "Authentication",
671 | "basic",
672 | "digest",
673 | "http",
674 | "middleware",
675 | "psr-15",
676 | "psr-7",
677 | "server"
678 | ],
679 | "time": "2018-08-04T10:41:49+00:00"
680 | },
681 | {
682 | "name": "middlewares/negotiation",
683 | "version": "v1.1.0",
684 | "source": {
685 | "type": "git",
686 | "url": "https://github.com/middlewares/negotiation.git",
687 | "reference": "4c8cee6e923834ec26905bab29788265a46134bd"
688 | },
689 | "dist": {
690 | "type": "zip",
691 | "url": "https://api.github.com/repos/middlewares/negotiation/zipball/4c8cee6e923834ec26905bab29788265a46134bd",
692 | "reference": "4c8cee6e923834ec26905bab29788265a46134bd",
693 | "shasum": ""
694 | },
695 | "require": {
696 | "middlewares/utils": "^2.1",
697 | "php": "^7.0",
698 | "psr/http-server-middleware": "^1.0",
699 | "willdurand/negotiation": "^2.1"
700 | },
701 | "require-dev": {
702 | "friendsofphp/php-cs-fixer": "^2.0",
703 | "phpunit/phpunit": "^6.0|^7.0",
704 | "squizlabs/php_codesniffer": "^3.0",
705 | "zendframework/zend-diactoros": "^1.3"
706 | },
707 | "type": "library",
708 | "autoload": {
709 | "psr-4": {
710 | "Middlewares\\": "src/"
711 | }
712 | },
713 | "notification-url": "https://packagist.org/downloads/",
714 | "license": [
715 | "MIT"
716 | ],
717 | "description": "Middleware to implement content negotiation",
718 | "homepage": "https://github.com/middlewares/negotiation",
719 | "keywords": [
720 | "content",
721 | "encoding",
722 | "http",
723 | "language",
724 | "middleware",
725 | "negotiation",
726 | "psr-15",
727 | "psr-7",
728 | "server"
729 | ],
730 | "time": "2018-08-04T10:41:52+00:00"
731 | },
732 | {
733 | "name": "middlewares/payload",
734 | "version": "v2.1.1",
735 | "source": {
736 | "type": "git",
737 | "url": "https://github.com/middlewares/payload.git",
738 | "reference": "f2e12a0567beb8beb2dfa51af5661bcd723ff52c"
739 | },
740 | "dist": {
741 | "type": "zip",
742 | "url": "https://api.github.com/repos/middlewares/payload/zipball/f2e12a0567beb8beb2dfa51af5661bcd723ff52c",
743 | "reference": "f2e12a0567beb8beb2dfa51af5661bcd723ff52c",
744 | "shasum": ""
745 | },
746 | "require": {
747 | "middlewares/utils": "^2.1",
748 | "php": "^7.0",
749 | "psr/http-server-middleware": "^1.0"
750 | },
751 | "require-dev": {
752 | "friendsofphp/php-cs-fixer": "^2.0",
753 | "phpstan/phpstan": "^0.9.2|^0.10.3",
754 | "phpunit/phpunit": "^6.0|^7.0",
755 | "squizlabs/php_codesniffer": "^3.0",
756 | "zendframework/zend-diactoros": "^1.3"
757 | },
758 | "suggest": {
759 | "middlewares/csv-payload": "Adds support for parsing CSV body of request"
760 | },
761 | "type": "library",
762 | "autoload": {
763 | "psr-4": {
764 | "Middlewares\\": "src/"
765 | }
766 | },
767 | "notification-url": "https://packagist.org/downloads/",
768 | "license": [
769 | "MIT"
770 | ],
771 | "description": "Middleware to parse the body of the request with support for json, csv and url-encode",
772 | "homepage": "https://github.com/middlewares/payload",
773 | "keywords": [
774 | "http",
775 | "json",
776 | "middleware",
777 | "payload",
778 | "psr-15",
779 | "psr-7",
780 | "server",
781 | "url-encode"
782 | ],
783 | "time": "2018-11-08T08:45:32+00:00"
784 | },
785 | {
786 | "name": "middlewares/utils",
787 | "version": "v2.2.0",
788 | "source": {
789 | "type": "git",
790 | "url": "https://github.com/middlewares/utils.git",
791 | "reference": "7dc49454b4fbf249226023c7b77658b6068abfbc"
792 | },
793 | "dist": {
794 | "type": "zip",
795 | "url": "https://api.github.com/repos/middlewares/utils/zipball/7dc49454b4fbf249226023c7b77658b6068abfbc",
796 | "reference": "7dc49454b4fbf249226023c7b77658b6068abfbc",
797 | "shasum": ""
798 | },
799 | "require": {
800 | "php": "^7.0",
801 | "psr/container": "^1.0",
802 | "psr/http-factory": "^1.0",
803 | "psr/http-message": "^1.0",
804 | "psr/http-server-middleware": "^1.0"
805 | },
806 | "require-dev": {
807 | "friendsofphp/php-cs-fixer": "^2.0",
808 | "guzzlehttp/psr7": "^1.3",
809 | "phpunit/phpunit": "^6.0|^7.0",
810 | "slim/http": "^0.3",
811 | "squizlabs/php_codesniffer": "^3.0",
812 | "zendframework/zend-diactoros": "^1.3"
813 | },
814 | "type": "library",
815 | "autoload": {
816 | "psr-4": {
817 | "Middlewares\\Utils\\": "src/"
818 | }
819 | },
820 | "notification-url": "https://packagist.org/downloads/",
821 | "license": [
822 | "MIT"
823 | ],
824 | "description": "Common utils to create PSR-15 middleware packages",
825 | "homepage": "https://github.com/middlewares/utils",
826 | "keywords": [
827 | "PSR-11",
828 | "http",
829 | "middleware",
830 | "psr-15",
831 | "psr-17",
832 | "psr-7"
833 | ],
834 | "time": "2019-03-05T22:06:37+00:00"
835 | },
836 | {
837 | "name": "monolog/monolog",
838 | "version": "1.25.3",
839 | "source": {
840 | "type": "git",
841 | "url": "https://github.com/Seldaek/monolog.git",
842 | "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1"
843 | },
844 | "dist": {
845 | "type": "zip",
846 | "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1",
847 | "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1",
848 | "shasum": ""
849 | },
850 | "require": {
851 | "php": ">=5.3.0",
852 | "psr/log": "~1.0"
853 | },
854 | "provide": {
855 | "psr/log-implementation": "1.0.0"
856 | },
857 | "require-dev": {
858 | "aws/aws-sdk-php": "^2.4.9 || ^3.0",
859 | "doctrine/couchdb": "~1.0@dev",
860 | "graylog2/gelf-php": "~1.0",
861 | "jakub-onderka/php-parallel-lint": "0.9",
862 | "php-amqplib/php-amqplib": "~2.4",
863 | "php-console/php-console": "^3.1.3",
864 | "phpunit/phpunit": "~4.5",
865 | "phpunit/phpunit-mock-objects": "2.3.0",
866 | "ruflin/elastica": ">=0.90 <3.0",
867 | "sentry/sentry": "^0.13",
868 | "swiftmailer/swiftmailer": "^5.3|^6.0"
869 | },
870 | "suggest": {
871 | "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
872 | "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
873 | "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
874 | "ext-mongo": "Allow sending log messages to a MongoDB server",
875 | "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
876 | "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
877 | "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
878 | "php-console/php-console": "Allow sending log messages to Google Chrome",
879 | "rollbar/rollbar": "Allow sending log messages to Rollbar",
880 | "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
881 | "sentry/sentry": "Allow sending log messages to a Sentry server"
882 | },
883 | "type": "library",
884 | "extra": {
885 | "branch-alias": {
886 | "dev-master": "2.0.x-dev"
887 | }
888 | },
889 | "autoload": {
890 | "psr-4": {
891 | "Monolog\\": "src/Monolog"
892 | }
893 | },
894 | "notification-url": "https://packagist.org/downloads/",
895 | "license": [
896 | "MIT"
897 | ],
898 | "authors": [
899 | {
900 | "name": "Jordi Boggiano",
901 | "email": "j.boggiano@seld.be",
902 | "homepage": "http://seld.be"
903 | }
904 | ],
905 | "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
906 | "homepage": "http://github.com/Seldaek/monolog",
907 | "keywords": [
908 | "log",
909 | "logging",
910 | "psr-3"
911 | ],
912 | "time": "2019-12-20T14:15:16+00:00"
913 | },
914 | {
915 | "name": "nikic/fast-route",
916 | "version": "v1.3.0",
917 | "source": {
918 | "type": "git",
919 | "url": "https://github.com/nikic/FastRoute.git",
920 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812"
921 | },
922 | "dist": {
923 | "type": "zip",
924 | "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
925 | "reference": "181d480e08d9476e61381e04a71b34dc0432e812",
926 | "shasum": ""
927 | },
928 | "require": {
929 | "php": ">=5.4.0"
930 | },
931 | "require-dev": {
932 | "phpunit/phpunit": "^4.8.35|~5.7"
933 | },
934 | "type": "library",
935 | "autoload": {
936 | "psr-4": {
937 | "FastRoute\\": "src/"
938 | },
939 | "files": [
940 | "src/functions.php"
941 | ]
942 | },
943 | "notification-url": "https://packagist.org/downloads/",
944 | "license": [
945 | "BSD-3-Clause"
946 | ],
947 | "authors": [
948 | {
949 | "name": "Nikita Popov",
950 | "email": "nikic@php.net"
951 | }
952 | ],
953 | "description": "Fast request router for PHP",
954 | "keywords": [
955 | "router",
956 | "routing"
957 | ],
958 | "time": "2018-02-13T20:26:39+00:00"
959 | },
960 | {
961 | "name": "nikic/php-parser",
962 | "version": "v4.4.0",
963 | "source": {
964 | "type": "git",
965 | "url": "https://github.com/nikic/PHP-Parser.git",
966 | "reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120"
967 | },
968 | "dist": {
969 | "type": "zip",
970 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120",
971 | "reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120",
972 | "shasum": ""
973 | },
974 | "require": {
975 | "ext-tokenizer": "*",
976 | "php": ">=7.0"
977 | },
978 | "require-dev": {
979 | "ircmaxell/php-yacc": "0.0.5",
980 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0"
981 | },
982 | "bin": [
983 | "bin/php-parse"
984 | ],
985 | "type": "library",
986 | "extra": {
987 | "branch-alias": {
988 | "dev-master": "4.3-dev"
989 | }
990 | },
991 | "autoload": {
992 | "psr-4": {
993 | "PhpParser\\": "lib/PhpParser"
994 | }
995 | },
996 | "notification-url": "https://packagist.org/downloads/",
997 | "license": [
998 | "BSD-3-Clause"
999 | ],
1000 | "authors": [
1001 | {
1002 | "name": "Nikita Popov"
1003 | }
1004 | ],
1005 | "description": "A PHP parser written in PHP",
1006 | "keywords": [
1007 | "parser",
1008 | "php"
1009 | ],
1010 | "time": "2020-04-10T16:34:50+00:00"
1011 | },
1012 | {
1013 | "name": "ocramius/package-versions",
1014 | "version": "1.8.0",
1015 | "source": {
1016 | "type": "git",
1017 | "url": "https://github.com/Ocramius/PackageVersions.git",
1018 | "reference": "421679846270a5772534828013a93be709fb13df"
1019 | },
1020 | "dist": {
1021 | "type": "zip",
1022 | "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/421679846270a5772534828013a93be709fb13df",
1023 | "reference": "421679846270a5772534828013a93be709fb13df",
1024 | "shasum": ""
1025 | },
1026 | "require": {
1027 | "composer-plugin-api": "^1.1.0 || ^2.0",
1028 | "php": "^7.4.0"
1029 | },
1030 | "require-dev": {
1031 | "composer/composer": "^1.9.3 || ^2.0@dev",
1032 | "doctrine/coding-standard": "^7.0.2",
1033 | "ext-zip": "^1.15.0",
1034 | "infection/infection": "^0.15.3",
1035 | "phpunit/phpunit": "^9.0.1",
1036 | "vimeo/psalm": "^3.9.3"
1037 | },
1038 | "type": "composer-plugin",
1039 | "extra": {
1040 | "class": "PackageVersions\\Installer",
1041 | "branch-alias": {
1042 | "dev-master": "1.99.x-dev"
1043 | }
1044 | },
1045 | "autoload": {
1046 | "psr-4": {
1047 | "PackageVersions\\": "src/PackageVersions"
1048 | }
1049 | },
1050 | "notification-url": "https://packagist.org/downloads/",
1051 | "license": [
1052 | "MIT"
1053 | ],
1054 | "authors": [
1055 | {
1056 | "name": "Marco Pivetta",
1057 | "email": "ocramius@gmail.com"
1058 | }
1059 | ],
1060 | "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
1061 | "funding": [
1062 | {
1063 | "url": "https://github.com/Ocramius",
1064 | "type": "github"
1065 | },
1066 | {
1067 | "url": "https://tidelift.com/funding/github/packagist/ocramius/package-versions",
1068 | "type": "tidelift"
1069 | }
1070 | ],
1071 | "time": "2020-04-06T17:43:35+00:00"
1072 | },
1073 | {
1074 | "name": "ocramius/proxy-manager",
1075 | "version": "2.8.0",
1076 | "source": {
1077 | "type": "git",
1078 | "url": "https://github.com/Ocramius/ProxyManager.git",
1079 | "reference": "ac1dd414fd114cfc0da9930e0ab46063c2f5e62a"
1080 | },
1081 | "dist": {
1082 | "type": "zip",
1083 | "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/ac1dd414fd114cfc0da9930e0ab46063c2f5e62a",
1084 | "reference": "ac1dd414fd114cfc0da9930e0ab46063c2f5e62a",
1085 | "shasum": ""
1086 | },
1087 | "require": {
1088 | "laminas/laminas-code": "^3.4.1",
1089 | "ocramius/package-versions": "^1.8.0",
1090 | "php": "~7.4.1",
1091 | "webimpress/safe-writer": "^2.0.1"
1092 | },
1093 | "conflict": {
1094 | "doctrine/annotations": "<1.6.1",
1095 | "laminas/laminas-stdlib": "<3.2.1",
1096 | "zendframework/zend-stdlib": "<3.2.1"
1097 | },
1098 | "require-dev": {
1099 | "doctrine/coding-standard": "^6.0.0",
1100 | "ext-phar": "*",
1101 | "infection/infection": "^0.16.2",
1102 | "nikic/php-parser": "^4.4.0",
1103 | "phpbench/phpbench": "^0.17.0",
1104 | "phpunit/phpunit": "^9.1.1",
1105 | "slevomat/coding-standard": "^5.0.4",
1106 | "squizlabs/php_codesniffer": "^3.5.4",
1107 | "vimeo/psalm": "^3.11.1"
1108 | },
1109 | "suggest": {
1110 | "laminas/laminas-json": "To have the JsonRpc adapter (Remote Object feature)",
1111 | "laminas/laminas-soap": "To have the Soap adapter (Remote Object feature)",
1112 | "laminas/laminas-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)",
1113 | "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects"
1114 | },
1115 | "type": "library",
1116 | "extra": {
1117 | "branch-alias": {
1118 | "dev-master": "3.0.x-dev"
1119 | }
1120 | },
1121 | "autoload": {
1122 | "psr-4": {
1123 | "ProxyManager\\": "src/ProxyManager"
1124 | }
1125 | },
1126 | "notification-url": "https://packagist.org/downloads/",
1127 | "license": [
1128 | "MIT"
1129 | ],
1130 | "authors": [
1131 | {
1132 | "name": "Marco Pivetta",
1133 | "email": "ocramius@gmail.com",
1134 | "homepage": "http://ocramius.github.io/"
1135 | }
1136 | ],
1137 | "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies",
1138 | "homepage": "https://github.com/Ocramius/ProxyManager",
1139 | "keywords": [
1140 | "aop",
1141 | "lazy loading",
1142 | "proxy",
1143 | "proxy pattern",
1144 | "service proxies"
1145 | ],
1146 | "funding": [
1147 | {
1148 | "url": "https://github.com/Ocramius",
1149 | "type": "github"
1150 | },
1151 | {
1152 | "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager",
1153 | "type": "tidelift"
1154 | }
1155 | ],
1156 | "time": "2020-04-13T14:42:16+00:00"
1157 | },
1158 | {
1159 | "name": "oscarotero/env",
1160 | "version": "v1.2.0",
1161 | "source": {
1162 | "type": "git",
1163 | "url": "https://github.com/oscarotero/env.git",
1164 | "reference": "4ab45ce5c1f2c62549208426bfa20a3d5fa008c6"
1165 | },
1166 | "dist": {
1167 | "type": "zip",
1168 | "url": "https://api.github.com/repos/oscarotero/env/zipball/4ab45ce5c1f2c62549208426bfa20a3d5fa008c6",
1169 | "reference": "4ab45ce5c1f2c62549208426bfa20a3d5fa008c6",
1170 | "shasum": ""
1171 | },
1172 | "require": {
1173 | "ext-ctype": "*",
1174 | "php": ">=5.2"
1175 | },
1176 | "type": "library",
1177 | "autoload": {
1178 | "psr-0": {
1179 | "Env": "src/"
1180 | }
1181 | },
1182 | "notification-url": "https://packagist.org/downloads/",
1183 | "license": [
1184 | "MIT"
1185 | ],
1186 | "authors": [
1187 | {
1188 | "name": "Oscar Otero",
1189 | "email": "oom@oscarotero.com",
1190 | "homepage": "http://oscarotero.com",
1191 | "role": "Developer"
1192 | }
1193 | ],
1194 | "description": "Simple library to consume environment variables",
1195 | "homepage": "https://github.com/oscarotero/env",
1196 | "keywords": [
1197 | "env"
1198 | ],
1199 | "time": "2019-04-03T18:28:43+00:00"
1200 | },
1201 | {
1202 | "name": "paragonie/random_compat",
1203 | "version": "v9.99.99",
1204 | "source": {
1205 | "type": "git",
1206 | "url": "https://github.com/paragonie/random_compat.git",
1207 | "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
1208 | },
1209 | "dist": {
1210 | "type": "zip",
1211 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
1212 | "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
1213 | "shasum": ""
1214 | },
1215 | "require": {
1216 | "php": "^7"
1217 | },
1218 | "require-dev": {
1219 | "phpunit/phpunit": "4.*|5.*",
1220 | "vimeo/psalm": "^1"
1221 | },
1222 | "suggest": {
1223 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
1224 | },
1225 | "type": "library",
1226 | "notification-url": "https://packagist.org/downloads/",
1227 | "license": [
1228 | "MIT"
1229 | ],
1230 | "authors": [
1231 | {
1232 | "name": "Paragon Initiative Enterprises",
1233 | "email": "security@paragonie.com",
1234 | "homepage": "https://paragonie.com"
1235 | }
1236 | ],
1237 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
1238 | "keywords": [
1239 | "csprng",
1240 | "polyfill",
1241 | "pseudorandom",
1242 | "random"
1243 | ],
1244 | "time": "2018-07-02T15:55:56+00:00"
1245 | },
1246 | {
1247 | "name": "php-di/invoker",
1248 | "version": "2.0.0",
1249 | "source": {
1250 | "type": "git",
1251 | "url": "https://github.com/PHP-DI/Invoker.git",
1252 | "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a"
1253 | },
1254 | "dist": {
1255 | "type": "zip",
1256 | "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/540c27c86f663e20fe39a24cd72fa76cdb21d41a",
1257 | "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a",
1258 | "shasum": ""
1259 | },
1260 | "require": {
1261 | "psr/container": "~1.0"
1262 | },
1263 | "require-dev": {
1264 | "athletic/athletic": "~0.1.8",
1265 | "phpunit/phpunit": "~4.5"
1266 | },
1267 | "type": "library",
1268 | "autoload": {
1269 | "psr-4": {
1270 | "Invoker\\": "src/"
1271 | }
1272 | },
1273 | "notification-url": "https://packagist.org/downloads/",
1274 | "license": [
1275 | "MIT"
1276 | ],
1277 | "description": "Generic and extensible callable invoker",
1278 | "homepage": "https://github.com/PHP-DI/Invoker",
1279 | "keywords": [
1280 | "callable",
1281 | "dependency",
1282 | "dependency-injection",
1283 | "injection",
1284 | "invoke",
1285 | "invoker"
1286 | ],
1287 | "time": "2017-03-20T19:28:22+00:00"
1288 | },
1289 | {
1290 | "name": "php-di/php-di",
1291 | "version": "6.1.0",
1292 | "source": {
1293 | "type": "git",
1294 | "url": "https://github.com/PHP-DI/PHP-DI.git",
1295 | "reference": "69238bd49acc0eb6a967029311eeadc3f7c5d538"
1296 | },
1297 | "dist": {
1298 | "type": "zip",
1299 | "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/69238bd49acc0eb6a967029311eeadc3f7c5d538",
1300 | "reference": "69238bd49acc0eb6a967029311eeadc3f7c5d538",
1301 | "shasum": ""
1302 | },
1303 | "require": {
1304 | "jeremeamia/superclosure": "^2.0",
1305 | "nikic/php-parser": "^2.0|^3.0|^4.0",
1306 | "php": ">=7.2.0",
1307 | "php-di/invoker": "^2.0",
1308 | "php-di/phpdoc-reader": "^2.0.1",
1309 | "psr/container": "^1.0"
1310 | },
1311 | "provide": {
1312 | "psr/container-implementation": "^1.0"
1313 | },
1314 | "require-dev": {
1315 | "doctrine/annotations": "~1.2",
1316 | "friendsofphp/php-cs-fixer": "^2.4",
1317 | "mnapoli/phpunit-easymock": "^1.2",
1318 | "ocramius/proxy-manager": "~2.0.2",
1319 | "phpstan/phpstan": "^0.12",
1320 | "phpunit/phpunit": "^8.5"
1321 | },
1322 | "suggest": {
1323 | "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)",
1324 | "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)"
1325 | },
1326 | "type": "library",
1327 | "autoload": {
1328 | "psr-4": {
1329 | "DI\\": "src/"
1330 | },
1331 | "files": [
1332 | "src/functions.php"
1333 | ]
1334 | },
1335 | "notification-url": "https://packagist.org/downloads/",
1336 | "license": [
1337 | "MIT"
1338 | ],
1339 | "description": "The dependency injection container for humans",
1340 | "homepage": "http://php-di.org/",
1341 | "keywords": [
1342 | "PSR-11",
1343 | "container",
1344 | "container-interop",
1345 | "dependency injection",
1346 | "di",
1347 | "ioc",
1348 | "psr11"
1349 | ],
1350 | "time": "2020-04-06T09:54:49+00:00"
1351 | },
1352 | {
1353 | "name": "php-di/phpdoc-reader",
1354 | "version": "2.1.1",
1355 | "source": {
1356 | "type": "git",
1357 | "url": "https://github.com/PHP-DI/PhpDocReader.git",
1358 | "reference": "15678f7451c020226807f520efb867ad26fbbfcf"
1359 | },
1360 | "dist": {
1361 | "type": "zip",
1362 | "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/15678f7451c020226807f520efb867ad26fbbfcf",
1363 | "reference": "15678f7451c020226807f520efb867ad26fbbfcf",
1364 | "shasum": ""
1365 | },
1366 | "require": {
1367 | "php": ">=5.4.0"
1368 | },
1369 | "require-dev": {
1370 | "phpunit/phpunit": "~4.6"
1371 | },
1372 | "type": "library",
1373 | "autoload": {
1374 | "psr-4": {
1375 | "PhpDocReader\\": "src/PhpDocReader"
1376 | }
1377 | },
1378 | "notification-url": "https://packagist.org/downloads/",
1379 | "license": [
1380 | "MIT"
1381 | ],
1382 | "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)",
1383 | "keywords": [
1384 | "phpdoc",
1385 | "reflection"
1386 | ],
1387 | "time": "2019-09-26T11:24:58+00:00"
1388 | },
1389 | {
1390 | "name": "psr/container",
1391 | "version": "1.0.0",
1392 | "source": {
1393 | "type": "git",
1394 | "url": "https://github.com/php-fig/container.git",
1395 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
1396 | },
1397 | "dist": {
1398 | "type": "zip",
1399 | "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
1400 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
1401 | "shasum": ""
1402 | },
1403 | "require": {
1404 | "php": ">=5.3.0"
1405 | },
1406 | "type": "library",
1407 | "extra": {
1408 | "branch-alias": {
1409 | "dev-master": "1.0.x-dev"
1410 | }
1411 | },
1412 | "autoload": {
1413 | "psr-4": {
1414 | "Psr\\Container\\": "src/"
1415 | }
1416 | },
1417 | "notification-url": "https://packagist.org/downloads/",
1418 | "license": [
1419 | "MIT"
1420 | ],
1421 | "authors": [
1422 | {
1423 | "name": "PHP-FIG",
1424 | "homepage": "http://www.php-fig.org/"
1425 | }
1426 | ],
1427 | "description": "Common Container Interface (PHP FIG PSR-11)",
1428 | "homepage": "https://github.com/php-fig/container",
1429 | "keywords": [
1430 | "PSR-11",
1431 | "container",
1432 | "container-interface",
1433 | "container-interop",
1434 | "psr"
1435 | ],
1436 | "time": "2017-02-14T16:28:37+00:00"
1437 | },
1438 | {
1439 | "name": "psr/http-factory",
1440 | "version": "1.0.1",
1441 | "source": {
1442 | "type": "git",
1443 | "url": "https://github.com/php-fig/http-factory.git",
1444 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
1445 | },
1446 | "dist": {
1447 | "type": "zip",
1448 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
1449 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
1450 | "shasum": ""
1451 | },
1452 | "require": {
1453 | "php": ">=7.0.0",
1454 | "psr/http-message": "^1.0"
1455 | },
1456 | "type": "library",
1457 | "extra": {
1458 | "branch-alias": {
1459 | "dev-master": "1.0.x-dev"
1460 | }
1461 | },
1462 | "autoload": {
1463 | "psr-4": {
1464 | "Psr\\Http\\Message\\": "src/"
1465 | }
1466 | },
1467 | "notification-url": "https://packagist.org/downloads/",
1468 | "license": [
1469 | "MIT"
1470 | ],
1471 | "authors": [
1472 | {
1473 | "name": "PHP-FIG",
1474 | "homepage": "http://www.php-fig.org/"
1475 | }
1476 | ],
1477 | "description": "Common interfaces for PSR-7 HTTP message factories",
1478 | "keywords": [
1479 | "factory",
1480 | "http",
1481 | "message",
1482 | "psr",
1483 | "psr-17",
1484 | "psr-7",
1485 | "request",
1486 | "response"
1487 | ],
1488 | "time": "2019-04-30T12:38:16+00:00"
1489 | },
1490 | {
1491 | "name": "psr/http-message",
1492 | "version": "1.0.1",
1493 | "source": {
1494 | "type": "git",
1495 | "url": "https://github.com/php-fig/http-message.git",
1496 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
1497 | },
1498 | "dist": {
1499 | "type": "zip",
1500 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
1501 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
1502 | "shasum": ""
1503 | },
1504 | "require": {
1505 | "php": ">=5.3.0"
1506 | },
1507 | "type": "library",
1508 | "extra": {
1509 | "branch-alias": {
1510 | "dev-master": "1.0.x-dev"
1511 | }
1512 | },
1513 | "autoload": {
1514 | "psr-4": {
1515 | "Psr\\Http\\Message\\": "src/"
1516 | }
1517 | },
1518 | "notification-url": "https://packagist.org/downloads/",
1519 | "license": [
1520 | "MIT"
1521 | ],
1522 | "authors": [
1523 | {
1524 | "name": "PHP-FIG",
1525 | "homepage": "http://www.php-fig.org/"
1526 | }
1527 | ],
1528 | "description": "Common interface for HTTP messages",
1529 | "homepage": "https://github.com/php-fig/http-message",
1530 | "keywords": [
1531 | "http",
1532 | "http-message",
1533 | "psr",
1534 | "psr-7",
1535 | "request",
1536 | "response"
1537 | ],
1538 | "time": "2016-08-06T14:39:51+00:00"
1539 | },
1540 | {
1541 | "name": "psr/http-server-handler",
1542 | "version": "1.0.1",
1543 | "source": {
1544 | "type": "git",
1545 | "url": "https://github.com/php-fig/http-server-handler.git",
1546 | "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7"
1547 | },
1548 | "dist": {
1549 | "type": "zip",
1550 | "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
1551 | "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
1552 | "shasum": ""
1553 | },
1554 | "require": {
1555 | "php": ">=7.0",
1556 | "psr/http-message": "^1.0"
1557 | },
1558 | "type": "library",
1559 | "extra": {
1560 | "branch-alias": {
1561 | "dev-master": "1.0.x-dev"
1562 | }
1563 | },
1564 | "autoload": {
1565 | "psr-4": {
1566 | "Psr\\Http\\Server\\": "src/"
1567 | }
1568 | },
1569 | "notification-url": "https://packagist.org/downloads/",
1570 | "license": [
1571 | "MIT"
1572 | ],
1573 | "authors": [
1574 | {
1575 | "name": "PHP-FIG",
1576 | "homepage": "http://www.php-fig.org/"
1577 | }
1578 | ],
1579 | "description": "Common interface for HTTP server-side request handler",
1580 | "keywords": [
1581 | "handler",
1582 | "http",
1583 | "http-interop",
1584 | "psr",
1585 | "psr-15",
1586 | "psr-7",
1587 | "request",
1588 | "response",
1589 | "server"
1590 | ],
1591 | "time": "2018-10-30T16:46:14+00:00"
1592 | },
1593 | {
1594 | "name": "psr/http-server-middleware",
1595 | "version": "1.0.1",
1596 | "source": {
1597 | "type": "git",
1598 | "url": "https://github.com/php-fig/http-server-middleware.git",
1599 | "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5"
1600 | },
1601 | "dist": {
1602 | "type": "zip",
1603 | "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5",
1604 | "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5",
1605 | "shasum": ""
1606 | },
1607 | "require": {
1608 | "php": ">=7.0",
1609 | "psr/http-message": "^1.0",
1610 | "psr/http-server-handler": "^1.0"
1611 | },
1612 | "type": "library",
1613 | "extra": {
1614 | "branch-alias": {
1615 | "dev-master": "1.0.x-dev"
1616 | }
1617 | },
1618 | "autoload": {
1619 | "psr-4": {
1620 | "Psr\\Http\\Server\\": "src/"
1621 | }
1622 | },
1623 | "notification-url": "https://packagist.org/downloads/",
1624 | "license": [
1625 | "MIT"
1626 | ],
1627 | "authors": [
1628 | {
1629 | "name": "PHP-FIG",
1630 | "homepage": "http://www.php-fig.org/"
1631 | }
1632 | ],
1633 | "description": "Common interface for HTTP server-side middleware",
1634 | "keywords": [
1635 | "http",
1636 | "http-interop",
1637 | "middleware",
1638 | "psr",
1639 | "psr-15",
1640 | "psr-7",
1641 | "request",
1642 | "response"
1643 | ],
1644 | "time": "2018-10-30T17:12:04+00:00"
1645 | },
1646 | {
1647 | "name": "psr/log",
1648 | "version": "1.1.3",
1649 | "source": {
1650 | "type": "git",
1651 | "url": "https://github.com/php-fig/log.git",
1652 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
1653 | },
1654 | "dist": {
1655 | "type": "zip",
1656 | "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
1657 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
1658 | "shasum": ""
1659 | },
1660 | "require": {
1661 | "php": ">=5.3.0"
1662 | },
1663 | "type": "library",
1664 | "extra": {
1665 | "branch-alias": {
1666 | "dev-master": "1.1.x-dev"
1667 | }
1668 | },
1669 | "autoload": {
1670 | "psr-4": {
1671 | "Psr\\Log\\": "Psr/Log/"
1672 | }
1673 | },
1674 | "notification-url": "https://packagist.org/downloads/",
1675 | "license": [
1676 | "MIT"
1677 | ],
1678 | "authors": [
1679 | {
1680 | "name": "PHP-FIG",
1681 | "homepage": "http://www.php-fig.org/"
1682 | }
1683 | ],
1684 | "description": "Common interface for logging libraries",
1685 | "homepage": "https://github.com/php-fig/log",
1686 | "keywords": [
1687 | "log",
1688 | "psr",
1689 | "psr-3"
1690 | ],
1691 | "time": "2020-03-23T09:12:05+00:00"
1692 | },
1693 | {
1694 | "name": "ramsey/uuid",
1695 | "version": "3.9.3",
1696 | "source": {
1697 | "type": "git",
1698 | "url": "https://github.com/ramsey/uuid.git",
1699 | "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92"
1700 | },
1701 | "dist": {
1702 | "type": "zip",
1703 | "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92",
1704 | "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92",
1705 | "shasum": ""
1706 | },
1707 | "require": {
1708 | "ext-json": "*",
1709 | "paragonie/random_compat": "^1 | ^2 | 9.99.99",
1710 | "php": "^5.4 | ^7 | ^8",
1711 | "symfony/polyfill-ctype": "^1.8"
1712 | },
1713 | "replace": {
1714 | "rhumsaa/uuid": "self.version"
1715 | },
1716 | "require-dev": {
1717 | "codeception/aspect-mock": "^1 | ^2",
1718 | "doctrine/annotations": "^1.2",
1719 | "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1",
1720 | "jakub-onderka/php-parallel-lint": "^1",
1721 | "mockery/mockery": "^0.9.11 | ^1",
1722 | "moontoast/math": "^1.1",
1723 | "paragonie/random-lib": "^2",
1724 | "php-mock/php-mock-phpunit": "^0.3 | ^1.1",
1725 | "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5",
1726 | "squizlabs/php_codesniffer": "^3.5"
1727 | },
1728 | "suggest": {
1729 | "ext-ctype": "Provides support for PHP Ctype functions",
1730 | "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator",
1731 | "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator",
1732 | "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator",
1733 | "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).",
1734 | "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
1735 | "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid",
1736 | "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
1737 | },
1738 | "type": "library",
1739 | "extra": {
1740 | "branch-alias": {
1741 | "dev-master": "3.x-dev"
1742 | }
1743 | },
1744 | "autoload": {
1745 | "psr-4": {
1746 | "Ramsey\\Uuid\\": "src/"
1747 | },
1748 | "files": [
1749 | "src/functions.php"
1750 | ]
1751 | },
1752 | "notification-url": "https://packagist.org/downloads/",
1753 | "license": [
1754 | "MIT"
1755 | ],
1756 | "authors": [
1757 | {
1758 | "name": "Ben Ramsey",
1759 | "email": "ben@benramsey.com",
1760 | "homepage": "https://benramsey.com"
1761 | },
1762 | {
1763 | "name": "Marijn Huizendveld",
1764 | "email": "marijn.huizendveld@gmail.com"
1765 | },
1766 | {
1767 | "name": "Thibaud Fabre",
1768 | "email": "thibaud@aztech.io"
1769 | }
1770 | ],
1771 | "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).",
1772 | "homepage": "https://github.com/ramsey/uuid",
1773 | "keywords": [
1774 | "guid",
1775 | "identifier",
1776 | "uuid"
1777 | ],
1778 | "time": "2020-02-21T04:36:14+00:00"
1779 | },
1780 | {
1781 | "name": "symfony/polyfill-ctype",
1782 | "version": "v1.15.0",
1783 | "source": {
1784 | "type": "git",
1785 | "url": "https://github.com/symfony/polyfill-ctype.git",
1786 | "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14"
1787 | },
1788 | "dist": {
1789 | "type": "zip",
1790 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14",
1791 | "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14",
1792 | "shasum": ""
1793 | },
1794 | "require": {
1795 | "php": ">=5.3.3"
1796 | },
1797 | "suggest": {
1798 | "ext-ctype": "For best performance"
1799 | },
1800 | "type": "library",
1801 | "extra": {
1802 | "branch-alias": {
1803 | "dev-master": "1.15-dev"
1804 | }
1805 | },
1806 | "autoload": {
1807 | "psr-4": {
1808 | "Symfony\\Polyfill\\Ctype\\": ""
1809 | },
1810 | "files": [
1811 | "bootstrap.php"
1812 | ]
1813 | },
1814 | "notification-url": "https://packagist.org/downloads/",
1815 | "license": [
1816 | "MIT"
1817 | ],
1818 | "authors": [
1819 | {
1820 | "name": "Gert de Pagter",
1821 | "email": "BackEndTea@gmail.com"
1822 | },
1823 | {
1824 | "name": "Symfony Community",
1825 | "homepage": "https://symfony.com/contributors"
1826 | }
1827 | ],
1828 | "description": "Symfony polyfill for ctype functions",
1829 | "homepage": "https://symfony.com",
1830 | "keywords": [
1831 | "compatibility",
1832 | "ctype",
1833 | "polyfill",
1834 | "portable"
1835 | ],
1836 | "time": "2020-02-27T09:26:54+00:00"
1837 | },
1838 | {
1839 | "name": "symfony/polyfill-php56",
1840 | "version": "v1.15.0",
1841 | "source": {
1842 | "type": "git",
1843 | "url": "https://github.com/symfony/polyfill-php56.git",
1844 | "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d"
1845 | },
1846 | "dist": {
1847 | "type": "zip",
1848 | "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/d51ec491c8ddceae7dca8dd6c7e30428f543f37d",
1849 | "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d",
1850 | "shasum": ""
1851 | },
1852 | "require": {
1853 | "php": ">=5.3.3",
1854 | "symfony/polyfill-util": "~1.0"
1855 | },
1856 | "type": "library",
1857 | "extra": {
1858 | "branch-alias": {
1859 | "dev-master": "1.15-dev"
1860 | }
1861 | },
1862 | "autoload": {
1863 | "psr-4": {
1864 | "Symfony\\Polyfill\\Php56\\": ""
1865 | },
1866 | "files": [
1867 | "bootstrap.php"
1868 | ]
1869 | },
1870 | "notification-url": "https://packagist.org/downloads/",
1871 | "license": [
1872 | "MIT"
1873 | ],
1874 | "authors": [
1875 | {
1876 | "name": "Nicolas Grekas",
1877 | "email": "p@tchwork.com"
1878 | },
1879 | {
1880 | "name": "Symfony Community",
1881 | "homepage": "https://symfony.com/contributors"
1882 | }
1883 | ],
1884 | "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
1885 | "homepage": "https://symfony.com",
1886 | "keywords": [
1887 | "compatibility",
1888 | "polyfill",
1889 | "portable",
1890 | "shim"
1891 | ],
1892 | "time": "2020-03-09T19:04:49+00:00"
1893 | },
1894 | {
1895 | "name": "symfony/polyfill-util",
1896 | "version": "v1.15.0",
1897 | "source": {
1898 | "type": "git",
1899 | "url": "https://github.com/symfony/polyfill-util.git",
1900 | "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027"
1901 | },
1902 | "dist": {
1903 | "type": "zip",
1904 | "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/d8e76c104127675d0ea3df3be0f2ae24a8619027",
1905 | "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027",
1906 | "shasum": ""
1907 | },
1908 | "require": {
1909 | "php": ">=5.3.3"
1910 | },
1911 | "type": "library",
1912 | "extra": {
1913 | "branch-alias": {
1914 | "dev-master": "1.15-dev"
1915 | }
1916 | },
1917 | "autoload": {
1918 | "psr-4": {
1919 | "Symfony\\Polyfill\\Util\\": ""
1920 | }
1921 | },
1922 | "notification-url": "https://packagist.org/downloads/",
1923 | "license": [
1924 | "MIT"
1925 | ],
1926 | "authors": [
1927 | {
1928 | "name": "Nicolas Grekas",
1929 | "email": "p@tchwork.com"
1930 | },
1931 | {
1932 | "name": "Symfony Community",
1933 | "homepage": "https://symfony.com/contributors"
1934 | }
1935 | ],
1936 | "description": "Symfony utilities for portability of PHP codes",
1937 | "homepage": "https://symfony.com",
1938 | "keywords": [
1939 | "compat",
1940 | "compatibility",
1941 | "polyfill",
1942 | "shim"
1943 | ],
1944 | "time": "2020-03-02T11:55:35+00:00"
1945 | },
1946 | {
1947 | "name": "webimpress/safe-writer",
1948 | "version": "2.0.1",
1949 | "source": {
1950 | "type": "git",
1951 | "url": "https://github.com/webimpress/safe-writer.git",
1952 | "reference": "d6e879960febb307c112538997316371f1e95b12"
1953 | },
1954 | "dist": {
1955 | "type": "zip",
1956 | "url": "https://api.github.com/repos/webimpress/safe-writer/zipball/d6e879960febb307c112538997316371f1e95b12",
1957 | "reference": "d6e879960febb307c112538997316371f1e95b12",
1958 | "shasum": ""
1959 | },
1960 | "require": {
1961 | "php": "^7.2"
1962 | },
1963 | "require-dev": {
1964 | "phpunit/phpunit": "^8.5.2 || ^9.0.1",
1965 | "webimpress/coding-standard": "^1.1.4"
1966 | },
1967 | "type": "library",
1968 | "extra": {
1969 | "branch-alias": {
1970 | "dev-master": "2.0.x-dev",
1971 | "dev-develop": "2.1.x-dev",
1972 | "dev-release-1.0": "1.0.x-dev"
1973 | }
1974 | },
1975 | "autoload": {
1976 | "psr-4": {
1977 | "Webimpress\\SafeWriter\\": "src/"
1978 | }
1979 | },
1980 | "notification-url": "https://packagist.org/downloads/",
1981 | "license": [
1982 | "BSD-2-Clause"
1983 | ],
1984 | "description": "Tool to write files safely, to avoid race conditions",
1985 | "keywords": [
1986 | "concurrent write",
1987 | "file writer",
1988 | "race condition",
1989 | "safe writer",
1990 | "webimpress"
1991 | ],
1992 | "funding": [
1993 | {
1994 | "url": "https://github.com/michalbundyra",
1995 | "type": "github"
1996 | }
1997 | ],
1998 | "time": "2020-03-21T15:49:08+00:00"
1999 | },
2000 | {
2001 | "name": "willdurand/negotiation",
2002 | "version": "v2.3.1",
2003 | "source": {
2004 | "type": "git",
2005 | "url": "https://github.com/willdurand/Negotiation.git",
2006 | "reference": "03436ededa67c6e83b9b12defac15384cb399dc9"
2007 | },
2008 | "dist": {
2009 | "type": "zip",
2010 | "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/03436ededa67c6e83b9b12defac15384cb399dc9",
2011 | "reference": "03436ededa67c6e83b9b12defac15384cb399dc9",
2012 | "shasum": ""
2013 | },
2014 | "require": {
2015 | "php": ">=5.4.0"
2016 | },
2017 | "require-dev": {
2018 | "phpunit/phpunit": "~4.5"
2019 | },
2020 | "type": "library",
2021 | "extra": {
2022 | "branch-alias": {
2023 | "dev-master": "2.3-dev"
2024 | }
2025 | },
2026 | "autoload": {
2027 | "psr-4": {
2028 | "Negotiation\\": "src/Negotiation"
2029 | }
2030 | },
2031 | "notification-url": "https://packagist.org/downloads/",
2032 | "license": [
2033 | "MIT"
2034 | ],
2035 | "authors": [
2036 | {
2037 | "name": "William Durand",
2038 | "email": "will+git@drnd.me"
2039 | }
2040 | ],
2041 | "description": "Content Negotiation tools for PHP provided as a standalone library.",
2042 | "homepage": "http://williamdurand.fr/Negotiation/",
2043 | "keywords": [
2044 | "accept",
2045 | "content",
2046 | "format",
2047 | "header",
2048 | "negotiation"
2049 | ],
2050 | "time": "2017-05-14T17:21:12+00:00"
2051 | }
2052 | ],
2053 | "packages-dev": [
2054 | {
2055 | "name": "myclabs/deep-copy",
2056 | "version": "1.9.5",
2057 | "source": {
2058 | "type": "git",
2059 | "url": "https://github.com/myclabs/DeepCopy.git",
2060 | "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef"
2061 | },
2062 | "dist": {
2063 | "type": "zip",
2064 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef",
2065 | "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef",
2066 | "shasum": ""
2067 | },
2068 | "require": {
2069 | "php": "^7.1"
2070 | },
2071 | "replace": {
2072 | "myclabs/deep-copy": "self.version"
2073 | },
2074 | "require-dev": {
2075 | "doctrine/collections": "^1.0",
2076 | "doctrine/common": "^2.6",
2077 | "phpunit/phpunit": "^7.1"
2078 | },
2079 | "type": "library",
2080 | "autoload": {
2081 | "psr-4": {
2082 | "DeepCopy\\": "src/DeepCopy/"
2083 | },
2084 | "files": [
2085 | "src/DeepCopy/deep_copy.php"
2086 | ]
2087 | },
2088 | "notification-url": "https://packagist.org/downloads/",
2089 | "license": [
2090 | "MIT"
2091 | ],
2092 | "description": "Create deep copies (clones) of your objects",
2093 | "keywords": [
2094 | "clone",
2095 | "copy",
2096 | "duplicate",
2097 | "object",
2098 | "object graph"
2099 | ],
2100 | "time": "2020-01-17T21:11:47+00:00"
2101 | },
2102 | {
2103 | "name": "phar-io/manifest",
2104 | "version": "1.0.3",
2105 | "source": {
2106 | "type": "git",
2107 | "url": "https://github.com/phar-io/manifest.git",
2108 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
2109 | },
2110 | "dist": {
2111 | "type": "zip",
2112 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
2113 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
2114 | "shasum": ""
2115 | },
2116 | "require": {
2117 | "ext-dom": "*",
2118 | "ext-phar": "*",
2119 | "phar-io/version": "^2.0",
2120 | "php": "^5.6 || ^7.0"
2121 | },
2122 | "type": "library",
2123 | "extra": {
2124 | "branch-alias": {
2125 | "dev-master": "1.0.x-dev"
2126 | }
2127 | },
2128 | "autoload": {
2129 | "classmap": [
2130 | "src/"
2131 | ]
2132 | },
2133 | "notification-url": "https://packagist.org/downloads/",
2134 | "license": [
2135 | "BSD-3-Clause"
2136 | ],
2137 | "authors": [
2138 | {
2139 | "name": "Arne Blankerts",
2140 | "email": "arne@blankerts.de",
2141 | "role": "Developer"
2142 | },
2143 | {
2144 | "name": "Sebastian Heuer",
2145 | "email": "sebastian@phpeople.de",
2146 | "role": "Developer"
2147 | },
2148 | {
2149 | "name": "Sebastian Bergmann",
2150 | "email": "sebastian@phpunit.de",
2151 | "role": "Developer"
2152 | }
2153 | ],
2154 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
2155 | "time": "2018-07-08T19:23:20+00:00"
2156 | },
2157 | {
2158 | "name": "phar-io/version",
2159 | "version": "2.0.1",
2160 | "source": {
2161 | "type": "git",
2162 | "url": "https://github.com/phar-io/version.git",
2163 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
2164 | },
2165 | "dist": {
2166 | "type": "zip",
2167 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
2168 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
2169 | "shasum": ""
2170 | },
2171 | "require": {
2172 | "php": "^5.6 || ^7.0"
2173 | },
2174 | "type": "library",
2175 | "autoload": {
2176 | "classmap": [
2177 | "src/"
2178 | ]
2179 | },
2180 | "notification-url": "https://packagist.org/downloads/",
2181 | "license": [
2182 | "BSD-3-Clause"
2183 | ],
2184 | "authors": [
2185 | {
2186 | "name": "Arne Blankerts",
2187 | "email": "arne@blankerts.de",
2188 | "role": "Developer"
2189 | },
2190 | {
2191 | "name": "Sebastian Heuer",
2192 | "email": "sebastian@phpeople.de",
2193 | "role": "Developer"
2194 | },
2195 | {
2196 | "name": "Sebastian Bergmann",
2197 | "email": "sebastian@phpunit.de",
2198 | "role": "Developer"
2199 | }
2200 | ],
2201 | "description": "Library for handling version information and constraints",
2202 | "time": "2018-07-08T19:19:57+00:00"
2203 | },
2204 | {
2205 | "name": "phpdocumentor/reflection-common",
2206 | "version": "2.1.0",
2207 | "source": {
2208 | "type": "git",
2209 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
2210 | "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b"
2211 | },
2212 | "dist": {
2213 | "type": "zip",
2214 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
2215 | "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b",
2216 | "shasum": ""
2217 | },
2218 | "require": {
2219 | "php": ">=7.1"
2220 | },
2221 | "type": "library",
2222 | "extra": {
2223 | "branch-alias": {
2224 | "dev-master": "2.x-dev"
2225 | }
2226 | },
2227 | "autoload": {
2228 | "psr-4": {
2229 | "phpDocumentor\\Reflection\\": "src/"
2230 | }
2231 | },
2232 | "notification-url": "https://packagist.org/downloads/",
2233 | "license": [
2234 | "MIT"
2235 | ],
2236 | "authors": [
2237 | {
2238 | "name": "Jaap van Otterdijk",
2239 | "email": "opensource@ijaap.nl"
2240 | }
2241 | ],
2242 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
2243 | "homepage": "http://www.phpdoc.org",
2244 | "keywords": [
2245 | "FQSEN",
2246 | "phpDocumentor",
2247 | "phpdoc",
2248 | "reflection",
2249 | "static analysis"
2250 | ],
2251 | "time": "2020-04-27T09:25:28+00:00"
2252 | },
2253 | {
2254 | "name": "phpdocumentor/reflection-docblock",
2255 | "version": "5.1.0",
2256 | "source": {
2257 | "type": "git",
2258 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
2259 | "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e"
2260 | },
2261 | "dist": {
2262 | "type": "zip",
2263 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e",
2264 | "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e",
2265 | "shasum": ""
2266 | },
2267 | "require": {
2268 | "ext-filter": "^7.1",
2269 | "php": "^7.2",
2270 | "phpdocumentor/reflection-common": "^2.0",
2271 | "phpdocumentor/type-resolver": "^1.0",
2272 | "webmozart/assert": "^1"
2273 | },
2274 | "require-dev": {
2275 | "doctrine/instantiator": "^1",
2276 | "mockery/mockery": "^1"
2277 | },
2278 | "type": "library",
2279 | "extra": {
2280 | "branch-alias": {
2281 | "dev-master": "5.x-dev"
2282 | }
2283 | },
2284 | "autoload": {
2285 | "psr-4": {
2286 | "phpDocumentor\\Reflection\\": "src"
2287 | }
2288 | },
2289 | "notification-url": "https://packagist.org/downloads/",
2290 | "license": [
2291 | "MIT"
2292 | ],
2293 | "authors": [
2294 | {
2295 | "name": "Mike van Riel",
2296 | "email": "me@mikevanriel.com"
2297 | },
2298 | {
2299 | "name": "Jaap van Otterdijk",
2300 | "email": "account@ijaap.nl"
2301 | }
2302 | ],
2303 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
2304 | "time": "2020-02-22T12:28:44+00:00"
2305 | },
2306 | {
2307 | "name": "phpdocumentor/type-resolver",
2308 | "version": "1.1.0",
2309 | "source": {
2310 | "type": "git",
2311 | "url": "https://github.com/phpDocumentor/TypeResolver.git",
2312 | "reference": "7462d5f123dfc080dfdf26897032a6513644fc95"
2313 | },
2314 | "dist": {
2315 | "type": "zip",
2316 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95",
2317 | "reference": "7462d5f123dfc080dfdf26897032a6513644fc95",
2318 | "shasum": ""
2319 | },
2320 | "require": {
2321 | "php": "^7.2",
2322 | "phpdocumentor/reflection-common": "^2.0"
2323 | },
2324 | "require-dev": {
2325 | "ext-tokenizer": "^7.2",
2326 | "mockery/mockery": "~1"
2327 | },
2328 | "type": "library",
2329 | "extra": {
2330 | "branch-alias": {
2331 | "dev-master": "1.x-dev"
2332 | }
2333 | },
2334 | "autoload": {
2335 | "psr-4": {
2336 | "phpDocumentor\\Reflection\\": "src"
2337 | }
2338 | },
2339 | "notification-url": "https://packagist.org/downloads/",
2340 | "license": [
2341 | "MIT"
2342 | ],
2343 | "authors": [
2344 | {
2345 | "name": "Mike van Riel",
2346 | "email": "me@mikevanriel.com"
2347 | }
2348 | ],
2349 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
2350 | "time": "2020-02-18T18:59:58+00:00"
2351 | },
2352 | {
2353 | "name": "phpspec/prophecy",
2354 | "version": "v1.10.3",
2355 | "source": {
2356 | "type": "git",
2357 | "url": "https://github.com/phpspec/prophecy.git",
2358 | "reference": "451c3cd1418cf640de218914901e51b064abb093"
2359 | },
2360 | "dist": {
2361 | "type": "zip",
2362 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
2363 | "reference": "451c3cd1418cf640de218914901e51b064abb093",
2364 | "shasum": ""
2365 | },
2366 | "require": {
2367 | "doctrine/instantiator": "^1.0.2",
2368 | "php": "^5.3|^7.0",
2369 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
2370 | "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
2371 | "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
2372 | },
2373 | "require-dev": {
2374 | "phpspec/phpspec": "^2.5 || ^3.2",
2375 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
2376 | },
2377 | "type": "library",
2378 | "extra": {
2379 | "branch-alias": {
2380 | "dev-master": "1.10.x-dev"
2381 | }
2382 | },
2383 | "autoload": {
2384 | "psr-4": {
2385 | "Prophecy\\": "src/Prophecy"
2386 | }
2387 | },
2388 | "notification-url": "https://packagist.org/downloads/",
2389 | "license": [
2390 | "MIT"
2391 | ],
2392 | "authors": [
2393 | {
2394 | "name": "Konstantin Kudryashov",
2395 | "email": "ever.zet@gmail.com",
2396 | "homepage": "http://everzet.com"
2397 | },
2398 | {
2399 | "name": "Marcello Duarte",
2400 | "email": "marcello.duarte@gmail.com"
2401 | }
2402 | ],
2403 | "description": "Highly opinionated mocking framework for PHP 5.3+",
2404 | "homepage": "https://github.com/phpspec/prophecy",
2405 | "keywords": [
2406 | "Double",
2407 | "Dummy",
2408 | "fake",
2409 | "mock",
2410 | "spy",
2411 | "stub"
2412 | ],
2413 | "time": "2020-03-05T15:02:03+00:00"
2414 | },
2415 | {
2416 | "name": "phpunit/php-code-coverage",
2417 | "version": "7.0.10",
2418 | "source": {
2419 | "type": "git",
2420 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
2421 | "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf"
2422 | },
2423 | "dist": {
2424 | "type": "zip",
2425 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf",
2426 | "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf",
2427 | "shasum": ""
2428 | },
2429 | "require": {
2430 | "ext-dom": "*",
2431 | "ext-xmlwriter": "*",
2432 | "php": "^7.2",
2433 | "phpunit/php-file-iterator": "^2.0.2",
2434 | "phpunit/php-text-template": "^1.2.1",
2435 | "phpunit/php-token-stream": "^3.1.1",
2436 | "sebastian/code-unit-reverse-lookup": "^1.0.1",
2437 | "sebastian/environment": "^4.2.2",
2438 | "sebastian/version": "^2.0.1",
2439 | "theseer/tokenizer": "^1.1.3"
2440 | },
2441 | "require-dev": {
2442 | "phpunit/phpunit": "^8.2.2"
2443 | },
2444 | "suggest": {
2445 | "ext-xdebug": "^2.7.2"
2446 | },
2447 | "type": "library",
2448 | "extra": {
2449 | "branch-alias": {
2450 | "dev-master": "7.0-dev"
2451 | }
2452 | },
2453 | "autoload": {
2454 | "classmap": [
2455 | "src/"
2456 | ]
2457 | },
2458 | "notification-url": "https://packagist.org/downloads/",
2459 | "license": [
2460 | "BSD-3-Clause"
2461 | ],
2462 | "authors": [
2463 | {
2464 | "name": "Sebastian Bergmann",
2465 | "email": "sebastian@phpunit.de",
2466 | "role": "lead"
2467 | }
2468 | ],
2469 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
2470 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
2471 | "keywords": [
2472 | "coverage",
2473 | "testing",
2474 | "xunit"
2475 | ],
2476 | "time": "2019-11-20T13:55:58+00:00"
2477 | },
2478 | {
2479 | "name": "phpunit/php-file-iterator",
2480 | "version": "2.0.2",
2481 | "source": {
2482 | "type": "git",
2483 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
2484 | "reference": "050bedf145a257b1ff02746c31894800e5122946"
2485 | },
2486 | "dist": {
2487 | "type": "zip",
2488 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946",
2489 | "reference": "050bedf145a257b1ff02746c31894800e5122946",
2490 | "shasum": ""
2491 | },
2492 | "require": {
2493 | "php": "^7.1"
2494 | },
2495 | "require-dev": {
2496 | "phpunit/phpunit": "^7.1"
2497 | },
2498 | "type": "library",
2499 | "extra": {
2500 | "branch-alias": {
2501 | "dev-master": "2.0.x-dev"
2502 | }
2503 | },
2504 | "autoload": {
2505 | "classmap": [
2506 | "src/"
2507 | ]
2508 | },
2509 | "notification-url": "https://packagist.org/downloads/",
2510 | "license": [
2511 | "BSD-3-Clause"
2512 | ],
2513 | "authors": [
2514 | {
2515 | "name": "Sebastian Bergmann",
2516 | "email": "sebastian@phpunit.de",
2517 | "role": "lead"
2518 | }
2519 | ],
2520 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
2521 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
2522 | "keywords": [
2523 | "filesystem",
2524 | "iterator"
2525 | ],
2526 | "time": "2018-09-13T20:33:42+00:00"
2527 | },
2528 | {
2529 | "name": "phpunit/php-text-template",
2530 | "version": "1.2.1",
2531 | "source": {
2532 | "type": "git",
2533 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
2534 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
2535 | },
2536 | "dist": {
2537 | "type": "zip",
2538 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
2539 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
2540 | "shasum": ""
2541 | },
2542 | "require": {
2543 | "php": ">=5.3.3"
2544 | },
2545 | "type": "library",
2546 | "autoload": {
2547 | "classmap": [
2548 | "src/"
2549 | ]
2550 | },
2551 | "notification-url": "https://packagist.org/downloads/",
2552 | "license": [
2553 | "BSD-3-Clause"
2554 | ],
2555 | "authors": [
2556 | {
2557 | "name": "Sebastian Bergmann",
2558 | "email": "sebastian@phpunit.de",
2559 | "role": "lead"
2560 | }
2561 | ],
2562 | "description": "Simple template engine.",
2563 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
2564 | "keywords": [
2565 | "template"
2566 | ],
2567 | "time": "2015-06-21T13:50:34+00:00"
2568 | },
2569 | {
2570 | "name": "phpunit/php-timer",
2571 | "version": "2.1.2",
2572 | "source": {
2573 | "type": "git",
2574 | "url": "https://github.com/sebastianbergmann/php-timer.git",
2575 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e"
2576 | },
2577 | "dist": {
2578 | "type": "zip",
2579 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e",
2580 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e",
2581 | "shasum": ""
2582 | },
2583 | "require": {
2584 | "php": "^7.1"
2585 | },
2586 | "require-dev": {
2587 | "phpunit/phpunit": "^7.0"
2588 | },
2589 | "type": "library",
2590 | "extra": {
2591 | "branch-alias": {
2592 | "dev-master": "2.1-dev"
2593 | }
2594 | },
2595 | "autoload": {
2596 | "classmap": [
2597 | "src/"
2598 | ]
2599 | },
2600 | "notification-url": "https://packagist.org/downloads/",
2601 | "license": [
2602 | "BSD-3-Clause"
2603 | ],
2604 | "authors": [
2605 | {
2606 | "name": "Sebastian Bergmann",
2607 | "email": "sebastian@phpunit.de",
2608 | "role": "lead"
2609 | }
2610 | ],
2611 | "description": "Utility class for timing",
2612 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
2613 | "keywords": [
2614 | "timer"
2615 | ],
2616 | "time": "2019-06-07T04:22:29+00:00"
2617 | },
2618 | {
2619 | "name": "phpunit/php-token-stream",
2620 | "version": "3.1.1",
2621 | "source": {
2622 | "type": "git",
2623 | "url": "https://github.com/sebastianbergmann/php-token-stream.git",
2624 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff"
2625 | },
2626 | "dist": {
2627 | "type": "zip",
2628 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff",
2629 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff",
2630 | "shasum": ""
2631 | },
2632 | "require": {
2633 | "ext-tokenizer": "*",
2634 | "php": "^7.1"
2635 | },
2636 | "require-dev": {
2637 | "phpunit/phpunit": "^7.0"
2638 | },
2639 | "type": "library",
2640 | "extra": {
2641 | "branch-alias": {
2642 | "dev-master": "3.1-dev"
2643 | }
2644 | },
2645 | "autoload": {
2646 | "classmap": [
2647 | "src/"
2648 | ]
2649 | },
2650 | "notification-url": "https://packagist.org/downloads/",
2651 | "license": [
2652 | "BSD-3-Clause"
2653 | ],
2654 | "authors": [
2655 | {
2656 | "name": "Sebastian Bergmann",
2657 | "email": "sebastian@phpunit.de"
2658 | }
2659 | ],
2660 | "description": "Wrapper around PHP's tokenizer extension.",
2661 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
2662 | "keywords": [
2663 | "tokenizer"
2664 | ],
2665 | "time": "2019-09-17T06:23:10+00:00"
2666 | },
2667 | {
2668 | "name": "phpunit/phpunit",
2669 | "version": "8.5.4",
2670 | "source": {
2671 | "type": "git",
2672 | "url": "https://github.com/sebastianbergmann/phpunit.git",
2673 | "reference": "8474e22d7d642f665084ba5ec780626cbd1efd23"
2674 | },
2675 | "dist": {
2676 | "type": "zip",
2677 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8474e22d7d642f665084ba5ec780626cbd1efd23",
2678 | "reference": "8474e22d7d642f665084ba5ec780626cbd1efd23",
2679 | "shasum": ""
2680 | },
2681 | "require": {
2682 | "doctrine/instantiator": "^1.2.0",
2683 | "ext-dom": "*",
2684 | "ext-json": "*",
2685 | "ext-libxml": "*",
2686 | "ext-mbstring": "*",
2687 | "ext-xml": "*",
2688 | "ext-xmlwriter": "*",
2689 | "myclabs/deep-copy": "^1.9.1",
2690 | "phar-io/manifest": "^1.0.3",
2691 | "phar-io/version": "^2.0.1",
2692 | "php": "^7.2",
2693 | "phpspec/prophecy": "^1.8.1",
2694 | "phpunit/php-code-coverage": "^7.0.7",
2695 | "phpunit/php-file-iterator": "^2.0.2",
2696 | "phpunit/php-text-template": "^1.2.1",
2697 | "phpunit/php-timer": "^2.1.2",
2698 | "sebastian/comparator": "^3.0.2",
2699 | "sebastian/diff": "^3.0.2",
2700 | "sebastian/environment": "^4.2.2",
2701 | "sebastian/exporter": "^3.1.1",
2702 | "sebastian/global-state": "^3.0.0",
2703 | "sebastian/object-enumerator": "^3.0.3",
2704 | "sebastian/resource-operations": "^2.0.1",
2705 | "sebastian/type": "^1.1.3",
2706 | "sebastian/version": "^2.0.1"
2707 | },
2708 | "require-dev": {
2709 | "ext-pdo": "*"
2710 | },
2711 | "suggest": {
2712 | "ext-soap": "*",
2713 | "ext-xdebug": "*",
2714 | "phpunit/php-invoker": "^2.0.0"
2715 | },
2716 | "bin": [
2717 | "phpunit"
2718 | ],
2719 | "type": "library",
2720 | "extra": {
2721 | "branch-alias": {
2722 | "dev-master": "8.5-dev"
2723 | }
2724 | },
2725 | "autoload": {
2726 | "classmap": [
2727 | "src/"
2728 | ]
2729 | },
2730 | "notification-url": "https://packagist.org/downloads/",
2731 | "license": [
2732 | "BSD-3-Clause"
2733 | ],
2734 | "authors": [
2735 | {
2736 | "name": "Sebastian Bergmann",
2737 | "email": "sebastian@phpunit.de",
2738 | "role": "lead"
2739 | }
2740 | ],
2741 | "description": "The PHP Unit Testing framework.",
2742 | "homepage": "https://phpunit.de/",
2743 | "keywords": [
2744 | "phpunit",
2745 | "testing",
2746 | "xunit"
2747 | ],
2748 | "time": "2020-04-23T04:39:42+00:00"
2749 | },
2750 | {
2751 | "name": "sebastian/code-unit-reverse-lookup",
2752 | "version": "1.0.1",
2753 | "source": {
2754 | "type": "git",
2755 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
2756 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
2757 | },
2758 | "dist": {
2759 | "type": "zip",
2760 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
2761 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
2762 | "shasum": ""
2763 | },
2764 | "require": {
2765 | "php": "^5.6 || ^7.0"
2766 | },
2767 | "require-dev": {
2768 | "phpunit/phpunit": "^5.7 || ^6.0"
2769 | },
2770 | "type": "library",
2771 | "extra": {
2772 | "branch-alias": {
2773 | "dev-master": "1.0.x-dev"
2774 | }
2775 | },
2776 | "autoload": {
2777 | "classmap": [
2778 | "src/"
2779 | ]
2780 | },
2781 | "notification-url": "https://packagist.org/downloads/",
2782 | "license": [
2783 | "BSD-3-Clause"
2784 | ],
2785 | "authors": [
2786 | {
2787 | "name": "Sebastian Bergmann",
2788 | "email": "sebastian@phpunit.de"
2789 | }
2790 | ],
2791 | "description": "Looks up which function or method a line of code belongs to",
2792 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
2793 | "time": "2017-03-04T06:30:41+00:00"
2794 | },
2795 | {
2796 | "name": "sebastian/comparator",
2797 | "version": "3.0.2",
2798 | "source": {
2799 | "type": "git",
2800 | "url": "https://github.com/sebastianbergmann/comparator.git",
2801 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da"
2802 | },
2803 | "dist": {
2804 | "type": "zip",
2805 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
2806 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
2807 | "shasum": ""
2808 | },
2809 | "require": {
2810 | "php": "^7.1",
2811 | "sebastian/diff": "^3.0",
2812 | "sebastian/exporter": "^3.1"
2813 | },
2814 | "require-dev": {
2815 | "phpunit/phpunit": "^7.1"
2816 | },
2817 | "type": "library",
2818 | "extra": {
2819 | "branch-alias": {
2820 | "dev-master": "3.0-dev"
2821 | }
2822 | },
2823 | "autoload": {
2824 | "classmap": [
2825 | "src/"
2826 | ]
2827 | },
2828 | "notification-url": "https://packagist.org/downloads/",
2829 | "license": [
2830 | "BSD-3-Clause"
2831 | ],
2832 | "authors": [
2833 | {
2834 | "name": "Jeff Welch",
2835 | "email": "whatthejeff@gmail.com"
2836 | },
2837 | {
2838 | "name": "Volker Dusch",
2839 | "email": "github@wallbash.com"
2840 | },
2841 | {
2842 | "name": "Bernhard Schussek",
2843 | "email": "bschussek@2bepublished.at"
2844 | },
2845 | {
2846 | "name": "Sebastian Bergmann",
2847 | "email": "sebastian@phpunit.de"
2848 | }
2849 | ],
2850 | "description": "Provides the functionality to compare PHP values for equality",
2851 | "homepage": "https://github.com/sebastianbergmann/comparator",
2852 | "keywords": [
2853 | "comparator",
2854 | "compare",
2855 | "equality"
2856 | ],
2857 | "time": "2018-07-12T15:12:46+00:00"
2858 | },
2859 | {
2860 | "name": "sebastian/diff",
2861 | "version": "3.0.2",
2862 | "source": {
2863 | "type": "git",
2864 | "url": "https://github.com/sebastianbergmann/diff.git",
2865 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29"
2866 | },
2867 | "dist": {
2868 | "type": "zip",
2869 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
2870 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
2871 | "shasum": ""
2872 | },
2873 | "require": {
2874 | "php": "^7.1"
2875 | },
2876 | "require-dev": {
2877 | "phpunit/phpunit": "^7.5 || ^8.0",
2878 | "symfony/process": "^2 || ^3.3 || ^4"
2879 | },
2880 | "type": "library",
2881 | "extra": {
2882 | "branch-alias": {
2883 | "dev-master": "3.0-dev"
2884 | }
2885 | },
2886 | "autoload": {
2887 | "classmap": [
2888 | "src/"
2889 | ]
2890 | },
2891 | "notification-url": "https://packagist.org/downloads/",
2892 | "license": [
2893 | "BSD-3-Clause"
2894 | ],
2895 | "authors": [
2896 | {
2897 | "name": "Kore Nordmann",
2898 | "email": "mail@kore-nordmann.de"
2899 | },
2900 | {
2901 | "name": "Sebastian Bergmann",
2902 | "email": "sebastian@phpunit.de"
2903 | }
2904 | ],
2905 | "description": "Diff implementation",
2906 | "homepage": "https://github.com/sebastianbergmann/diff",
2907 | "keywords": [
2908 | "diff",
2909 | "udiff",
2910 | "unidiff",
2911 | "unified diff"
2912 | ],
2913 | "time": "2019-02-04T06:01:07+00:00"
2914 | },
2915 | {
2916 | "name": "sebastian/environment",
2917 | "version": "4.2.3",
2918 | "source": {
2919 | "type": "git",
2920 | "url": "https://github.com/sebastianbergmann/environment.git",
2921 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368"
2922 | },
2923 | "dist": {
2924 | "type": "zip",
2925 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
2926 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
2927 | "shasum": ""
2928 | },
2929 | "require": {
2930 | "php": "^7.1"
2931 | },
2932 | "require-dev": {
2933 | "phpunit/phpunit": "^7.5"
2934 | },
2935 | "suggest": {
2936 | "ext-posix": "*"
2937 | },
2938 | "type": "library",
2939 | "extra": {
2940 | "branch-alias": {
2941 | "dev-master": "4.2-dev"
2942 | }
2943 | },
2944 | "autoload": {
2945 | "classmap": [
2946 | "src/"
2947 | ]
2948 | },
2949 | "notification-url": "https://packagist.org/downloads/",
2950 | "license": [
2951 | "BSD-3-Clause"
2952 | ],
2953 | "authors": [
2954 | {
2955 | "name": "Sebastian Bergmann",
2956 | "email": "sebastian@phpunit.de"
2957 | }
2958 | ],
2959 | "description": "Provides functionality to handle HHVM/PHP environments",
2960 | "homepage": "http://www.github.com/sebastianbergmann/environment",
2961 | "keywords": [
2962 | "Xdebug",
2963 | "environment",
2964 | "hhvm"
2965 | ],
2966 | "time": "2019-11-20T08:46:58+00:00"
2967 | },
2968 | {
2969 | "name": "sebastian/exporter",
2970 | "version": "3.1.2",
2971 | "source": {
2972 | "type": "git",
2973 | "url": "https://github.com/sebastianbergmann/exporter.git",
2974 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e"
2975 | },
2976 | "dist": {
2977 | "type": "zip",
2978 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e",
2979 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e",
2980 | "shasum": ""
2981 | },
2982 | "require": {
2983 | "php": "^7.0",
2984 | "sebastian/recursion-context": "^3.0"
2985 | },
2986 | "require-dev": {
2987 | "ext-mbstring": "*",
2988 | "phpunit/phpunit": "^6.0"
2989 | },
2990 | "type": "library",
2991 | "extra": {
2992 | "branch-alias": {
2993 | "dev-master": "3.1.x-dev"
2994 | }
2995 | },
2996 | "autoload": {
2997 | "classmap": [
2998 | "src/"
2999 | ]
3000 | },
3001 | "notification-url": "https://packagist.org/downloads/",
3002 | "license": [
3003 | "BSD-3-Clause"
3004 | ],
3005 | "authors": [
3006 | {
3007 | "name": "Sebastian Bergmann",
3008 | "email": "sebastian@phpunit.de"
3009 | },
3010 | {
3011 | "name": "Jeff Welch",
3012 | "email": "whatthejeff@gmail.com"
3013 | },
3014 | {
3015 | "name": "Volker Dusch",
3016 | "email": "github@wallbash.com"
3017 | },
3018 | {
3019 | "name": "Adam Harvey",
3020 | "email": "aharvey@php.net"
3021 | },
3022 | {
3023 | "name": "Bernhard Schussek",
3024 | "email": "bschussek@gmail.com"
3025 | }
3026 | ],
3027 | "description": "Provides the functionality to export PHP variables for visualization",
3028 | "homepage": "http://www.github.com/sebastianbergmann/exporter",
3029 | "keywords": [
3030 | "export",
3031 | "exporter"
3032 | ],
3033 | "time": "2019-09-14T09:02:43+00:00"
3034 | },
3035 | {
3036 | "name": "sebastian/global-state",
3037 | "version": "3.0.0",
3038 | "source": {
3039 | "type": "git",
3040 | "url": "https://github.com/sebastianbergmann/global-state.git",
3041 | "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4"
3042 | },
3043 | "dist": {
3044 | "type": "zip",
3045 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4",
3046 | "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4",
3047 | "shasum": ""
3048 | },
3049 | "require": {
3050 | "php": "^7.2",
3051 | "sebastian/object-reflector": "^1.1.1",
3052 | "sebastian/recursion-context": "^3.0"
3053 | },
3054 | "require-dev": {
3055 | "ext-dom": "*",
3056 | "phpunit/phpunit": "^8.0"
3057 | },
3058 | "suggest": {
3059 | "ext-uopz": "*"
3060 | },
3061 | "type": "library",
3062 | "extra": {
3063 | "branch-alias": {
3064 | "dev-master": "3.0-dev"
3065 | }
3066 | },
3067 | "autoload": {
3068 | "classmap": [
3069 | "src/"
3070 | ]
3071 | },
3072 | "notification-url": "https://packagist.org/downloads/",
3073 | "license": [
3074 | "BSD-3-Clause"
3075 | ],
3076 | "authors": [
3077 | {
3078 | "name": "Sebastian Bergmann",
3079 | "email": "sebastian@phpunit.de"
3080 | }
3081 | ],
3082 | "description": "Snapshotting of global state",
3083 | "homepage": "http://www.github.com/sebastianbergmann/global-state",
3084 | "keywords": [
3085 | "global state"
3086 | ],
3087 | "time": "2019-02-01T05:30:01+00:00"
3088 | },
3089 | {
3090 | "name": "sebastian/object-enumerator",
3091 | "version": "3.0.3",
3092 | "source": {
3093 | "type": "git",
3094 | "url": "https://github.com/sebastianbergmann/object-enumerator.git",
3095 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
3096 | },
3097 | "dist": {
3098 | "type": "zip",
3099 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
3100 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
3101 | "shasum": ""
3102 | },
3103 | "require": {
3104 | "php": "^7.0",
3105 | "sebastian/object-reflector": "^1.1.1",
3106 | "sebastian/recursion-context": "^3.0"
3107 | },
3108 | "require-dev": {
3109 | "phpunit/phpunit": "^6.0"
3110 | },
3111 | "type": "library",
3112 | "extra": {
3113 | "branch-alias": {
3114 | "dev-master": "3.0.x-dev"
3115 | }
3116 | },
3117 | "autoload": {
3118 | "classmap": [
3119 | "src/"
3120 | ]
3121 | },
3122 | "notification-url": "https://packagist.org/downloads/",
3123 | "license": [
3124 | "BSD-3-Clause"
3125 | ],
3126 | "authors": [
3127 | {
3128 | "name": "Sebastian Bergmann",
3129 | "email": "sebastian@phpunit.de"
3130 | }
3131 | ],
3132 | "description": "Traverses array structures and object graphs to enumerate all referenced objects",
3133 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
3134 | "time": "2017-08-03T12:35:26+00:00"
3135 | },
3136 | {
3137 | "name": "sebastian/object-reflector",
3138 | "version": "1.1.1",
3139 | "source": {
3140 | "type": "git",
3141 | "url": "https://github.com/sebastianbergmann/object-reflector.git",
3142 | "reference": "773f97c67f28de00d397be301821b06708fca0be"
3143 | },
3144 | "dist": {
3145 | "type": "zip",
3146 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
3147 | "reference": "773f97c67f28de00d397be301821b06708fca0be",
3148 | "shasum": ""
3149 | },
3150 | "require": {
3151 | "php": "^7.0"
3152 | },
3153 | "require-dev": {
3154 | "phpunit/phpunit": "^6.0"
3155 | },
3156 | "type": "library",
3157 | "extra": {
3158 | "branch-alias": {
3159 | "dev-master": "1.1-dev"
3160 | }
3161 | },
3162 | "autoload": {
3163 | "classmap": [
3164 | "src/"
3165 | ]
3166 | },
3167 | "notification-url": "https://packagist.org/downloads/",
3168 | "license": [
3169 | "BSD-3-Clause"
3170 | ],
3171 | "authors": [
3172 | {
3173 | "name": "Sebastian Bergmann",
3174 | "email": "sebastian@phpunit.de"
3175 | }
3176 | ],
3177 | "description": "Allows reflection of object attributes, including inherited and non-public ones",
3178 | "homepage": "https://github.com/sebastianbergmann/object-reflector/",
3179 | "time": "2017-03-29T09:07:27+00:00"
3180 | },
3181 | {
3182 | "name": "sebastian/recursion-context",
3183 | "version": "3.0.0",
3184 | "source": {
3185 | "type": "git",
3186 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
3187 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
3188 | },
3189 | "dist": {
3190 | "type": "zip",
3191 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
3192 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
3193 | "shasum": ""
3194 | },
3195 | "require": {
3196 | "php": "^7.0"
3197 | },
3198 | "require-dev": {
3199 | "phpunit/phpunit": "^6.0"
3200 | },
3201 | "type": "library",
3202 | "extra": {
3203 | "branch-alias": {
3204 | "dev-master": "3.0.x-dev"
3205 | }
3206 | },
3207 | "autoload": {
3208 | "classmap": [
3209 | "src/"
3210 | ]
3211 | },
3212 | "notification-url": "https://packagist.org/downloads/",
3213 | "license": [
3214 | "BSD-3-Clause"
3215 | ],
3216 | "authors": [
3217 | {
3218 | "name": "Jeff Welch",
3219 | "email": "whatthejeff@gmail.com"
3220 | },
3221 | {
3222 | "name": "Sebastian Bergmann",
3223 | "email": "sebastian@phpunit.de"
3224 | },
3225 | {
3226 | "name": "Adam Harvey",
3227 | "email": "aharvey@php.net"
3228 | }
3229 | ],
3230 | "description": "Provides functionality to recursively process PHP variables",
3231 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
3232 | "time": "2017-03-03T06:23:57+00:00"
3233 | },
3234 | {
3235 | "name": "sebastian/resource-operations",
3236 | "version": "2.0.1",
3237 | "source": {
3238 | "type": "git",
3239 | "url": "https://github.com/sebastianbergmann/resource-operations.git",
3240 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9"
3241 | },
3242 | "dist": {
3243 | "type": "zip",
3244 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
3245 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
3246 | "shasum": ""
3247 | },
3248 | "require": {
3249 | "php": "^7.1"
3250 | },
3251 | "type": "library",
3252 | "extra": {
3253 | "branch-alias": {
3254 | "dev-master": "2.0-dev"
3255 | }
3256 | },
3257 | "autoload": {
3258 | "classmap": [
3259 | "src/"
3260 | ]
3261 | },
3262 | "notification-url": "https://packagist.org/downloads/",
3263 | "license": [
3264 | "BSD-3-Clause"
3265 | ],
3266 | "authors": [
3267 | {
3268 | "name": "Sebastian Bergmann",
3269 | "email": "sebastian@phpunit.de"
3270 | }
3271 | ],
3272 | "description": "Provides a list of PHP built-in functions that operate on resources",
3273 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
3274 | "time": "2018-10-04T04:07:39+00:00"
3275 | },
3276 | {
3277 | "name": "sebastian/type",
3278 | "version": "1.1.3",
3279 | "source": {
3280 | "type": "git",
3281 | "url": "https://github.com/sebastianbergmann/type.git",
3282 | "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3"
3283 | },
3284 | "dist": {
3285 | "type": "zip",
3286 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3",
3287 | "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3",
3288 | "shasum": ""
3289 | },
3290 | "require": {
3291 | "php": "^7.2"
3292 | },
3293 | "require-dev": {
3294 | "phpunit/phpunit": "^8.2"
3295 | },
3296 | "type": "library",
3297 | "extra": {
3298 | "branch-alias": {
3299 | "dev-master": "1.1-dev"
3300 | }
3301 | },
3302 | "autoload": {
3303 | "classmap": [
3304 | "src/"
3305 | ]
3306 | },
3307 | "notification-url": "https://packagist.org/downloads/",
3308 | "license": [
3309 | "BSD-3-Clause"
3310 | ],
3311 | "authors": [
3312 | {
3313 | "name": "Sebastian Bergmann",
3314 | "email": "sebastian@phpunit.de",
3315 | "role": "lead"
3316 | }
3317 | ],
3318 | "description": "Collection of value objects that represent the types of the PHP type system",
3319 | "homepage": "https://github.com/sebastianbergmann/type",
3320 | "time": "2019-07-02T08:10:15+00:00"
3321 | },
3322 | {
3323 | "name": "sebastian/version",
3324 | "version": "2.0.1",
3325 | "source": {
3326 | "type": "git",
3327 | "url": "https://github.com/sebastianbergmann/version.git",
3328 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
3329 | },
3330 | "dist": {
3331 | "type": "zip",
3332 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
3333 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
3334 | "shasum": ""
3335 | },
3336 | "require": {
3337 | "php": ">=5.6"
3338 | },
3339 | "type": "library",
3340 | "extra": {
3341 | "branch-alias": {
3342 | "dev-master": "2.0.x-dev"
3343 | }
3344 | },
3345 | "autoload": {
3346 | "classmap": [
3347 | "src/"
3348 | ]
3349 | },
3350 | "notification-url": "https://packagist.org/downloads/",
3351 | "license": [
3352 | "BSD-3-Clause"
3353 | ],
3354 | "authors": [
3355 | {
3356 | "name": "Sebastian Bergmann",
3357 | "email": "sebastian@phpunit.de",
3358 | "role": "lead"
3359 | }
3360 | ],
3361 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
3362 | "homepage": "https://github.com/sebastianbergmann/version",
3363 | "time": "2016-10-03T07:35:21+00:00"
3364 | },
3365 | {
3366 | "name": "theseer/tokenizer",
3367 | "version": "1.1.3",
3368 | "source": {
3369 | "type": "git",
3370 | "url": "https://github.com/theseer/tokenizer.git",
3371 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9"
3372 | },
3373 | "dist": {
3374 | "type": "zip",
3375 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
3376 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
3377 | "shasum": ""
3378 | },
3379 | "require": {
3380 | "ext-dom": "*",
3381 | "ext-tokenizer": "*",
3382 | "ext-xmlwriter": "*",
3383 | "php": "^7.0"
3384 | },
3385 | "type": "library",
3386 | "autoload": {
3387 | "classmap": [
3388 | "src/"
3389 | ]
3390 | },
3391 | "notification-url": "https://packagist.org/downloads/",
3392 | "license": [
3393 | "BSD-3-Clause"
3394 | ],
3395 | "authors": [
3396 | {
3397 | "name": "Arne Blankerts",
3398 | "email": "arne@blankerts.de",
3399 | "role": "Developer"
3400 | }
3401 | ],
3402 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
3403 | "time": "2019-06-13T22:48:21+00:00"
3404 | },
3405 | {
3406 | "name": "webmozart/assert",
3407 | "version": "1.8.0",
3408 | "source": {
3409 | "type": "git",
3410 | "url": "https://github.com/webmozart/assert.git",
3411 | "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6"
3412 | },
3413 | "dist": {
3414 | "type": "zip",
3415 | "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
3416 | "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
3417 | "shasum": ""
3418 | },
3419 | "require": {
3420 | "php": "^5.3.3 || ^7.0",
3421 | "symfony/polyfill-ctype": "^1.8"
3422 | },
3423 | "conflict": {
3424 | "vimeo/psalm": "<3.9.1"
3425 | },
3426 | "require-dev": {
3427 | "phpunit/phpunit": "^4.8.36 || ^7.5.13"
3428 | },
3429 | "type": "library",
3430 | "autoload": {
3431 | "psr-4": {
3432 | "Webmozart\\Assert\\": "src/"
3433 | }
3434 | },
3435 | "notification-url": "https://packagist.org/downloads/",
3436 | "license": [
3437 | "MIT"
3438 | ],
3439 | "authors": [
3440 | {
3441 | "name": "Bernhard Schussek",
3442 | "email": "bschussek@gmail.com"
3443 | }
3444 | ],
3445 | "description": "Assertions to validate method input/output with nice error messages.",
3446 | "keywords": [
3447 | "assert",
3448 | "check",
3449 | "validate"
3450 | ],
3451 | "time": "2020-04-18T12:12:48+00:00"
3452 | }
3453 | ],
3454 | "aliases": [],
3455 | "minimum-stability": "stable",
3456 | "stability-flags": [],
3457 | "prefer-stable": false,
3458 | "prefer-lowest": false,
3459 | "platform": {
3460 | "php": "^7.4",
3461 | "ext-json": "*",
3462 | "ext-pdo": "*",
3463 | "ext-pdo_pgsql": "*"
3464 | },
3465 | "platform-dev": [],
3466 | "plugin-api-version": "1.1.0"
3467 | }
3468 |
--------------------------------------------------------------------------------