├── var
└── cache
│ └── .gitignore
├── .gitignore
├── .env
├── Readme.md
├── .php_cs
├── src
├── Controller
│ ├── CandidateReturn.php
│ ├── AuthenticationStep1.php
│ ├── GetFindTypes.php
│ ├── GetTestTypes.php
│ ├── DownloadRoleCategory.php
│ ├── CreateFind.php
│ ├── AuthenticationStep2.php
│ ├── CreateMatch.php
│ ├── AuthenticationRefresh.php
│ ├── CreateTest.php
│ ├── Dashboard.php
│ └── CreateRole.php
└── Service
│ ├── AccessTokenManager.php
│ └── Database.php
├── composer.json
├── public
└── index.php
└── composer.lock
/var/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | .php_cs.cache
3 | .env.local
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | MATCH_BASE_URL=https://api-happyrmatch.demo.happyr-dev.com
2 | MATCH_CLIENT_IDENTIFIER=foo
3 | MATCH_CLIENT_SECRET=bar
4 |
5 | # The redirect_uri must match the URI configured in the API dashboard.
6 | MATCH_AUTH_REDIRECT_URI=http://127.0.0.1:8000/step-2
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Demo implementation of Happyr Match
2 |
3 |
4 | ## Try it out
5 |
6 | Clone the project
7 |
8 | ```
9 | composer install
10 |
11 | # Use Symfony binary (it is super nice)
12 | symfony serve
13 |
14 | # Or built in php server
15 | php -S 127.0.0.1:8000 -t public
16 | ```
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | in(__DIR__.'/src')
5 | ->in(__DIR__.'/public')
6 | ;
7 |
8 | return PhpCsFixer\Config::create()
9 | ->setRiskyAllowed(true)
10 | ->setRules([
11 | '@Symfony' => true,
12 | 'declare_strict_types' => true,
13 | 'date_time_immutable' => true,
14 | ])
15 | ->setFinder($finder)
16 | ;
--------------------------------------------------------------------------------
/src/Controller/CandidateReturn.php:
--------------------------------------------------------------------------------
1 | Back to Dashboard';
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "happyr/match-demo",
3 | "type": "project",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Nyholm",
8 | "email": "tobias.nyholm@gmail.com"
9 | }
10 | ],
11 | "require": {
12 | "php": ">=7.2",
13 | "symfony/http-client": "^4.3",
14 | "symfony/dotenv": "^4.3",
15 | "nyholm/psr7": "^1.3"
16 | },
17 | "autoload": {
18 | "psr-4": {
19 | "App\\": "src/"
20 | }
21 | },
22 | "config": {
23 | "platform": {
24 | "php": "7.4.12"
25 | },
26 | "preferred-install": {
27 | "*": "dist"
28 | },
29 | "sort-packages": true
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Service/AccessTokenManager.php:
--------------------------------------------------------------------------------
1 | Match demo
18 |
The HTML is very limited and the application is poor. We are using minimal code just to demo stuff.
19 | HTML;
20 |
21 | $authUrl = getenv('MATCH_BASE_URL').'/oauth/authorize?'.http_build_query([
22 | 'response_type' => 'code',
23 | 'client_id' => getenv('MATCH_CLIENT_IDENTIFIER'),
24 | 'redirect_uri' => getenv('MATCH_AUTH_REDIRECT_URI'),
25 | 'scope' => 'find add_candidate test match learn',
26 | ]);
27 |
28 | echo 'Authenticate
';
29 |
30 | // TODO if we got an access token already, we can skip authentication
31 | if (AccessTokenManager::hasToken()) {
32 | echo 'You already have a token stored. It might still be valid, do you want to try?
';
33 | echo 'Continue with existing token';
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Controller/GetFindTypes.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
16 | 'auth_bearer' => AccessTokenManager::fetch()['access_token'] ?? '',
17 | 'headers' => [
18 | 'Accept' => 'application/vnd.api+json',
19 | 'Content-Type' => 'application/vnd.api+json',
20 | ],
21 | ]);
22 |
23 | $response = $httpClient->request('GET', '/api/find-types');
24 |
25 | if (200 !== $response->getStatusCode()) {
26 | echo 'Error when getting find types:';
27 | echo '
'.$response->getContent(false).'
';
28 |
29 | echo 'Back to Startpage';
30 |
31 | return;
32 | }
33 |
34 | echo 'Got find types
';
35 |
36 | $data = $response->toArray()['data'];
37 | echo ''.json_encode($data, JSON_PRETTY_PRINT).'
';
38 |
39 | echo '
Back to Dashboard';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Controller/GetTestTypes.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
16 | 'auth_bearer' => AccessTokenManager::fetch()['access_token'] ?? '',
17 | 'headers' => [
18 | 'Accept' => 'application/vnd.api+json',
19 | 'Content-Type' => 'application/vnd.api+json',
20 | ],
21 | ]);
22 |
23 | $response = $httpClient->request('GET', '/api/test-types');
24 |
25 | if (200 !== $response->getStatusCode()) {
26 | echo 'Error when getting test types:';
27 | echo '
'.$response->getContent(false).'
';
28 |
29 | echo 'Back to Startpage';
30 |
31 | return;
32 | }
33 |
34 | echo 'Got test types
';
35 |
36 | $data = $response->toArray()['data'];
37 | echo ''.json_encode($data, JSON_PRETTY_PRINT).'
';
38 |
39 | echo '
Back to Dashboard';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Controller/DownloadRoleCategory.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
16 | 'headers' => [
17 | 'Accept' => 'application/vnd.api+json',
18 | ],
19 | ]);
20 |
21 | $response = $httpClient->request('GET', '/api/roles/all-categories', [
22 | 'auth_bearer' => AccessTokenManager::fetch()['access_token'] ?? '',
23 | 'headers' => [
24 | 'Content-Type' => 'application/vnd.api+json',
25 | ],
26 | ]);
27 |
28 | if (200 !== $response->getStatusCode()) {
29 | echo 'Error when creating role:';
30 | echo '
'.$response->getContent(false).'
';
31 |
32 | echo 'Back to Startpage';
33 |
34 | return;
35 | }
36 |
37 | echo 'Back to Dashboard
';
38 | echo 'Here they are:
';
39 |
40 | $data = $response->toArray()['data'];
41 | echo ''.json_encode($data, JSON_PRETTY_PRINT).'
';
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Controller/CreateFind.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
17 | 'auth_bearer' => AccessTokenManager::fetch()['access_token'] ?? '',
18 | 'headers' => [
19 | 'Accept' => 'application/vnd.api+json',
20 | 'Content-Type' => 'application/vnd.api+json',
21 | ],
22 | ]);
23 |
24 | $response = $httpClient->request('POST', '/api/find', [
25 | 'json' => [
26 | 'role' => Database::findRole(),
27 | 'type' => '585e6742-c187-4ee3-adea-947cae9e0acf',
28 | 'callback_url' => 'https://example.com/where-to-send-candidates',
29 | ],
30 | ]);
31 |
32 | if (202 !== $response->getStatusCode()) {
33 | echo 'Error when creating Find:';
34 | echo '
'.$response->getContent(false).'
';
35 |
36 | echo 'Back to Startpage';
37 |
38 | return;
39 | }
40 |
41 | echo 'Find request was accepted
';
42 | echo 'Back to Dashboard';
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Controller/AuthenticationStep2.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
21 | ]);
22 |
23 | $response = $httpClient->request('POST', '/oauth/token', [
24 | 'body' => http_build_query([
25 | 'code' => $query['code'],
26 | 'grant_type' => 'authorization_code',
27 | 'client_id' => getenv('MATCH_CLIENT_IDENTIFIER'),
28 | 'client_secret' => getenv('MATCH_CLIENT_SECRET'),
29 | 'redirect_uri' => getenv('MATCH_AUTH_REDIRECT_URI'),
30 | ]),
31 | ]);
32 |
33 | if (200 !== $response->getStatusCode()) {
34 | echo 'Error when getting access code:';
35 | echo '
'.$response->getContent(false).'
';
36 |
37 | echo 'Back to Startpage';
38 |
39 | return;
40 | }
41 |
42 | AccessTokenManager::store($response->toArray());
43 |
44 | echo 'We saved the access token to disk. Go to next step
';
45 | echo 'Continue';
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Controller/CreateMatch.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
17 | 'auth_bearer' => AccessTokenManager::fetch()['access_token'] ?? '',
18 | 'headers' => [
19 | 'Accept' => 'application/vnd.api+json',
20 | 'Content-Type' => 'application/vnd.api+json',
21 | ],
22 | ]);
23 |
24 | $candidateId = Database::findCandidate();
25 | $role = Database::findRole();
26 |
27 | if (empty($candidateId)) {
28 | echo 'Cannot get match without a candidate';
29 |
30 | return;
31 | }
32 |
33 | if (empty($role)) {
34 | echo 'Cannot get match without a role';
35 |
36 | return;
37 | }
38 |
39 | $response = $httpClient->request('GET', '/api/candidates/'.$candidateId.'/match?type=medium&role='.$role);
40 |
41 | if (200 !== $response->getStatusCode()) {
42 | echo 'Error when getting match:';
43 | echo '
'.$response->getContent(false).'
';
44 |
45 | echo 'Back to Startpage';
46 |
47 | return;
48 | }
49 |
50 | Database::storeMatch($response->toArray());
51 |
52 | echo 'We got a match
';
53 | echo 'Back to Dashboard';
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Controller/AuthenticationRefresh.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
21 | 'auth_bearer' => $accessToken['access_token'] ?? '',
22 | 'headers' => [
23 | 'Content-Type' => 'application/x-www-form-urlencoded',
24 | ],
25 | ]);
26 |
27 | $response = $httpClient->request('POST', '/oauth/token', [
28 | 'body' => http_build_query([
29 | 'refresh_token' => $accessToken['refresh_token'] ?? '',
30 | 'grant_type' => 'refresh_token',
31 | 'client_id' => getenv('MATCH_CLIENT_IDENTIFIER'),
32 | 'client_secret' => getenv('MATCH_CLIENT_SECRET'),
33 | ]),
34 | ]);
35 |
36 | if (200 !== $response->getStatusCode()) {
37 | echo 'Error when the new getting access code:';
38 | echo '
'.$response->getContent(false).'
';
39 |
40 | echo 'Back to Startpage';
41 |
42 | return;
43 | }
44 |
45 | AccessTokenManager::store($response->toArray());
46 |
47 | echo 'We saved the new access token to disk. Go to next step
';
48 | echo 'Continue';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | loadEnv(dirname(__DIR__).'/.env');
10 |
11 | $url = $_SERVER['REQUEST_URI'];
12 | $urlPath = parse_url($url, PHP_URL_PATH);
13 |
14 | // A very simple router
15 | switch ($urlPath) {
16 | case '/':
17 | case '/step-1':
18 | (new \App\Controller\AuthenticationStep1())->run($url);
19 | break;
20 | case '/step-2':
21 | (new \App\Controller\AuthenticationStep2())->run($url);
22 | break;
23 | case '/refresh':
24 | (new \App\Controller\AuthenticationRefresh())->run($url);
25 | break;
26 | case '/dashboard':
27 | (new \App\Controller\Dashboard())->run($url);
28 | break;
29 | case '/candidate-return':
30 | (new \App\Controller\CandidateReturn())->run($url);
31 | break;
32 | case '/create-role':
33 | (new \App\Controller\CreateRole())->run($url);
34 | break;
35 | case '/download-role-category':
36 | (new \App\Controller\DownloadRoleCategory())->run($url);
37 | break;
38 | case '/create-test':
39 | (new \App\Controller\CreateTest())->run($url);
40 | break;
41 | case '/get-match':
42 | (new \App\Controller\CreateMatch())->run($url);
43 | break;
44 | case '/find':
45 | (new \App\Controller\CreateFind())->run($url);
46 | break;
47 | case '/find-types':
48 | (new \App\Controller\GetFindTypes())->run($url);
49 | break;
50 | case '/test-types':
51 | (new \App\Controller\GetTestTypes())->run($url);
52 | break;
53 | default:
54 | echo 'Page not found';
55 | }
56 |
--------------------------------------------------------------------------------
/src/Controller/CreateTest.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
17 | 'auth_bearer' => AccessTokenManager::fetch()['access_token'] ?? '',
18 | 'headers' => [
19 | 'Accept' => 'application/vnd.api+json',
20 | 'Content-Type' => 'application/vnd.api+json',
21 | ],
22 | ]);
23 |
24 | $candidateRedirectUri = 'http://'.$_SERVER['HTTP_HOST'].'/candidate-return';
25 | $response = $httpClient->request('POST', '/api/tests', [
26 | 'json' => [
27 | 'role' => Database::findRole(),
28 | 'types' => ['ca2cfc8b-f2f9-4d5b-a293-925622f63ebb'],
29 | 'redirect_uri' => $candidateRedirectUri,
30 | ],
31 | ]);
32 |
33 | if (201 !== $response->getStatusCode()) {
34 | echo 'Error when creating test:';
35 | echo '
'.$response->getContent(false).'
';
36 |
37 | echo 'Back to Startpage';
38 |
39 | return;
40 | }
41 |
42 | $data = $response->toArray();
43 |
44 | Database::storeTest([
45 | 'id' => $data['data']['id'],
46 | 'url' => $data['data']['attributes']['url'].'?redirect-uri='.urlencode($candidateRedirectUri),
47 | ]);
48 |
49 | echo 'Test is created
';
50 | echo 'Back to Dashboard';
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Controller/Dashboard.php:
--------------------------------------------------------------------------------
1 | Dashboard
24 | We assume that you have a valid access token. If you don't have that yet you will notice soon =)
25 |
26 | Role
27 | Current role id: {$role}
28 | Create new role
29 |
30 |
31 | Get all role categories
32 |
33 | Test
34 | Current test id: {$test['id']}
35 | Create new test
36 | HTML;
37 | if (!empty($test['url'])) {
38 | echo 'Send a candidate to this URL to ask them to "do the test":
'.$test['url'];
39 | }
40 |
41 | echo <<Candidate
43 | Current candidate id: {$candidate}
44 | Here is the match of that candidate:
45 | HTML;
46 |
47 | if (empty($match)) {
48 | echo 'Calculate match';
49 | } else {
50 | echo ''.json_encode($match, JSON_PRETTY_PRINT).'
';
51 | }
52 |
53 | echo <<Find request
55 | Send new Find request by clicking here.
56 |
57 | Get all find types
58 | See all type of Find requests by clicking here.
59 |
60 | Get all test types
61 | See all type of tests by clicking here.
62 |
63 |
64 | Refresh AccessToken
65 | If the token have expired, you may refresh it.
66 |
67 |
68 | Startpage
69 | Back to startpage
70 |
71 | HTML;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Service/Database.php:
--------------------------------------------------------------------------------
1 | null];
38 | }
39 |
40 | public static function storeTest(array $test): void
41 | {
42 | self::store('test', $test);
43 | }
44 |
45 | public static function findMatch(?string $candidateId): array
46 | {
47 | if (null === $candidateId) {
48 | return [];
49 | }
50 |
51 | return self::fetch()['match'] ?? [];
52 | }
53 |
54 | public static function storeMatch(array $match): void
55 | {
56 | self::store('match', $match);
57 | }
58 |
59 | private static function store(string $key, $value): void
60 | {
61 | $data = self::fetch();
62 | $data[$key] = $value;
63 | $file = self::getStoragePath();
64 | file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT));
65 | }
66 |
67 | private static function fetch(): array
68 | {
69 | $file = self::getStoragePath();
70 | if (!file_exists($file)) {
71 | return [];
72 | }
73 |
74 | $fileContent = file_get_contents($file);
75 |
76 | return json_decode($fileContent, true);
77 | }
78 |
79 | private static function getStoragePath()
80 | {
81 | return dirname(__DIR__).'/..'.self::STORAGE;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Controller/CreateRole.php:
--------------------------------------------------------------------------------
1 | getenv('MATCH_BASE_URL'),
17 | 'headers' => [
18 | 'Accept' => 'application/vnd.api+json',
19 | ],
20 | ]);
21 |
22 | // Get the role category
23 | $response = $httpClient->request('GET', 'https://api.happyrmatch.com/api/role-categories/search?language=sv&name=receptionist');
24 | $roleCategory = $response->toArray()['data'][0]['id'];
25 |
26 | $response = $httpClient->request('POST', '/api/roles', [
27 | 'auth_bearer' => AccessTokenManager::fetch()['access_token'] ?? '',
28 | 'headers' => [
29 | 'Content-Type' => 'application/vnd.api+json',
30 | ],
31 | 'json' => [
32 | 'advert_title' => 'Funny receptionist',
33 | 'advert_body_text' => 'Long text',
34 | 'advert_body_html' => 'Long text<\/b>',
35 | 'advert_link' => 'https://my-app.com/advert/123',
36 | 'description' => 'Short text',
37 | 'employment_duration' => 1,
38 | 'role_category' => $roleCategory,
39 | 'work_hours' => 1,
40 | 'location' => [
41 | 'country' => 'SE',
42 | 'region' => 'Stockholms län',
43 | 'city' => 'Stockholm',
44 | 'address' => 'Drottninggatan 7',
45 | ],
46 | ],
47 | ]);
48 |
49 | if (201 !== $response->getStatusCode()) {
50 | echo 'Error when creating role:';
51 | echo '
'.$response->getContent(false).'
';
52 |
53 | echo 'Back to Startpage';
54 |
55 | return;
56 | }
57 |
58 | Database::storeRole($response->toArray()['data']['id']);
59 |
60 | echo 'Role is created
';
61 | echo 'Back to Dashboard';
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/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": "84996011575ad0f3df25773cf6ebba3e",
8 | "packages": [
9 | {
10 | "name": "nyholm/psr7",
11 | "version": "1.4.1",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/Nyholm/psr7.git",
15 | "reference": "2212385b47153ea71b1c1b1374f8cb5e4f7892ec"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/Nyholm/psr7/zipball/2212385b47153ea71b1c1b1374f8cb5e4f7892ec",
20 | "reference": "2212385b47153ea71b1c1b1374f8cb5e4f7892ec",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "php": ">=7.1",
25 | "php-http/message-factory": "^1.0",
26 | "psr/http-factory": "^1.0",
27 | "psr/http-message": "^1.0"
28 | },
29 | "provide": {
30 | "psr/http-factory-implementation": "1.0",
31 | "psr/http-message-implementation": "1.0"
32 | },
33 | "require-dev": {
34 | "http-interop/http-factory-tests": "^0.9",
35 | "php-http/psr7-integration-tests": "^1.0",
36 | "phpunit/phpunit": "^7.5 || 8.5 || 9.4",
37 | "symfony/error-handler": "^4.4"
38 | },
39 | "type": "library",
40 | "extra": {
41 | "branch-alias": {
42 | "dev-master": "1.4-dev"
43 | }
44 | },
45 | "autoload": {
46 | "psr-4": {
47 | "Nyholm\\Psr7\\": "src/"
48 | }
49 | },
50 | "notification-url": "https://packagist.org/downloads/",
51 | "license": [
52 | "MIT"
53 | ],
54 | "authors": [
55 | {
56 | "name": "Tobias Nyholm",
57 | "email": "tobias.nyholm@gmail.com"
58 | },
59 | {
60 | "name": "Martijn van der Ven",
61 | "email": "martijn@vanderven.se"
62 | }
63 | ],
64 | "description": "A fast PHP7 implementation of PSR-7",
65 | "homepage": "https://tnyholm.se",
66 | "keywords": [
67 | "psr-17",
68 | "psr-7"
69 | ],
70 | "support": {
71 | "issues": "https://github.com/Nyholm/psr7/issues",
72 | "source": "https://github.com/Nyholm/psr7/tree/1.4.1"
73 | },
74 | "funding": [
75 | {
76 | "url": "https://github.com/Zegnat",
77 | "type": "github"
78 | },
79 | {
80 | "url": "https://github.com/nyholm",
81 | "type": "github"
82 | }
83 | ],
84 | "time": "2021-07-02T08:32:20+00:00"
85 | },
86 | {
87 | "name": "php-http/message-factory",
88 | "version": "v1.0.2",
89 | "source": {
90 | "type": "git",
91 | "url": "https://github.com/php-http/message-factory.git",
92 | "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1"
93 | },
94 | "dist": {
95 | "type": "zip",
96 | "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1",
97 | "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1",
98 | "shasum": ""
99 | },
100 | "require": {
101 | "php": ">=5.4",
102 | "psr/http-message": "^1.0"
103 | },
104 | "type": "library",
105 | "extra": {
106 | "branch-alias": {
107 | "dev-master": "1.0-dev"
108 | }
109 | },
110 | "autoload": {
111 | "psr-4": {
112 | "Http\\Message\\": "src/"
113 | }
114 | },
115 | "notification-url": "https://packagist.org/downloads/",
116 | "license": [
117 | "MIT"
118 | ],
119 | "authors": [
120 | {
121 | "name": "Márk Sági-Kazár",
122 | "email": "mark.sagikazar@gmail.com"
123 | }
124 | ],
125 | "description": "Factory interfaces for PSR-7 HTTP Message",
126 | "homepage": "http://php-http.org",
127 | "keywords": [
128 | "factory",
129 | "http",
130 | "message",
131 | "stream",
132 | "uri"
133 | ],
134 | "support": {
135 | "issues": "https://github.com/php-http/message-factory/issues",
136 | "source": "https://github.com/php-http/message-factory/tree/master"
137 | },
138 | "time": "2015-12-19T14:08:53+00:00"
139 | },
140 | {
141 | "name": "psr/container",
142 | "version": "1.1.1",
143 | "source": {
144 | "type": "git",
145 | "url": "https://github.com/php-fig/container.git",
146 | "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf"
147 | },
148 | "dist": {
149 | "type": "zip",
150 | "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf",
151 | "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf",
152 | "shasum": ""
153 | },
154 | "require": {
155 | "php": ">=7.2.0"
156 | },
157 | "type": "library",
158 | "autoload": {
159 | "psr-4": {
160 | "Psr\\Container\\": "src/"
161 | }
162 | },
163 | "notification-url": "https://packagist.org/downloads/",
164 | "license": [
165 | "MIT"
166 | ],
167 | "authors": [
168 | {
169 | "name": "PHP-FIG",
170 | "homepage": "https://www.php-fig.org/"
171 | }
172 | ],
173 | "description": "Common Container Interface (PHP FIG PSR-11)",
174 | "homepage": "https://github.com/php-fig/container",
175 | "keywords": [
176 | "PSR-11",
177 | "container",
178 | "container-interface",
179 | "container-interop",
180 | "psr"
181 | ],
182 | "support": {
183 | "issues": "https://github.com/php-fig/container/issues",
184 | "source": "https://github.com/php-fig/container/tree/1.1.1"
185 | },
186 | "time": "2021-03-05T17:36:06+00:00"
187 | },
188 | {
189 | "name": "psr/http-factory",
190 | "version": "1.0.1",
191 | "source": {
192 | "type": "git",
193 | "url": "https://github.com/php-fig/http-factory.git",
194 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
195 | },
196 | "dist": {
197 | "type": "zip",
198 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
199 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
200 | "shasum": ""
201 | },
202 | "require": {
203 | "php": ">=7.0.0",
204 | "psr/http-message": "^1.0"
205 | },
206 | "type": "library",
207 | "extra": {
208 | "branch-alias": {
209 | "dev-master": "1.0.x-dev"
210 | }
211 | },
212 | "autoload": {
213 | "psr-4": {
214 | "Psr\\Http\\Message\\": "src/"
215 | }
216 | },
217 | "notification-url": "https://packagist.org/downloads/",
218 | "license": [
219 | "MIT"
220 | ],
221 | "authors": [
222 | {
223 | "name": "PHP-FIG",
224 | "homepage": "http://www.php-fig.org/"
225 | }
226 | ],
227 | "description": "Common interfaces for PSR-7 HTTP message factories",
228 | "keywords": [
229 | "factory",
230 | "http",
231 | "message",
232 | "psr",
233 | "psr-17",
234 | "psr-7",
235 | "request",
236 | "response"
237 | ],
238 | "support": {
239 | "source": "https://github.com/php-fig/http-factory/tree/master"
240 | },
241 | "time": "2019-04-30T12:38:16+00:00"
242 | },
243 | {
244 | "name": "psr/http-message",
245 | "version": "1.0.1",
246 | "source": {
247 | "type": "git",
248 | "url": "https://github.com/php-fig/http-message.git",
249 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
250 | },
251 | "dist": {
252 | "type": "zip",
253 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
254 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
255 | "shasum": ""
256 | },
257 | "require": {
258 | "php": ">=5.3.0"
259 | },
260 | "type": "library",
261 | "extra": {
262 | "branch-alias": {
263 | "dev-master": "1.0.x-dev"
264 | }
265 | },
266 | "autoload": {
267 | "psr-4": {
268 | "Psr\\Http\\Message\\": "src/"
269 | }
270 | },
271 | "notification-url": "https://packagist.org/downloads/",
272 | "license": [
273 | "MIT"
274 | ],
275 | "authors": [
276 | {
277 | "name": "PHP-FIG",
278 | "homepage": "http://www.php-fig.org/"
279 | }
280 | ],
281 | "description": "Common interface for HTTP messages",
282 | "homepage": "https://github.com/php-fig/http-message",
283 | "keywords": [
284 | "http",
285 | "http-message",
286 | "psr",
287 | "psr-7",
288 | "request",
289 | "response"
290 | ],
291 | "support": {
292 | "source": "https://github.com/php-fig/http-message/tree/master"
293 | },
294 | "time": "2016-08-06T14:39:51+00:00"
295 | },
296 | {
297 | "name": "psr/log",
298 | "version": "1.1.4",
299 | "source": {
300 | "type": "git",
301 | "url": "https://github.com/php-fig/log.git",
302 | "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
303 | },
304 | "dist": {
305 | "type": "zip",
306 | "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
307 | "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
308 | "shasum": ""
309 | },
310 | "require": {
311 | "php": ">=5.3.0"
312 | },
313 | "type": "library",
314 | "extra": {
315 | "branch-alias": {
316 | "dev-master": "1.1.x-dev"
317 | }
318 | },
319 | "autoload": {
320 | "psr-4": {
321 | "Psr\\Log\\": "Psr/Log/"
322 | }
323 | },
324 | "notification-url": "https://packagist.org/downloads/",
325 | "license": [
326 | "MIT"
327 | ],
328 | "authors": [
329 | {
330 | "name": "PHP-FIG",
331 | "homepage": "https://www.php-fig.org/"
332 | }
333 | ],
334 | "description": "Common interface for logging libraries",
335 | "homepage": "https://github.com/php-fig/log",
336 | "keywords": [
337 | "log",
338 | "psr",
339 | "psr-3"
340 | ],
341 | "support": {
342 | "source": "https://github.com/php-fig/log/tree/1.1.4"
343 | },
344 | "time": "2021-05-03T11:20:27+00:00"
345 | },
346 | {
347 | "name": "symfony/dotenv",
348 | "version": "v4.4.29",
349 | "source": {
350 | "type": "git",
351 | "url": "https://github.com/symfony/dotenv.git",
352 | "reference": "2b078eff6268875f4639cf16e48e321982c671bb"
353 | },
354 | "dist": {
355 | "type": "zip",
356 | "url": "https://api.github.com/repos/symfony/dotenv/zipball/2b078eff6268875f4639cf16e48e321982c671bb",
357 | "reference": "2b078eff6268875f4639cf16e48e321982c671bb",
358 | "shasum": ""
359 | },
360 | "require": {
361 | "php": ">=7.1.3"
362 | },
363 | "require-dev": {
364 | "symfony/process": "^3.4.2|^4.0|^5.0"
365 | },
366 | "type": "library",
367 | "autoload": {
368 | "psr-4": {
369 | "Symfony\\Component\\Dotenv\\": ""
370 | },
371 | "exclude-from-classmap": [
372 | "/Tests/"
373 | ]
374 | },
375 | "notification-url": "https://packagist.org/downloads/",
376 | "license": [
377 | "MIT"
378 | ],
379 | "authors": [
380 | {
381 | "name": "Fabien Potencier",
382 | "email": "fabien@symfony.com"
383 | },
384 | {
385 | "name": "Symfony Community",
386 | "homepage": "https://symfony.com/contributors"
387 | }
388 | ],
389 | "description": "Registers environment variables from a .env file",
390 | "homepage": "https://symfony.com",
391 | "keywords": [
392 | "dotenv",
393 | "env",
394 | "environment"
395 | ],
396 | "support": {
397 | "source": "https://github.com/symfony/dotenv/tree/v4.4.29"
398 | },
399 | "funding": [
400 | {
401 | "url": "https://symfony.com/sponsor",
402 | "type": "custom"
403 | },
404 | {
405 | "url": "https://github.com/fabpot",
406 | "type": "github"
407 | },
408 | {
409 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
410 | "type": "tidelift"
411 | }
412 | ],
413 | "time": "2021-07-27T16:19:30+00:00"
414 | },
415 | {
416 | "name": "symfony/http-client",
417 | "version": "v4.4.31",
418 | "source": {
419 | "type": "git",
420 | "url": "https://github.com/symfony/http-client.git",
421 | "reference": "6b900ffa399e25203f30f79f6f4a56b89eee14c2"
422 | },
423 | "dist": {
424 | "type": "zip",
425 | "url": "https://api.github.com/repos/symfony/http-client/zipball/6b900ffa399e25203f30f79f6f4a56b89eee14c2",
426 | "reference": "6b900ffa399e25203f30f79f6f4a56b89eee14c2",
427 | "shasum": ""
428 | },
429 | "require": {
430 | "php": ">=7.1.3",
431 | "psr/log": "^1|^2|^3",
432 | "symfony/http-client-contracts": "^1.1.10|^2",
433 | "symfony/polyfill-php73": "^1.11",
434 | "symfony/polyfill-php80": "^1.16",
435 | "symfony/service-contracts": "^1.0|^2"
436 | },
437 | "provide": {
438 | "php-http/async-client-implementation": "*",
439 | "php-http/client-implementation": "*",
440 | "psr/http-client-implementation": "1.0",
441 | "symfony/http-client-implementation": "1.1|2.0"
442 | },
443 | "require-dev": {
444 | "guzzlehttp/promises": "^1.4",
445 | "nyholm/psr7": "^1.0",
446 | "php-http/httplug": "^1.0|^2.0",
447 | "psr/http-client": "^1.0",
448 | "symfony/dependency-injection": "^4.3|^5.0",
449 | "symfony/http-kernel": "^4.4.13",
450 | "symfony/process": "^4.2|^5.0"
451 | },
452 | "type": "library",
453 | "autoload": {
454 | "psr-4": {
455 | "Symfony\\Component\\HttpClient\\": ""
456 | },
457 | "exclude-from-classmap": [
458 | "/Tests/"
459 | ]
460 | },
461 | "notification-url": "https://packagist.org/downloads/",
462 | "license": [
463 | "MIT"
464 | ],
465 | "authors": [
466 | {
467 | "name": "Nicolas Grekas",
468 | "email": "p@tchwork.com"
469 | },
470 | {
471 | "name": "Symfony Community",
472 | "homepage": "https://symfony.com/contributors"
473 | }
474 | ],
475 | "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
476 | "homepage": "https://symfony.com",
477 | "support": {
478 | "source": "https://github.com/symfony/http-client/tree/v4.4.31"
479 | },
480 | "funding": [
481 | {
482 | "url": "https://symfony.com/sponsor",
483 | "type": "custom"
484 | },
485 | {
486 | "url": "https://github.com/fabpot",
487 | "type": "github"
488 | },
489 | {
490 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
491 | "type": "tidelift"
492 | }
493 | ],
494 | "time": "2021-09-06T10:00:00+00:00"
495 | },
496 | {
497 | "name": "symfony/http-client-contracts",
498 | "version": "v2.4.0",
499 | "source": {
500 | "type": "git",
501 | "url": "https://github.com/symfony/http-client-contracts.git",
502 | "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4"
503 | },
504 | "dist": {
505 | "type": "zip",
506 | "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
507 | "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
508 | "shasum": ""
509 | },
510 | "require": {
511 | "php": ">=7.2.5"
512 | },
513 | "suggest": {
514 | "symfony/http-client-implementation": ""
515 | },
516 | "type": "library",
517 | "extra": {
518 | "branch-alias": {
519 | "dev-main": "2.4-dev"
520 | },
521 | "thanks": {
522 | "name": "symfony/contracts",
523 | "url": "https://github.com/symfony/contracts"
524 | }
525 | },
526 | "autoload": {
527 | "psr-4": {
528 | "Symfony\\Contracts\\HttpClient\\": ""
529 | }
530 | },
531 | "notification-url": "https://packagist.org/downloads/",
532 | "license": [
533 | "MIT"
534 | ],
535 | "authors": [
536 | {
537 | "name": "Nicolas Grekas",
538 | "email": "p@tchwork.com"
539 | },
540 | {
541 | "name": "Symfony Community",
542 | "homepage": "https://symfony.com/contributors"
543 | }
544 | ],
545 | "description": "Generic abstractions related to HTTP clients",
546 | "homepage": "https://symfony.com",
547 | "keywords": [
548 | "abstractions",
549 | "contracts",
550 | "decoupling",
551 | "interfaces",
552 | "interoperability",
553 | "standards"
554 | ],
555 | "support": {
556 | "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0"
557 | },
558 | "funding": [
559 | {
560 | "url": "https://symfony.com/sponsor",
561 | "type": "custom"
562 | },
563 | {
564 | "url": "https://github.com/fabpot",
565 | "type": "github"
566 | },
567 | {
568 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
569 | "type": "tidelift"
570 | }
571 | ],
572 | "time": "2021-04-11T23:07:08+00:00"
573 | },
574 | {
575 | "name": "symfony/polyfill-php73",
576 | "version": "v1.23.0",
577 | "source": {
578 | "type": "git",
579 | "url": "https://github.com/symfony/polyfill-php73.git",
580 | "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010"
581 | },
582 | "dist": {
583 | "type": "zip",
584 | "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010",
585 | "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010",
586 | "shasum": ""
587 | },
588 | "require": {
589 | "php": ">=7.1"
590 | },
591 | "type": "library",
592 | "extra": {
593 | "branch-alias": {
594 | "dev-main": "1.23-dev"
595 | },
596 | "thanks": {
597 | "name": "symfony/polyfill",
598 | "url": "https://github.com/symfony/polyfill"
599 | }
600 | },
601 | "autoload": {
602 | "psr-4": {
603 | "Symfony\\Polyfill\\Php73\\": ""
604 | },
605 | "files": [
606 | "bootstrap.php"
607 | ],
608 | "classmap": [
609 | "Resources/stubs"
610 | ]
611 | },
612 | "notification-url": "https://packagist.org/downloads/",
613 | "license": [
614 | "MIT"
615 | ],
616 | "authors": [
617 | {
618 | "name": "Nicolas Grekas",
619 | "email": "p@tchwork.com"
620 | },
621 | {
622 | "name": "Symfony Community",
623 | "homepage": "https://symfony.com/contributors"
624 | }
625 | ],
626 | "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
627 | "homepage": "https://symfony.com",
628 | "keywords": [
629 | "compatibility",
630 | "polyfill",
631 | "portable",
632 | "shim"
633 | ],
634 | "support": {
635 | "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0"
636 | },
637 | "funding": [
638 | {
639 | "url": "https://symfony.com/sponsor",
640 | "type": "custom"
641 | },
642 | {
643 | "url": "https://github.com/fabpot",
644 | "type": "github"
645 | },
646 | {
647 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
648 | "type": "tidelift"
649 | }
650 | ],
651 | "time": "2021-02-19T12:13:01+00:00"
652 | },
653 | {
654 | "name": "symfony/polyfill-php80",
655 | "version": "v1.23.1",
656 | "source": {
657 | "type": "git",
658 | "url": "https://github.com/symfony/polyfill-php80.git",
659 | "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be"
660 | },
661 | "dist": {
662 | "type": "zip",
663 | "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be",
664 | "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be",
665 | "shasum": ""
666 | },
667 | "require": {
668 | "php": ">=7.1"
669 | },
670 | "type": "library",
671 | "extra": {
672 | "branch-alias": {
673 | "dev-main": "1.23-dev"
674 | },
675 | "thanks": {
676 | "name": "symfony/polyfill",
677 | "url": "https://github.com/symfony/polyfill"
678 | }
679 | },
680 | "autoload": {
681 | "psr-4": {
682 | "Symfony\\Polyfill\\Php80\\": ""
683 | },
684 | "files": [
685 | "bootstrap.php"
686 | ],
687 | "classmap": [
688 | "Resources/stubs"
689 | ]
690 | },
691 | "notification-url": "https://packagist.org/downloads/",
692 | "license": [
693 | "MIT"
694 | ],
695 | "authors": [
696 | {
697 | "name": "Ion Bazan",
698 | "email": "ion.bazan@gmail.com"
699 | },
700 | {
701 | "name": "Nicolas Grekas",
702 | "email": "p@tchwork.com"
703 | },
704 | {
705 | "name": "Symfony Community",
706 | "homepage": "https://symfony.com/contributors"
707 | }
708 | ],
709 | "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
710 | "homepage": "https://symfony.com",
711 | "keywords": [
712 | "compatibility",
713 | "polyfill",
714 | "portable",
715 | "shim"
716 | ],
717 | "support": {
718 | "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1"
719 | },
720 | "funding": [
721 | {
722 | "url": "https://symfony.com/sponsor",
723 | "type": "custom"
724 | },
725 | {
726 | "url": "https://github.com/fabpot",
727 | "type": "github"
728 | },
729 | {
730 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
731 | "type": "tidelift"
732 | }
733 | ],
734 | "time": "2021-07-28T13:41:28+00:00"
735 | },
736 | {
737 | "name": "symfony/service-contracts",
738 | "version": "v2.4.0",
739 | "source": {
740 | "type": "git",
741 | "url": "https://github.com/symfony/service-contracts.git",
742 | "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb"
743 | },
744 | "dist": {
745 | "type": "zip",
746 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
747 | "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
748 | "shasum": ""
749 | },
750 | "require": {
751 | "php": ">=7.2.5",
752 | "psr/container": "^1.1"
753 | },
754 | "suggest": {
755 | "symfony/service-implementation": ""
756 | },
757 | "type": "library",
758 | "extra": {
759 | "branch-alias": {
760 | "dev-main": "2.4-dev"
761 | },
762 | "thanks": {
763 | "name": "symfony/contracts",
764 | "url": "https://github.com/symfony/contracts"
765 | }
766 | },
767 | "autoload": {
768 | "psr-4": {
769 | "Symfony\\Contracts\\Service\\": ""
770 | }
771 | },
772 | "notification-url": "https://packagist.org/downloads/",
773 | "license": [
774 | "MIT"
775 | ],
776 | "authors": [
777 | {
778 | "name": "Nicolas Grekas",
779 | "email": "p@tchwork.com"
780 | },
781 | {
782 | "name": "Symfony Community",
783 | "homepage": "https://symfony.com/contributors"
784 | }
785 | ],
786 | "description": "Generic abstractions related to writing services",
787 | "homepage": "https://symfony.com",
788 | "keywords": [
789 | "abstractions",
790 | "contracts",
791 | "decoupling",
792 | "interfaces",
793 | "interoperability",
794 | "standards"
795 | ],
796 | "support": {
797 | "source": "https://github.com/symfony/service-contracts/tree/v2.4.0"
798 | },
799 | "funding": [
800 | {
801 | "url": "https://symfony.com/sponsor",
802 | "type": "custom"
803 | },
804 | {
805 | "url": "https://github.com/fabpot",
806 | "type": "github"
807 | },
808 | {
809 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
810 | "type": "tidelift"
811 | }
812 | ],
813 | "time": "2021-04-01T10:43:52+00:00"
814 | }
815 | ],
816 | "packages-dev": [],
817 | "aliases": [],
818 | "minimum-stability": "stable",
819 | "stability-flags": [],
820 | "prefer-stable": false,
821 | "prefer-lowest": false,
822 | "platform": {
823 | "php": ">=7.2"
824 | },
825 | "platform-dev": [],
826 | "platform-overrides": {
827 | "php": "7.4.12"
828 | },
829 | "plugin-api-version": "2.0.0"
830 | }
831 |
--------------------------------------------------------------------------------