├── .gitignore ├── README.md ├── _flash_message.php ├── _footer.php ├── _header.php ├── bin ├── run_tests ├── start_server └── stop_server ├── bootstrap.php ├── challenge_chapter_04 └── public │ └── congratulations.php ├── challenge_chapter_07 ├── _footer.php ├── _header.php ├── bootstrap.php ├── pages │ ├── 404.php │ ├── login.php │ ├── logout.php │ └── tasks.php └── public │ ├── bootstrap.min.css │ └── index.php ├── challenge_chapter_08 ├── _footer.php ├── _header.php ├── bootstrap.php ├── data │ └── .gitkeep ├── pages │ ├── 404.php │ ├── create-task.php │ ├── list-tasks.php │ ├── login.php │ └── logout.php └── public │ ├── bootstrap.min.css │ └── index.php ├── challenge_chapter_09 ├── _footer.php ├── _header.php ├── bootstrap.php ├── data │ └── .gitkeep ├── pages │ ├── 404.php │ ├── create-task.php │ ├── edit-task.php │ ├── functions │ │ └── tasks-crud.php │ ├── list-tasks.php │ ├── login.php │ ├── logout.php │ ├── mark-as-done.php │ └── snippets │ │ └── _task-form.php └── public │ ├── bootstrap.min.css │ └── index.php ├── composer.json ├── composer.lock ├── data └── .gitkeep ├── docker-compose.yml ├── docker └── webserver │ ├── Dockerfile │ └── php.ini ├── pages ├── 404.php ├── create-tour.php ├── delete-tour.php ├── edit-tour.php ├── error.php ├── functions │ └── tour-crud.php ├── homepage.php ├── list-tours.php ├── login.php ├── logout.php ├── name.php ├── oops.php ├── pictures.php ├── random.php ├── secret.php ├── snippets │ └── _tour-form.php └── tour.php ├── php-prod.ini ├── php.ini ├── phpunit.xml ├── public ├── apple.jpg ├── bootstrap.min.css ├── cat.jpg ├── elephpant.jpg ├── favicon.ico └── index.php ├── secret.txt └── tests ├── ChallengeChapter04Test.php ├── ChallengeChapter07Test.php ├── ChallengeChapter08Test.php ├── ChallengeChapter09Test.php ├── HomepageTest.php ├── PicturesTest.php ├── ToursTest.php ├── berlin.jpg └── paris.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | /data/* 2 | /public/uploads/* 3 | /vendor/ 4 | /challenge_chapter_08/data/* 5 | /challenge_chapter_09/data/* 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | If you want to run the tests for this project, but you don't want to install any specific PHP version on your computer, you can use the bundled Docker setup. 4 | First, install Docker on your machine. 5 | Then run: 6 | 7 | ```bash 8 | docker-compose pull 9 | ``` 10 | 11 | This will pull the required Docker image. 12 | 13 | Now run: 14 | 15 | ```bash 16 | docker-compose run --rm composer install --prefer-dist --no-progress --ignore-platform-reqs 17 | ``` 18 | 19 | This will install the required PHP dependencies in `vendor/`. 20 | 21 | Finally, run: 22 | 23 | ```bash 24 | bin/run_tests 25 | ``` 26 | 27 | If you want to look at the website itself: 28 | 29 | ```bash 30 | bin/start_server 31 | ``` 32 | 33 | Then go to 34 | 35 | To stop the webserver: 36 | 37 | ```bash 38 | bin/stop_server 39 | ``` 40 | 41 | ## Challenges 42 | 43 | To start the projects of the challenges: 44 | 45 | ```bash 46 | bin/start_server challenge_chapter_04 47 | ``` 48 | -------------------------------------------------------------------------------- /_flash_message.php: -------------------------------------------------------------------------------- 1 | 4 | 7 | 12 | -------------------------------------------------------------------------------- /_footer.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /_header.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | <?php echo htmlspecialchars($title, ENT_QUOTES); ?> 8 | 9 | 10 | 11 |
12 | 53 | 'English', 5 | 'nl' => 'Nederlands', 6 | 'de' => 'Deutsch' 7 | ]; 8 | 9 | // Default language: en 10 | $defaultLanguage = 'en'; 11 | 12 | $selectedLanguage = $defaultLanguage; 13 | 14 | if (isset($_GET['language'])) { 15 | // If the user selected a language, use it 16 | $selectedLanguage = $_GET['language']; 17 | } elseif (isset($_COOKIE['language'])) { 18 | // If the language is stored as a cookie, use it 19 | $selectedLanguage = $_COOKIE['language']; 20 | } 21 | 22 | // If the language doesn't actually exist, use the default language 23 | if (!isset($availableLanguages[$selectedLanguage])) { 24 | $selectedLanguage = $defaultLanguage; 25 | } 26 | 27 | setcookie('language', $selectedLanguage); 28 | 29 | $messages = [ 30 | 'en' => 'Congratulations!', 31 | 'nl' => 'Gefeliciteerd!', 32 | 'de' => 'Gratuliere!' 33 | ] 34 | ?> 35 | 36 | 37 | 38 | Congratulations 39 | 40 | 41 |
42 |

43 | 44 | 56 |

57 |

58 | 59 |

60 |
61 |

62 | 63 |

64 | 65 | 66 | -------------------------------------------------------------------------------- /challenge_chapter_07/_footer.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | -------------------------------------------------------------------------------- /challenge_chapter_07/_header.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | <?php echo htmlspecialchars($title, ENT_QUOTES); ?> 8 | 9 | 10 | 11 |
12 | 38 | 7 |

Page not found

8 |

Sorry, we could not find the page you requested.

9 | '$2y$10$1sXx3dPquOicIl53Y7XRdOqyS4P6flYXXpxHpTC83ZnusdxpEPtXe', 8 | 'tomas' => '$2y$10$vruLMw5IRBI4WwGVfpwF2Om3VgULeTrd7yC3tHAURqVLaCW72unQy' 9 | ]; 10 | 11 | if (isset($_POST['username'], $_POST['password'])) { 12 | // The user has submitted the login form 13 | 14 | if (isset($users[$_POST['username']])) { 15 | // The provided username is correct, now validate the password 16 | $expectedPasswordHash = $users[$_POST['username']]; 17 | 18 | if (password_verify($_POST['password'], $expectedPasswordHash)) { 19 | // The provided password is also correct 20 | 21 | // Remember the username of the user who just logged in 22 | $_SESSION['authenticated_user'] = $_POST['username']; 23 | 24 | // Redirect to / 25 | header('Location: /tasks'); 26 | exit; 27 | } 28 | } 29 | } 30 | ?> 31 | 35 |
36 |
37 | 40 | 41 |
42 |
43 | 46 | 47 |
48 |
49 | 50 |
51 |
52 | 18 |

Manage tasks

19 |

New task

20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |

Tasks

31 | 37 | 46 | 'login.php', 4 | '/logout' => 'logout.php', 5 | '/tasks' => 'tasks.php' 6 | ]; 7 | 8 | $pathInfo = $_SERVER['PATH_INFO'] ?? '/'; 9 | if (isset($urlMap[$pathInfo])) { 10 | // Load a specific page script 11 | include(__DIR__ . '/../pages/' . $urlMap[$pathInfo]); 12 | } else { 13 | // Return a 404 status code 14 | header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); 15 | include(__DIR__ . '/../pages/404.php'); 16 | } 17 | -------------------------------------------------------------------------------- /challenge_chapter_08/_footer.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | -------------------------------------------------------------------------------- /challenge_chapter_08/_header.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | <?php echo htmlspecialchars($title, ENT_QUOTES); ?> 8 | 9 | 10 | 11 |
12 | 41 | 7 |

Page not found

8 |

Sorry, we could not find the page you requested.

9 | $_POST['task'] ?? '' 14 | ]; 15 | 16 | if ($normalizedData['task'] === '') { 17 | $formErrors['task'] = 'Task can not be empty'; 18 | } 19 | 20 | if (count($formErrors) === 0) { 21 | $tasksJsonFile = __DIR__ . '/../data/tasks.json'; 22 | if (file_exists($tasksJsonFile)) { 23 | $jsonData = file_get_contents($tasksJsonFile); 24 | 25 | $tasksData = json_decode($jsonData, true); 26 | } else { 27 | $tasksData = []; 28 | } 29 | 30 | $tasksData[] = [ 31 | 'username' => $_SESSION['authenticated_user'], 32 | 'task' => $normalizedData['task'] 33 | ]; 34 | file_put_contents($tasksJsonFile, json_encode($tasksData, JSON_PRETTY_PRINT)); 35 | } 36 | } 37 | 38 | $title = 'New task'; 39 | include(__DIR__ . '/../_header.php'); 40 | ?> 41 |

New task

42 |
43 |
44 | 45 | 46 | 51 |
52 |
53 | 54 |
55 |
56 | 21 |

Tasks

22 | 33 | '$2y$10$1sXx3dPquOicIl53Y7XRdOqyS4P6flYXXpxHpTC83ZnusdxpEPtXe', 8 | 'tomas' => '$2y$10$vruLMw5IRBI4WwGVfpwF2Om3VgULeTrd7yC3tHAURqVLaCW72unQy' 9 | ]; 10 | 11 | if (isset($_POST['username'], $_POST['password'])) { 12 | // The user has submitted the login form 13 | 14 | if (isset($users[$_POST['username']])) { 15 | // The provided username is correct, now validate the password 16 | $expectedPasswordHash = $users[$_POST['username']]; 17 | 18 | if (password_verify($_POST['password'], $expectedPasswordHash)) { 19 | // The provided password is also correct 20 | 21 | // Remember the username of the user who just logged in 22 | $_SESSION['authenticated_user'] = $_POST['username']; 23 | 24 | // Redirect to / 25 | header('Location: /list-tasks'); 26 | exit; 27 | } 28 | } 29 | } 30 | ?> 31 | 35 |
36 |
37 | 40 | 41 |
42 |
43 | 46 | 47 |
48 |
49 | 50 |
51 |
52 | 'login.php', 4 | '/logout' => 'logout.php', 5 | '/list-tasks' => 'list-tasks.php', 6 | '/create-task' => 'create-task.php' 7 | ]; 8 | 9 | $pathInfo = $_SERVER['PATH_INFO'] ?? '/'; 10 | if (isset($urlMap[$pathInfo])) { 11 | // Load a specific page script 12 | include(__DIR__ . '/../pages/' . $urlMap[$pathInfo]); 13 | } else { 14 | // Return a 404 status code 15 | header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); 16 | include(__DIR__ . '/../pages/404.php'); 17 | } 18 | -------------------------------------------------------------------------------- /challenge_chapter_09/_footer.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | -------------------------------------------------------------------------------- /challenge_chapter_09/_header.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | <?php echo htmlspecialchars($title, ENT_QUOTES); ?> 8 | 9 | 10 | 11 |
12 | 41 | 7 |

Page not found

8 |

Sorry, we could not find the page you requested.

9 | count($tasksData) + 1, 19 | 'username' => $_SESSION['authenticated_user'], 20 | 'task' => $normalizedData['task'] 21 | ]; 22 | 23 | save_all_tasks($tasksData); 24 | 25 | header('Location: /list-tasks'); 26 | exit; 27 | } 28 | } 29 | 30 | $title = 'New task'; 31 | include(__DIR__ . '/../_header.php'); 32 | ?> 33 |

New task

34 | 36 |

New task

37 | 38 | isset($submittedData['task']) 7 | ? (string)$submittedData['task'] 8 | : '', 9 | ]; 10 | } 11 | 12 | function validate_normalized_data(array $normalizedData): array 13 | { 14 | $formErrors = []; 15 | 16 | if ($normalizedData['task'] === '') { 17 | $formErrors['task'] = 'Task can not be empty'; 18 | } 19 | 20 | return $formErrors; 21 | } 22 | 23 | function load_all_tasks_data(): array 24 | { 25 | $tasksJsonFile = __DIR__ . '/../../data/tasks.json'; 26 | if (!file_exists($tasksJsonFile)) { 27 | return []; 28 | } 29 | 30 | $jsonData = file_get_contents($tasksJsonFile); 31 | 32 | return json_decode($jsonData, true); 33 | } 34 | 35 | function save_all_tasks(array $tasksData): void 36 | { 37 | $tasksJsonFile = __DIR__ . '/../../data/tasks.json'; 38 | 39 | $jsonData = json_encode($tasksData, JSON_PRETTY_PRINT); 40 | 41 | file_put_contents($tasksJsonFile, $jsonData); 42 | } 43 | 44 | function load_task_data(int $id): array 45 | { 46 | $tasksData = load_all_tasks_data(); 47 | 48 | foreach ($tasksData as $taskData) { 49 | if ($taskData['id'] === $id) { 50 | return $taskData; 51 | } 52 | } 53 | 54 | throw new RuntimeException('Could not find task with ID ' . $id); 55 | } 56 | 57 | function save_task_data(array $modifiedTaskData): void 58 | { 59 | $tasksData = load_all_tasks_data(); 60 | 61 | foreach ($tasksData as $key => $taskData) { 62 | if ($taskData['id'] === $modifiedTaskData['id']) { 63 | $tasksData[$key] = $modifiedTaskData; 64 | } 65 | } 66 | 67 | save_all_tasks($tasksData); 68 | } 69 | 70 | function delete_task(int $id): void 71 | { 72 | $tasksData = load_all_tasks_data(); 73 | 74 | foreach ($tasksData as $key => $taskData) { 75 | if ($taskData['id'] === $id) { 76 | $tasksData[$key]['is_deleted'] = true; 77 | } 78 | } 79 | 80 | save_all_tasks($tasksData); 81 | } 82 | -------------------------------------------------------------------------------- /challenge_chapter_09/pages/list-tasks.php: -------------------------------------------------------------------------------- 1 | 14 |

Tasks

15 | 39 | '$2y$10$1sXx3dPquOicIl53Y7XRdOqyS4P6flYXXpxHpTC83ZnusdxpEPtXe', 8 | 'tomas' => '$2y$10$vruLMw5IRBI4WwGVfpwF2Om3VgULeTrd7yC3tHAURqVLaCW72unQy' 9 | ]; 10 | 11 | if (isset($_POST['username'], $_POST['password'])) { 12 | // The user has submitted the login form 13 | 14 | if (isset($users[$_POST['username']])) { 15 | // The provided username is correct, now validate the password 16 | $expectedPasswordHash = $users[$_POST['username']]; 17 | 18 | if (password_verify($_POST['password'], $expectedPasswordHash)) { 19 | // The provided password is also correct 20 | 21 | // Remember the username of the user who just logged in 22 | $_SESSION['authenticated_user'] = $_POST['username']; 23 | 24 | // Redirect to / 25 | header('Location: /list-tasks'); 26 | exit; 27 | } 28 | } 29 | } 30 | ?> 31 | 35 |
36 |
37 | 40 | 41 |
42 |
43 | 46 | 47 |
48 |
49 | 50 |
51 |
52 | 8 |
9 |
10 | 11 | 12 | 17 |
18 |
19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /challenge_chapter_09/public/index.php: -------------------------------------------------------------------------------- 1 | 'login.php', 4 | '/logout' => 'logout.php', 5 | '/list-tasks' => 'list-tasks.php', 6 | '/create-task' => 'create-task.php', 7 | '/edit-task' => 'edit-task.php', 8 | '/mark-as-done' => 'mark-as-done.php' 9 | ]; 10 | 11 | $pathInfo = $_SERVER['PATH_INFO'] ?? '/'; 12 | if (isset($urlMap[$pathInfo])) { 13 | // Load a specific page script 14 | include(__DIR__ . '/../pages/' . $urlMap[$pathInfo]); 15 | } else { 16 | // Return a 404 status code 17 | header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); 18 | include(__DIR__ . '/../pages/404.php'); 19 | } 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require-dev": { 3 | "phpunit/phpunit": "^9.2", 4 | "symfony/panther": "^0.7.1", 5 | "symfony/css-selector": "^5.1", 6 | "symfony/mime": "^5.1" 7 | }, 8 | "require": { 9 | "ext-json": "*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /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": "e714c4af59b46e1b2e2b29d5f635b294", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.3.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", 21 | "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1 || ^8.0" 26 | }, 27 | "require-dev": { 28 | "doctrine/coding-standard": "^6.0", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpbench/phpbench": "^0.13", 32 | "phpstan/phpstan-phpunit": "^0.11", 33 | "phpstan/phpstan-shim": "^0.11", 34 | "phpunit/phpunit": "^7.0" 35 | }, 36 | "type": "library", 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "1.2.x-dev" 40 | } 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 45 | } 46 | }, 47 | "notification-url": "https://packagist.org/downloads/", 48 | "license": [ 49 | "MIT" 50 | ], 51 | "authors": [ 52 | { 53 | "name": "Marco Pivetta", 54 | "email": "ocramius@gmail.com", 55 | "homepage": "http://ocramius.github.com/" 56 | } 57 | ], 58 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 59 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 60 | "keywords": [ 61 | "constructor", 62 | "instantiate" 63 | ], 64 | "funding": [ 65 | { 66 | "url": "https://www.doctrine-project.org/sponsorship.html", 67 | "type": "custom" 68 | }, 69 | { 70 | "url": "https://www.patreon.com/phpdoctrine", 71 | "type": "patreon" 72 | }, 73 | { 74 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 75 | "type": "tidelift" 76 | } 77 | ], 78 | "time": "2020-05-29T17:27:14+00:00" 79 | }, 80 | { 81 | "name": "myclabs/deep-copy", 82 | "version": "1.9.5", 83 | "source": { 84 | "type": "git", 85 | "url": "https://github.com/myclabs/DeepCopy.git", 86 | "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" 87 | }, 88 | "dist": { 89 | "type": "zip", 90 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", 91 | "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", 92 | "shasum": "" 93 | }, 94 | "require": { 95 | "php": "^7.1" 96 | }, 97 | "replace": { 98 | "myclabs/deep-copy": "self.version" 99 | }, 100 | "require-dev": { 101 | "doctrine/collections": "^1.0", 102 | "doctrine/common": "^2.6", 103 | "phpunit/phpunit": "^7.1" 104 | }, 105 | "type": "library", 106 | "autoload": { 107 | "psr-4": { 108 | "DeepCopy\\": "src/DeepCopy/" 109 | }, 110 | "files": [ 111 | "src/DeepCopy/deep_copy.php" 112 | ] 113 | }, 114 | "notification-url": "https://packagist.org/downloads/", 115 | "license": [ 116 | "MIT" 117 | ], 118 | "description": "Create deep copies (clones) of your objects", 119 | "keywords": [ 120 | "clone", 121 | "copy", 122 | "duplicate", 123 | "object", 124 | "object graph" 125 | ], 126 | "time": "2020-01-17T21:11:47+00:00" 127 | }, 128 | { 129 | "name": "phar-io/manifest", 130 | "version": "1.0.3", 131 | "source": { 132 | "type": "git", 133 | "url": "https://github.com/phar-io/manifest.git", 134 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" 135 | }, 136 | "dist": { 137 | "type": "zip", 138 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 139 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 140 | "shasum": "" 141 | }, 142 | "require": { 143 | "ext-dom": "*", 144 | "ext-phar": "*", 145 | "phar-io/version": "^2.0", 146 | "php": "^5.6 || ^7.0" 147 | }, 148 | "type": "library", 149 | "extra": { 150 | "branch-alias": { 151 | "dev-master": "1.0.x-dev" 152 | } 153 | }, 154 | "autoload": { 155 | "classmap": [ 156 | "src/" 157 | ] 158 | }, 159 | "notification-url": "https://packagist.org/downloads/", 160 | "license": [ 161 | "BSD-3-Clause" 162 | ], 163 | "authors": [ 164 | { 165 | "name": "Arne Blankerts", 166 | "email": "arne@blankerts.de", 167 | "role": "Developer" 168 | }, 169 | { 170 | "name": "Sebastian Heuer", 171 | "email": "sebastian@phpeople.de", 172 | "role": "Developer" 173 | }, 174 | { 175 | "name": "Sebastian Bergmann", 176 | "email": "sebastian@phpunit.de", 177 | "role": "Developer" 178 | } 179 | ], 180 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 181 | "time": "2018-07-08T19:23:20+00:00" 182 | }, 183 | { 184 | "name": "phar-io/version", 185 | "version": "2.0.1", 186 | "source": { 187 | "type": "git", 188 | "url": "https://github.com/phar-io/version.git", 189 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" 190 | }, 191 | "dist": { 192 | "type": "zip", 193 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", 194 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", 195 | "shasum": "" 196 | }, 197 | "require": { 198 | "php": "^5.6 || ^7.0" 199 | }, 200 | "type": "library", 201 | "autoload": { 202 | "classmap": [ 203 | "src/" 204 | ] 205 | }, 206 | "notification-url": "https://packagist.org/downloads/", 207 | "license": [ 208 | "BSD-3-Clause" 209 | ], 210 | "authors": [ 211 | { 212 | "name": "Arne Blankerts", 213 | "email": "arne@blankerts.de", 214 | "role": "Developer" 215 | }, 216 | { 217 | "name": "Sebastian Heuer", 218 | "email": "sebastian@phpeople.de", 219 | "role": "Developer" 220 | }, 221 | { 222 | "name": "Sebastian Bergmann", 223 | "email": "sebastian@phpunit.de", 224 | "role": "Developer" 225 | } 226 | ], 227 | "description": "Library for handling version information and constraints", 228 | "time": "2018-07-08T19:19:57+00:00" 229 | }, 230 | { 231 | "name": "php-webdriver/webdriver", 232 | "version": "1.8.2", 233 | "source": { 234 | "type": "git", 235 | "url": "https://github.com/php-webdriver/php-webdriver.git", 236 | "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab" 237 | }, 238 | "dist": { 239 | "type": "zip", 240 | "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", 241 | "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", 242 | "shasum": "" 243 | }, 244 | "require": { 245 | "ext-curl": "*", 246 | "ext-json": "*", 247 | "ext-zip": "*", 248 | "php": "^5.6 || ~7.0", 249 | "symfony/polyfill-mbstring": "^1.12", 250 | "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0" 251 | }, 252 | "require-dev": { 253 | "friendsofphp/php-cs-fixer": "^2.0", 254 | "jakub-onderka/php-parallel-lint": "^1.0", 255 | "php-coveralls/php-coveralls": "^2.0", 256 | "php-mock/php-mock-phpunit": "^1.1", 257 | "phpunit/phpunit": "^5.7", 258 | "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", 259 | "sminnee/phpunit-mock-objects": "^3.4", 260 | "squizlabs/php_codesniffer": "^3.5", 261 | "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0" 262 | }, 263 | "suggest": { 264 | "ext-SimpleXML": "For Firefox profile creation" 265 | }, 266 | "type": "library", 267 | "extra": { 268 | "branch-alias": { 269 | "dev-master": "1.8.x-dev" 270 | } 271 | }, 272 | "autoload": { 273 | "psr-4": { 274 | "Facebook\\WebDriver\\": "lib/" 275 | }, 276 | "files": [ 277 | "lib/Exception/TimeoutException.php" 278 | ] 279 | }, 280 | "notification-url": "https://packagist.org/downloads/", 281 | "license": [ 282 | "MIT" 283 | ], 284 | "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", 285 | "homepage": "https://github.com/php-webdriver/php-webdriver", 286 | "keywords": [ 287 | "Chromedriver", 288 | "geckodriver", 289 | "php", 290 | "selenium", 291 | "webdriver" 292 | ], 293 | "time": "2020-03-04T14:40:12+00:00" 294 | }, 295 | { 296 | "name": "phpdocumentor/reflection-common", 297 | "version": "2.1.0", 298 | "source": { 299 | "type": "git", 300 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 301 | "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" 302 | }, 303 | "dist": { 304 | "type": "zip", 305 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", 306 | "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", 307 | "shasum": "" 308 | }, 309 | "require": { 310 | "php": ">=7.1" 311 | }, 312 | "type": "library", 313 | "extra": { 314 | "branch-alias": { 315 | "dev-master": "2.x-dev" 316 | } 317 | }, 318 | "autoload": { 319 | "psr-4": { 320 | "phpDocumentor\\Reflection\\": "src/" 321 | } 322 | }, 323 | "notification-url": "https://packagist.org/downloads/", 324 | "license": [ 325 | "MIT" 326 | ], 327 | "authors": [ 328 | { 329 | "name": "Jaap van Otterdijk", 330 | "email": "opensource@ijaap.nl" 331 | } 332 | ], 333 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 334 | "homepage": "http://www.phpdoc.org", 335 | "keywords": [ 336 | "FQSEN", 337 | "phpDocumentor", 338 | "phpdoc", 339 | "reflection", 340 | "static analysis" 341 | ], 342 | "time": "2020-04-27T09:25:28+00:00" 343 | }, 344 | { 345 | "name": "phpdocumentor/reflection-docblock", 346 | "version": "5.1.0", 347 | "source": { 348 | "type": "git", 349 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 350 | "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" 351 | }, 352 | "dist": { 353 | "type": "zip", 354 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", 355 | "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", 356 | "shasum": "" 357 | }, 358 | "require": { 359 | "ext-filter": "^7.1", 360 | "php": "^7.2", 361 | "phpdocumentor/reflection-common": "^2.0", 362 | "phpdocumentor/type-resolver": "^1.0", 363 | "webmozart/assert": "^1" 364 | }, 365 | "require-dev": { 366 | "doctrine/instantiator": "^1", 367 | "mockery/mockery": "^1" 368 | }, 369 | "type": "library", 370 | "extra": { 371 | "branch-alias": { 372 | "dev-master": "5.x-dev" 373 | } 374 | }, 375 | "autoload": { 376 | "psr-4": { 377 | "phpDocumentor\\Reflection\\": "src" 378 | } 379 | }, 380 | "notification-url": "https://packagist.org/downloads/", 381 | "license": [ 382 | "MIT" 383 | ], 384 | "authors": [ 385 | { 386 | "name": "Mike van Riel", 387 | "email": "me@mikevanriel.com" 388 | }, 389 | { 390 | "name": "Jaap van Otterdijk", 391 | "email": "account@ijaap.nl" 392 | } 393 | ], 394 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 395 | "time": "2020-02-22T12:28:44+00:00" 396 | }, 397 | { 398 | "name": "phpdocumentor/type-resolver", 399 | "version": "1.1.0", 400 | "source": { 401 | "type": "git", 402 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 403 | "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" 404 | }, 405 | "dist": { 406 | "type": "zip", 407 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", 408 | "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", 409 | "shasum": "" 410 | }, 411 | "require": { 412 | "php": "^7.2", 413 | "phpdocumentor/reflection-common": "^2.0" 414 | }, 415 | "require-dev": { 416 | "ext-tokenizer": "^7.2", 417 | "mockery/mockery": "~1" 418 | }, 419 | "type": "library", 420 | "extra": { 421 | "branch-alias": { 422 | "dev-master": "1.x-dev" 423 | } 424 | }, 425 | "autoload": { 426 | "psr-4": { 427 | "phpDocumentor\\Reflection\\": "src" 428 | } 429 | }, 430 | "notification-url": "https://packagist.org/downloads/", 431 | "license": [ 432 | "MIT" 433 | ], 434 | "authors": [ 435 | { 436 | "name": "Mike van Riel", 437 | "email": "me@mikevanriel.com" 438 | } 439 | ], 440 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 441 | "time": "2020-02-18T18:59:58+00:00" 442 | }, 443 | { 444 | "name": "phpspec/prophecy", 445 | "version": "v1.10.3", 446 | "source": { 447 | "type": "git", 448 | "url": "https://github.com/phpspec/prophecy.git", 449 | "reference": "451c3cd1418cf640de218914901e51b064abb093" 450 | }, 451 | "dist": { 452 | "type": "zip", 453 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", 454 | "reference": "451c3cd1418cf640de218914901e51b064abb093", 455 | "shasum": "" 456 | }, 457 | "require": { 458 | "doctrine/instantiator": "^1.0.2", 459 | "php": "^5.3|^7.0", 460 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", 461 | "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", 462 | "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" 463 | }, 464 | "require-dev": { 465 | "phpspec/phpspec": "^2.5 || ^3.2", 466 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" 467 | }, 468 | "type": "library", 469 | "extra": { 470 | "branch-alias": { 471 | "dev-master": "1.10.x-dev" 472 | } 473 | }, 474 | "autoload": { 475 | "psr-4": { 476 | "Prophecy\\": "src/Prophecy" 477 | } 478 | }, 479 | "notification-url": "https://packagist.org/downloads/", 480 | "license": [ 481 | "MIT" 482 | ], 483 | "authors": [ 484 | { 485 | "name": "Konstantin Kudryashov", 486 | "email": "ever.zet@gmail.com", 487 | "homepage": "http://everzet.com" 488 | }, 489 | { 490 | "name": "Marcello Duarte", 491 | "email": "marcello.duarte@gmail.com" 492 | } 493 | ], 494 | "description": "Highly opinionated mocking framework for PHP 5.3+", 495 | "homepage": "https://github.com/phpspec/prophecy", 496 | "keywords": [ 497 | "Double", 498 | "Dummy", 499 | "fake", 500 | "mock", 501 | "spy", 502 | "stub" 503 | ], 504 | "time": "2020-03-05T15:02:03+00:00" 505 | }, 506 | { 507 | "name": "phpunit/php-code-coverage", 508 | "version": "8.0.2", 509 | "source": { 510 | "type": "git", 511 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 512 | "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc" 513 | }, 514 | "dist": { 515 | "type": "zip", 516 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca6647ffddd2add025ab3f21644a441d7c146cdc", 517 | "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc", 518 | "shasum": "" 519 | }, 520 | "require": { 521 | "ext-dom": "*", 522 | "ext-xmlwriter": "*", 523 | "php": "^7.3", 524 | "phpunit/php-file-iterator": "^3.0", 525 | "phpunit/php-text-template": "^2.0", 526 | "phpunit/php-token-stream": "^4.0", 527 | "sebastian/code-unit-reverse-lookup": "^2.0", 528 | "sebastian/environment": "^5.0", 529 | "sebastian/version": "^3.0", 530 | "theseer/tokenizer": "^1.1.3" 531 | }, 532 | "require-dev": { 533 | "phpunit/phpunit": "^9.0" 534 | }, 535 | "suggest": { 536 | "ext-pcov": "*", 537 | "ext-xdebug": "*" 538 | }, 539 | "type": "library", 540 | "extra": { 541 | "branch-alias": { 542 | "dev-master": "8.0-dev" 543 | } 544 | }, 545 | "autoload": { 546 | "classmap": [ 547 | "src/" 548 | ] 549 | }, 550 | "notification-url": "https://packagist.org/downloads/", 551 | "license": [ 552 | "BSD-3-Clause" 553 | ], 554 | "authors": [ 555 | { 556 | "name": "Sebastian Bergmann", 557 | "email": "sebastian@phpunit.de", 558 | "role": "lead" 559 | } 560 | ], 561 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 562 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 563 | "keywords": [ 564 | "coverage", 565 | "testing", 566 | "xunit" 567 | ], 568 | "funding": [ 569 | { 570 | "url": "https://github.com/sebastianbergmann", 571 | "type": "github" 572 | } 573 | ], 574 | "time": "2020-05-23T08:02:54+00:00" 575 | }, 576 | { 577 | "name": "phpunit/php-file-iterator", 578 | "version": "3.0.1", 579 | "source": { 580 | "type": "git", 581 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 582 | "reference": "4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4" 583 | }, 584 | "dist": { 585 | "type": "zip", 586 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4", 587 | "reference": "4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4", 588 | "shasum": "" 589 | }, 590 | "require": { 591 | "php": "^7.3" 592 | }, 593 | "require-dev": { 594 | "phpunit/phpunit": "^9.0" 595 | }, 596 | "type": "library", 597 | "extra": { 598 | "branch-alias": { 599 | "dev-master": "3.0-dev" 600 | } 601 | }, 602 | "autoload": { 603 | "classmap": [ 604 | "src/" 605 | ] 606 | }, 607 | "notification-url": "https://packagist.org/downloads/", 608 | "license": [ 609 | "BSD-3-Clause" 610 | ], 611 | "authors": [ 612 | { 613 | "name": "Sebastian Bergmann", 614 | "email": "sebastian@phpunit.de", 615 | "role": "lead" 616 | } 617 | ], 618 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 619 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 620 | "keywords": [ 621 | "filesystem", 622 | "iterator" 623 | ], 624 | "funding": [ 625 | { 626 | "url": "https://github.com/sebastianbergmann", 627 | "type": "github" 628 | } 629 | ], 630 | "time": "2020-04-18T05:02:12+00:00" 631 | }, 632 | { 633 | "name": "phpunit/php-invoker", 634 | "version": "3.0.0", 635 | "source": { 636 | "type": "git", 637 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 638 | "reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a" 639 | }, 640 | "dist": { 641 | "type": "zip", 642 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/7579d5a1ba7f3ac11c80004d205877911315ae7a", 643 | "reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a", 644 | "shasum": "" 645 | }, 646 | "require": { 647 | "php": "^7.3" 648 | }, 649 | "require-dev": { 650 | "ext-pcntl": "*", 651 | "phpunit/phpunit": "^9.0" 652 | }, 653 | "suggest": { 654 | "ext-pcntl": "*" 655 | }, 656 | "type": "library", 657 | "extra": { 658 | "branch-alias": { 659 | "dev-master": "3.0-dev" 660 | } 661 | }, 662 | "autoload": { 663 | "classmap": [ 664 | "src/" 665 | ] 666 | }, 667 | "notification-url": "https://packagist.org/downloads/", 668 | "license": [ 669 | "BSD-3-Clause" 670 | ], 671 | "authors": [ 672 | { 673 | "name": "Sebastian Bergmann", 674 | "email": "sebastian@phpunit.de", 675 | "role": "lead" 676 | } 677 | ], 678 | "description": "Invoke callables with a timeout", 679 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 680 | "keywords": [ 681 | "process" 682 | ], 683 | "time": "2020-02-07T06:06:11+00:00" 684 | }, 685 | { 686 | "name": "phpunit/php-text-template", 687 | "version": "2.0.0", 688 | "source": { 689 | "type": "git", 690 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 691 | "reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346" 692 | }, 693 | "dist": { 694 | "type": "zip", 695 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/526dc996cc0ebdfa428cd2dfccd79b7b53fee346", 696 | "reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346", 697 | "shasum": "" 698 | }, 699 | "require": { 700 | "php": "^7.3" 701 | }, 702 | "type": "library", 703 | "extra": { 704 | "branch-alias": { 705 | "dev-master": "2.0-dev" 706 | } 707 | }, 708 | "autoload": { 709 | "classmap": [ 710 | "src/" 711 | ] 712 | }, 713 | "notification-url": "https://packagist.org/downloads/", 714 | "license": [ 715 | "BSD-3-Clause" 716 | ], 717 | "authors": [ 718 | { 719 | "name": "Sebastian Bergmann", 720 | "email": "sebastian@phpunit.de", 721 | "role": "lead" 722 | } 723 | ], 724 | "description": "Simple template engine.", 725 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 726 | "keywords": [ 727 | "template" 728 | ], 729 | "time": "2020-02-01T07:43:44+00:00" 730 | }, 731 | { 732 | "name": "phpunit/php-timer", 733 | "version": "5.0.0", 734 | "source": { 735 | "type": "git", 736 | "url": "https://github.com/sebastianbergmann/php-timer.git", 737 | "reference": "b0d089de001ba60ffa3be36b23e1b8150d072238" 738 | }, 739 | "dist": { 740 | "type": "zip", 741 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/b0d089de001ba60ffa3be36b23e1b8150d072238", 742 | "reference": "b0d089de001ba60ffa3be36b23e1b8150d072238", 743 | "shasum": "" 744 | }, 745 | "require": { 746 | "php": "^7.3" 747 | }, 748 | "require-dev": { 749 | "phpunit/phpunit": "^9.2" 750 | }, 751 | "type": "library", 752 | "extra": { 753 | "branch-alias": { 754 | "dev-master": "5.0-dev" 755 | } 756 | }, 757 | "autoload": { 758 | "classmap": [ 759 | "src/" 760 | ] 761 | }, 762 | "notification-url": "https://packagist.org/downloads/", 763 | "license": [ 764 | "BSD-3-Clause" 765 | ], 766 | "authors": [ 767 | { 768 | "name": "Sebastian Bergmann", 769 | "email": "sebastian@phpunit.de", 770 | "role": "lead" 771 | } 772 | ], 773 | "description": "Utility class for timing", 774 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 775 | "keywords": [ 776 | "timer" 777 | ], 778 | "funding": [ 779 | { 780 | "url": "https://github.com/sebastianbergmann", 781 | "type": "github" 782 | } 783 | ], 784 | "time": "2020-06-07T12:05:53+00:00" 785 | }, 786 | { 787 | "name": "phpunit/php-token-stream", 788 | "version": "4.0.1", 789 | "source": { 790 | "type": "git", 791 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 792 | "reference": "cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c" 793 | }, 794 | "dist": { 795 | "type": "zip", 796 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c", 797 | "reference": "cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c", 798 | "shasum": "" 799 | }, 800 | "require": { 801 | "ext-tokenizer": "*", 802 | "php": "^7.3" 803 | }, 804 | "require-dev": { 805 | "phpunit/phpunit": "^9.0" 806 | }, 807 | "type": "library", 808 | "extra": { 809 | "branch-alias": { 810 | "dev-master": "4.0-dev" 811 | } 812 | }, 813 | "autoload": { 814 | "classmap": [ 815 | "src/" 816 | ] 817 | }, 818 | "notification-url": "https://packagist.org/downloads/", 819 | "license": [ 820 | "BSD-3-Clause" 821 | ], 822 | "authors": [ 823 | { 824 | "name": "Sebastian Bergmann", 825 | "email": "sebastian@phpunit.de" 826 | } 827 | ], 828 | "description": "Wrapper around PHP's tokenizer extension.", 829 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 830 | "keywords": [ 831 | "tokenizer" 832 | ], 833 | "funding": [ 834 | { 835 | "url": "https://github.com/sebastianbergmann", 836 | "type": "github" 837 | } 838 | ], 839 | "time": "2020-05-06T09:56:31+00:00" 840 | }, 841 | { 842 | "name": "phpunit/phpunit", 843 | "version": "9.2.2", 844 | "source": { 845 | "type": "git", 846 | "url": "https://github.com/sebastianbergmann/phpunit.git", 847 | "reference": "8fd0d8f80029682da89516a554f4d5f5a030345c" 848 | }, 849 | "dist": { 850 | "type": "zip", 851 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8fd0d8f80029682da89516a554f4d5f5a030345c", 852 | "reference": "8fd0d8f80029682da89516a554f4d5f5a030345c", 853 | "shasum": "" 854 | }, 855 | "require": { 856 | "doctrine/instantiator": "^1.2.0", 857 | "ext-dom": "*", 858 | "ext-json": "*", 859 | "ext-libxml": "*", 860 | "ext-mbstring": "*", 861 | "ext-xml": "*", 862 | "ext-xmlwriter": "*", 863 | "myclabs/deep-copy": "^1.9.1", 864 | "phar-io/manifest": "^1.0.3", 865 | "phar-io/version": "^2.0.1", 866 | "php": "^7.3", 867 | "phpspec/prophecy": "^1.8.1", 868 | "phpunit/php-code-coverage": "^8.0.1", 869 | "phpunit/php-file-iterator": "^3.0", 870 | "phpunit/php-invoker": "^3.0", 871 | "phpunit/php-text-template": "^2.0", 872 | "phpunit/php-timer": "^5.0", 873 | "sebastian/code-unit": "^1.0.2", 874 | "sebastian/comparator": "^4.0", 875 | "sebastian/diff": "^4.0", 876 | "sebastian/environment": "^5.0.1", 877 | "sebastian/exporter": "^4.0", 878 | "sebastian/global-state": "^4.0", 879 | "sebastian/object-enumerator": "^4.0", 880 | "sebastian/resource-operations": "^3.0", 881 | "sebastian/type": "^2.1", 882 | "sebastian/version": "^3.0" 883 | }, 884 | "require-dev": { 885 | "ext-pdo": "*", 886 | "phpspec/prophecy-phpunit": "^2.0" 887 | }, 888 | "suggest": { 889 | "ext-soap": "*", 890 | "ext-xdebug": "*" 891 | }, 892 | "bin": [ 893 | "phpunit" 894 | ], 895 | "type": "library", 896 | "extra": { 897 | "branch-alias": { 898 | "dev-master": "9.2-dev" 899 | } 900 | }, 901 | "autoload": { 902 | "classmap": [ 903 | "src/" 904 | ], 905 | "files": [ 906 | "src/Framework/Assert/Functions.php" 907 | ] 908 | }, 909 | "notification-url": "https://packagist.org/downloads/", 910 | "license": [ 911 | "BSD-3-Clause" 912 | ], 913 | "authors": [ 914 | { 915 | "name": "Sebastian Bergmann", 916 | "email": "sebastian@phpunit.de", 917 | "role": "lead" 918 | } 919 | ], 920 | "description": "The PHP Unit Testing framework.", 921 | "homepage": "https://phpunit.de/", 922 | "keywords": [ 923 | "phpunit", 924 | "testing", 925 | "xunit" 926 | ], 927 | "funding": [ 928 | { 929 | "url": "https://phpunit.de/donate.html", 930 | "type": "custom" 931 | }, 932 | { 933 | "url": "https://github.com/sebastianbergmann", 934 | "type": "github" 935 | } 936 | ], 937 | "time": "2020-06-07T14:14:21+00:00" 938 | }, 939 | { 940 | "name": "psr/container", 941 | "version": "1.0.0", 942 | "source": { 943 | "type": "git", 944 | "url": "https://github.com/php-fig/container.git", 945 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" 946 | }, 947 | "dist": { 948 | "type": "zip", 949 | "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 950 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 951 | "shasum": "" 952 | }, 953 | "require": { 954 | "php": ">=5.3.0" 955 | }, 956 | "type": "library", 957 | "extra": { 958 | "branch-alias": { 959 | "dev-master": "1.0.x-dev" 960 | } 961 | }, 962 | "autoload": { 963 | "psr-4": { 964 | "Psr\\Container\\": "src/" 965 | } 966 | }, 967 | "notification-url": "https://packagist.org/downloads/", 968 | "license": [ 969 | "MIT" 970 | ], 971 | "authors": [ 972 | { 973 | "name": "PHP-FIG", 974 | "homepage": "http://www.php-fig.org/" 975 | } 976 | ], 977 | "description": "Common Container Interface (PHP FIG PSR-11)", 978 | "homepage": "https://github.com/php-fig/container", 979 | "keywords": [ 980 | "PSR-11", 981 | "container", 982 | "container-interface", 983 | "container-interop", 984 | "psr" 985 | ], 986 | "time": "2017-02-14T16:28:37+00:00" 987 | }, 988 | { 989 | "name": "psr/log", 990 | "version": "1.1.3", 991 | "source": { 992 | "type": "git", 993 | "url": "https://github.com/php-fig/log.git", 994 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" 995 | }, 996 | "dist": { 997 | "type": "zip", 998 | "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", 999 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", 1000 | "shasum": "" 1001 | }, 1002 | "require": { 1003 | "php": ">=5.3.0" 1004 | }, 1005 | "type": "library", 1006 | "extra": { 1007 | "branch-alias": { 1008 | "dev-master": "1.1.x-dev" 1009 | } 1010 | }, 1011 | "autoload": { 1012 | "psr-4": { 1013 | "Psr\\Log\\": "Psr/Log/" 1014 | } 1015 | }, 1016 | "notification-url": "https://packagist.org/downloads/", 1017 | "license": [ 1018 | "MIT" 1019 | ], 1020 | "authors": [ 1021 | { 1022 | "name": "PHP-FIG", 1023 | "homepage": "http://www.php-fig.org/" 1024 | } 1025 | ], 1026 | "description": "Common interface for logging libraries", 1027 | "homepage": "https://github.com/php-fig/log", 1028 | "keywords": [ 1029 | "log", 1030 | "psr", 1031 | "psr-3" 1032 | ], 1033 | "time": "2020-03-23T09:12:05+00:00" 1034 | }, 1035 | { 1036 | "name": "sebastian/code-unit", 1037 | "version": "1.0.2", 1038 | "source": { 1039 | "type": "git", 1040 | "url": "https://github.com/sebastianbergmann/code-unit.git", 1041 | "reference": "ac958085bc19fcd1d36425c781ef4cbb5b06e2a5" 1042 | }, 1043 | "dist": { 1044 | "type": "zip", 1045 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ac958085bc19fcd1d36425c781ef4cbb5b06e2a5", 1046 | "reference": "ac958085bc19fcd1d36425c781ef4cbb5b06e2a5", 1047 | "shasum": "" 1048 | }, 1049 | "require": { 1050 | "php": "^7.3" 1051 | }, 1052 | "require-dev": { 1053 | "phpunit/phpunit": "^9.0" 1054 | }, 1055 | "type": "library", 1056 | "extra": { 1057 | "branch-alias": { 1058 | "dev-master": "1.0-dev" 1059 | } 1060 | }, 1061 | "autoload": { 1062 | "classmap": [ 1063 | "src/" 1064 | ] 1065 | }, 1066 | "notification-url": "https://packagist.org/downloads/", 1067 | "license": [ 1068 | "BSD-3-Clause" 1069 | ], 1070 | "authors": [ 1071 | { 1072 | "name": "Sebastian Bergmann", 1073 | "email": "sebastian@phpunit.de", 1074 | "role": "lead" 1075 | } 1076 | ], 1077 | "description": "Collection of value objects that represent the PHP code units", 1078 | "homepage": "https://github.com/sebastianbergmann/code-unit", 1079 | "funding": [ 1080 | { 1081 | "url": "https://github.com/sebastianbergmann", 1082 | "type": "github" 1083 | } 1084 | ], 1085 | "time": "2020-04-30T05:58:10+00:00" 1086 | }, 1087 | { 1088 | "name": "sebastian/code-unit-reverse-lookup", 1089 | "version": "2.0.0", 1090 | "source": { 1091 | "type": "git", 1092 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1093 | "reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e" 1094 | }, 1095 | "dist": { 1096 | "type": "zip", 1097 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5b5dbe0044085ac41df47e79d34911a15b96d82e", 1098 | "reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e", 1099 | "shasum": "" 1100 | }, 1101 | "require": { 1102 | "php": "^7.3" 1103 | }, 1104 | "require-dev": { 1105 | "phpunit/phpunit": "^9.0" 1106 | }, 1107 | "type": "library", 1108 | "extra": { 1109 | "branch-alias": { 1110 | "dev-master": "2.0-dev" 1111 | } 1112 | }, 1113 | "autoload": { 1114 | "classmap": [ 1115 | "src/" 1116 | ] 1117 | }, 1118 | "notification-url": "https://packagist.org/downloads/", 1119 | "license": [ 1120 | "BSD-3-Clause" 1121 | ], 1122 | "authors": [ 1123 | { 1124 | "name": "Sebastian Bergmann", 1125 | "email": "sebastian@phpunit.de" 1126 | } 1127 | ], 1128 | "description": "Looks up which function or method a line of code belongs to", 1129 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1130 | "time": "2020-02-07T06:20:13+00:00" 1131 | }, 1132 | { 1133 | "name": "sebastian/comparator", 1134 | "version": "4.0.0", 1135 | "source": { 1136 | "type": "git", 1137 | "url": "https://github.com/sebastianbergmann/comparator.git", 1138 | "reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8" 1139 | }, 1140 | "dist": { 1141 | "type": "zip", 1142 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85b3435da967696ed618ff745f32be3ff4a2b8e8", 1143 | "reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8", 1144 | "shasum": "" 1145 | }, 1146 | "require": { 1147 | "php": "^7.3", 1148 | "sebastian/diff": "^4.0", 1149 | "sebastian/exporter": "^4.0" 1150 | }, 1151 | "require-dev": { 1152 | "phpunit/phpunit": "^9.0" 1153 | }, 1154 | "type": "library", 1155 | "extra": { 1156 | "branch-alias": { 1157 | "dev-master": "4.0-dev" 1158 | } 1159 | }, 1160 | "autoload": { 1161 | "classmap": [ 1162 | "src/" 1163 | ] 1164 | }, 1165 | "notification-url": "https://packagist.org/downloads/", 1166 | "license": [ 1167 | "BSD-3-Clause" 1168 | ], 1169 | "authors": [ 1170 | { 1171 | "name": "Sebastian Bergmann", 1172 | "email": "sebastian@phpunit.de" 1173 | }, 1174 | { 1175 | "name": "Jeff Welch", 1176 | "email": "whatthejeff@gmail.com" 1177 | }, 1178 | { 1179 | "name": "Volker Dusch", 1180 | "email": "github@wallbash.com" 1181 | }, 1182 | { 1183 | "name": "Bernhard Schussek", 1184 | "email": "bschussek@2bepublished.at" 1185 | } 1186 | ], 1187 | "description": "Provides the functionality to compare PHP values for equality", 1188 | "homepage": "https://github.com/sebastianbergmann/comparator", 1189 | "keywords": [ 1190 | "comparator", 1191 | "compare", 1192 | "equality" 1193 | ], 1194 | "time": "2020-02-07T06:08:51+00:00" 1195 | }, 1196 | { 1197 | "name": "sebastian/diff", 1198 | "version": "4.0.1", 1199 | "source": { 1200 | "type": "git", 1201 | "url": "https://github.com/sebastianbergmann/diff.git", 1202 | "reference": "3e523c576f29dacecff309f35e4cc5a5c168e78a" 1203 | }, 1204 | "dist": { 1205 | "type": "zip", 1206 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3e523c576f29dacecff309f35e4cc5a5c168e78a", 1207 | "reference": "3e523c576f29dacecff309f35e4cc5a5c168e78a", 1208 | "shasum": "" 1209 | }, 1210 | "require": { 1211 | "php": "^7.3" 1212 | }, 1213 | "require-dev": { 1214 | "phpunit/phpunit": "^9.0", 1215 | "symfony/process": "^4.2 || ^5" 1216 | }, 1217 | "type": "library", 1218 | "extra": { 1219 | "branch-alias": { 1220 | "dev-master": "4.0-dev" 1221 | } 1222 | }, 1223 | "autoload": { 1224 | "classmap": [ 1225 | "src/" 1226 | ] 1227 | }, 1228 | "notification-url": "https://packagist.org/downloads/", 1229 | "license": [ 1230 | "BSD-3-Clause" 1231 | ], 1232 | "authors": [ 1233 | { 1234 | "name": "Sebastian Bergmann", 1235 | "email": "sebastian@phpunit.de" 1236 | }, 1237 | { 1238 | "name": "Kore Nordmann", 1239 | "email": "mail@kore-nordmann.de" 1240 | } 1241 | ], 1242 | "description": "Diff implementation", 1243 | "homepage": "https://github.com/sebastianbergmann/diff", 1244 | "keywords": [ 1245 | "diff", 1246 | "udiff", 1247 | "unidiff", 1248 | "unified diff" 1249 | ], 1250 | "funding": [ 1251 | { 1252 | "url": "https://github.com/sebastianbergmann", 1253 | "type": "github" 1254 | } 1255 | ], 1256 | "time": "2020-05-08T05:01:12+00:00" 1257 | }, 1258 | { 1259 | "name": "sebastian/environment", 1260 | "version": "5.1.0", 1261 | "source": { 1262 | "type": "git", 1263 | "url": "https://github.com/sebastianbergmann/environment.git", 1264 | "reference": "c753f04d68cd489b6973cf9b4e505e191af3b05c" 1265 | }, 1266 | "dist": { 1267 | "type": "zip", 1268 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c753f04d68cd489b6973cf9b4e505e191af3b05c", 1269 | "reference": "c753f04d68cd489b6973cf9b4e505e191af3b05c", 1270 | "shasum": "" 1271 | }, 1272 | "require": { 1273 | "php": "^7.3" 1274 | }, 1275 | "require-dev": { 1276 | "phpunit/phpunit": "^9.0" 1277 | }, 1278 | "suggest": { 1279 | "ext-posix": "*" 1280 | }, 1281 | "type": "library", 1282 | "extra": { 1283 | "branch-alias": { 1284 | "dev-master": "5.0-dev" 1285 | } 1286 | }, 1287 | "autoload": { 1288 | "classmap": [ 1289 | "src/" 1290 | ] 1291 | }, 1292 | "notification-url": "https://packagist.org/downloads/", 1293 | "license": [ 1294 | "BSD-3-Clause" 1295 | ], 1296 | "authors": [ 1297 | { 1298 | "name": "Sebastian Bergmann", 1299 | "email": "sebastian@phpunit.de" 1300 | } 1301 | ], 1302 | "description": "Provides functionality to handle HHVM/PHP environments", 1303 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1304 | "keywords": [ 1305 | "Xdebug", 1306 | "environment", 1307 | "hhvm" 1308 | ], 1309 | "funding": [ 1310 | { 1311 | "url": "https://github.com/sebastianbergmann", 1312 | "type": "github" 1313 | } 1314 | ], 1315 | "time": "2020-04-14T13:36:52+00:00" 1316 | }, 1317 | { 1318 | "name": "sebastian/exporter", 1319 | "version": "4.0.0", 1320 | "source": { 1321 | "type": "git", 1322 | "url": "https://github.com/sebastianbergmann/exporter.git", 1323 | "reference": "80c26562e964016538f832f305b2286e1ec29566" 1324 | }, 1325 | "dist": { 1326 | "type": "zip", 1327 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/80c26562e964016538f832f305b2286e1ec29566", 1328 | "reference": "80c26562e964016538f832f305b2286e1ec29566", 1329 | "shasum": "" 1330 | }, 1331 | "require": { 1332 | "php": "^7.3", 1333 | "sebastian/recursion-context": "^4.0" 1334 | }, 1335 | "require-dev": { 1336 | "ext-mbstring": "*", 1337 | "phpunit/phpunit": "^9.0" 1338 | }, 1339 | "type": "library", 1340 | "extra": { 1341 | "branch-alias": { 1342 | "dev-master": "4.0-dev" 1343 | } 1344 | }, 1345 | "autoload": { 1346 | "classmap": [ 1347 | "src/" 1348 | ] 1349 | }, 1350 | "notification-url": "https://packagist.org/downloads/", 1351 | "license": [ 1352 | "BSD-3-Clause" 1353 | ], 1354 | "authors": [ 1355 | { 1356 | "name": "Sebastian Bergmann", 1357 | "email": "sebastian@phpunit.de" 1358 | }, 1359 | { 1360 | "name": "Jeff Welch", 1361 | "email": "whatthejeff@gmail.com" 1362 | }, 1363 | { 1364 | "name": "Volker Dusch", 1365 | "email": "github@wallbash.com" 1366 | }, 1367 | { 1368 | "name": "Adam Harvey", 1369 | "email": "aharvey@php.net" 1370 | }, 1371 | { 1372 | "name": "Bernhard Schussek", 1373 | "email": "bschussek@gmail.com" 1374 | } 1375 | ], 1376 | "description": "Provides the functionality to export PHP variables for visualization", 1377 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1378 | "keywords": [ 1379 | "export", 1380 | "exporter" 1381 | ], 1382 | "time": "2020-02-07T06:10:52+00:00" 1383 | }, 1384 | { 1385 | "name": "sebastian/global-state", 1386 | "version": "4.0.0", 1387 | "source": { 1388 | "type": "git", 1389 | "url": "https://github.com/sebastianbergmann/global-state.git", 1390 | "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72" 1391 | }, 1392 | "dist": { 1393 | "type": "zip", 1394 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bdb1e7c79e592b8c82cb1699be3c8743119b8a72", 1395 | "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72", 1396 | "shasum": "" 1397 | }, 1398 | "require": { 1399 | "php": "^7.3", 1400 | "sebastian/object-reflector": "^2.0", 1401 | "sebastian/recursion-context": "^4.0" 1402 | }, 1403 | "require-dev": { 1404 | "ext-dom": "*", 1405 | "phpunit/phpunit": "^9.0" 1406 | }, 1407 | "suggest": { 1408 | "ext-uopz": "*" 1409 | }, 1410 | "type": "library", 1411 | "extra": { 1412 | "branch-alias": { 1413 | "dev-master": "4.0-dev" 1414 | } 1415 | }, 1416 | "autoload": { 1417 | "classmap": [ 1418 | "src/" 1419 | ] 1420 | }, 1421 | "notification-url": "https://packagist.org/downloads/", 1422 | "license": [ 1423 | "BSD-3-Clause" 1424 | ], 1425 | "authors": [ 1426 | { 1427 | "name": "Sebastian Bergmann", 1428 | "email": "sebastian@phpunit.de" 1429 | } 1430 | ], 1431 | "description": "Snapshotting of global state", 1432 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1433 | "keywords": [ 1434 | "global state" 1435 | ], 1436 | "time": "2020-02-07T06:11:37+00:00" 1437 | }, 1438 | { 1439 | "name": "sebastian/object-enumerator", 1440 | "version": "4.0.0", 1441 | "source": { 1442 | "type": "git", 1443 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1444 | "reference": "e67516b175550abad905dc952f43285957ef4363" 1445 | }, 1446 | "dist": { 1447 | "type": "zip", 1448 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67516b175550abad905dc952f43285957ef4363", 1449 | "reference": "e67516b175550abad905dc952f43285957ef4363", 1450 | "shasum": "" 1451 | }, 1452 | "require": { 1453 | "php": "^7.3", 1454 | "sebastian/object-reflector": "^2.0", 1455 | "sebastian/recursion-context": "^4.0" 1456 | }, 1457 | "require-dev": { 1458 | "phpunit/phpunit": "^9.0" 1459 | }, 1460 | "type": "library", 1461 | "extra": { 1462 | "branch-alias": { 1463 | "dev-master": "4.0-dev" 1464 | } 1465 | }, 1466 | "autoload": { 1467 | "classmap": [ 1468 | "src/" 1469 | ] 1470 | }, 1471 | "notification-url": "https://packagist.org/downloads/", 1472 | "license": [ 1473 | "BSD-3-Clause" 1474 | ], 1475 | "authors": [ 1476 | { 1477 | "name": "Sebastian Bergmann", 1478 | "email": "sebastian@phpunit.de" 1479 | } 1480 | ], 1481 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1482 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1483 | "time": "2020-02-07T06:12:23+00:00" 1484 | }, 1485 | { 1486 | "name": "sebastian/object-reflector", 1487 | "version": "2.0.0", 1488 | "source": { 1489 | "type": "git", 1490 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1491 | "reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7" 1492 | }, 1493 | "dist": { 1494 | "type": "zip", 1495 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/f4fd0835cabb0d4a6546d9fe291e5740037aa1e7", 1496 | "reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7", 1497 | "shasum": "" 1498 | }, 1499 | "require": { 1500 | "php": "^7.3" 1501 | }, 1502 | "require-dev": { 1503 | "phpunit/phpunit": "^9.0" 1504 | }, 1505 | "type": "library", 1506 | "extra": { 1507 | "branch-alias": { 1508 | "dev-master": "2.0-dev" 1509 | } 1510 | }, 1511 | "autoload": { 1512 | "classmap": [ 1513 | "src/" 1514 | ] 1515 | }, 1516 | "notification-url": "https://packagist.org/downloads/", 1517 | "license": [ 1518 | "BSD-3-Clause" 1519 | ], 1520 | "authors": [ 1521 | { 1522 | "name": "Sebastian Bergmann", 1523 | "email": "sebastian@phpunit.de" 1524 | } 1525 | ], 1526 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1527 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1528 | "time": "2020-02-07T06:19:40+00:00" 1529 | }, 1530 | { 1531 | "name": "sebastian/recursion-context", 1532 | "version": "4.0.0", 1533 | "source": { 1534 | "type": "git", 1535 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1536 | "reference": "cdd86616411fc3062368b720b0425de10bd3d579" 1537 | }, 1538 | "dist": { 1539 | "type": "zip", 1540 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cdd86616411fc3062368b720b0425de10bd3d579", 1541 | "reference": "cdd86616411fc3062368b720b0425de10bd3d579", 1542 | "shasum": "" 1543 | }, 1544 | "require": { 1545 | "php": "^7.3" 1546 | }, 1547 | "require-dev": { 1548 | "phpunit/phpunit": "^9.0" 1549 | }, 1550 | "type": "library", 1551 | "extra": { 1552 | "branch-alias": { 1553 | "dev-master": "4.0-dev" 1554 | } 1555 | }, 1556 | "autoload": { 1557 | "classmap": [ 1558 | "src/" 1559 | ] 1560 | }, 1561 | "notification-url": "https://packagist.org/downloads/", 1562 | "license": [ 1563 | "BSD-3-Clause" 1564 | ], 1565 | "authors": [ 1566 | { 1567 | "name": "Sebastian Bergmann", 1568 | "email": "sebastian@phpunit.de" 1569 | }, 1570 | { 1571 | "name": "Jeff Welch", 1572 | "email": "whatthejeff@gmail.com" 1573 | }, 1574 | { 1575 | "name": "Adam Harvey", 1576 | "email": "aharvey@php.net" 1577 | } 1578 | ], 1579 | "description": "Provides functionality to recursively process PHP variables", 1580 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1581 | "time": "2020-02-07T06:18:20+00:00" 1582 | }, 1583 | { 1584 | "name": "sebastian/resource-operations", 1585 | "version": "3.0.0", 1586 | "source": { 1587 | "type": "git", 1588 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1589 | "reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98" 1590 | }, 1591 | "dist": { 1592 | "type": "zip", 1593 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98", 1594 | "reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98", 1595 | "shasum": "" 1596 | }, 1597 | "require": { 1598 | "php": "^7.3" 1599 | }, 1600 | "require-dev": { 1601 | "phpunit/phpunit": "^9.0" 1602 | }, 1603 | "type": "library", 1604 | "extra": { 1605 | "branch-alias": { 1606 | "dev-master": "3.0-dev" 1607 | } 1608 | }, 1609 | "autoload": { 1610 | "classmap": [ 1611 | "src/" 1612 | ] 1613 | }, 1614 | "notification-url": "https://packagist.org/downloads/", 1615 | "license": [ 1616 | "BSD-3-Clause" 1617 | ], 1618 | "authors": [ 1619 | { 1620 | "name": "Sebastian Bergmann", 1621 | "email": "sebastian@phpunit.de" 1622 | } 1623 | ], 1624 | "description": "Provides a list of PHP built-in functions that operate on resources", 1625 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1626 | "time": "2020-02-07T06:13:02+00:00" 1627 | }, 1628 | { 1629 | "name": "sebastian/type", 1630 | "version": "2.1.0", 1631 | "source": { 1632 | "type": "git", 1633 | "url": "https://github.com/sebastianbergmann/type.git", 1634 | "reference": "bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8" 1635 | }, 1636 | "dist": { 1637 | "type": "zip", 1638 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8", 1639 | "reference": "bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8", 1640 | "shasum": "" 1641 | }, 1642 | "require": { 1643 | "php": "^7.3" 1644 | }, 1645 | "require-dev": { 1646 | "phpunit/phpunit": "^9.2" 1647 | }, 1648 | "type": "library", 1649 | "extra": { 1650 | "branch-alias": { 1651 | "dev-master": "2.1-dev" 1652 | } 1653 | }, 1654 | "autoload": { 1655 | "classmap": [ 1656 | "src/" 1657 | ] 1658 | }, 1659 | "notification-url": "https://packagist.org/downloads/", 1660 | "license": [ 1661 | "BSD-3-Clause" 1662 | ], 1663 | "authors": [ 1664 | { 1665 | "name": "Sebastian Bergmann", 1666 | "email": "sebastian@phpunit.de", 1667 | "role": "lead" 1668 | } 1669 | ], 1670 | "description": "Collection of value objects that represent the types of the PHP type system", 1671 | "homepage": "https://github.com/sebastianbergmann/type", 1672 | "funding": [ 1673 | { 1674 | "url": "https://github.com/sebastianbergmann", 1675 | "type": "github" 1676 | } 1677 | ], 1678 | "time": "2020-06-01T12:21:09+00:00" 1679 | }, 1680 | { 1681 | "name": "sebastian/version", 1682 | "version": "3.0.0", 1683 | "source": { 1684 | "type": "git", 1685 | "url": "https://github.com/sebastianbergmann/version.git", 1686 | "reference": "0411bde656dce64202b39c2f4473993a9081d39e" 1687 | }, 1688 | "dist": { 1689 | "type": "zip", 1690 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/0411bde656dce64202b39c2f4473993a9081d39e", 1691 | "reference": "0411bde656dce64202b39c2f4473993a9081d39e", 1692 | "shasum": "" 1693 | }, 1694 | "require": { 1695 | "php": "^7.3" 1696 | }, 1697 | "type": "library", 1698 | "extra": { 1699 | "branch-alias": { 1700 | "dev-master": "3.0-dev" 1701 | } 1702 | }, 1703 | "autoload": { 1704 | "classmap": [ 1705 | "src/" 1706 | ] 1707 | }, 1708 | "notification-url": "https://packagist.org/downloads/", 1709 | "license": [ 1710 | "BSD-3-Clause" 1711 | ], 1712 | "authors": [ 1713 | { 1714 | "name": "Sebastian Bergmann", 1715 | "email": "sebastian@phpunit.de", 1716 | "role": "lead" 1717 | } 1718 | ], 1719 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1720 | "homepage": "https://github.com/sebastianbergmann/version", 1721 | "time": "2020-01-21T06:36:37+00:00" 1722 | }, 1723 | { 1724 | "name": "symfony/browser-kit", 1725 | "version": "v5.1.0", 1726 | "source": { 1727 | "type": "git", 1728 | "url": "https://github.com/symfony/browser-kit.git", 1729 | "reference": "b9adef763c4f98660d1f8b924f6d61718f8ae0bc" 1730 | }, 1731 | "dist": { 1732 | "type": "zip", 1733 | "url": "https://api.github.com/repos/symfony/browser-kit/zipball/b9adef763c4f98660d1f8b924f6d61718f8ae0bc", 1734 | "reference": "b9adef763c4f98660d1f8b924f6d61718f8ae0bc", 1735 | "shasum": "" 1736 | }, 1737 | "require": { 1738 | "php": ">=7.2.5", 1739 | "symfony/dom-crawler": "^4.4|^5.0" 1740 | }, 1741 | "require-dev": { 1742 | "symfony/css-selector": "^4.4|^5.0", 1743 | "symfony/http-client": "^4.4|^5.0", 1744 | "symfony/mime": "^4.4|^5.0", 1745 | "symfony/process": "^4.4|^5.0" 1746 | }, 1747 | "suggest": { 1748 | "symfony/process": "" 1749 | }, 1750 | "type": "library", 1751 | "extra": { 1752 | "branch-alias": { 1753 | "dev-master": "5.1-dev" 1754 | } 1755 | }, 1756 | "autoload": { 1757 | "psr-4": { 1758 | "Symfony\\Component\\BrowserKit\\": "" 1759 | }, 1760 | "exclude-from-classmap": [ 1761 | "/Tests/" 1762 | ] 1763 | }, 1764 | "notification-url": "https://packagist.org/downloads/", 1765 | "license": [ 1766 | "MIT" 1767 | ], 1768 | "authors": [ 1769 | { 1770 | "name": "Fabien Potencier", 1771 | "email": "fabien@symfony.com" 1772 | }, 1773 | { 1774 | "name": "Symfony Community", 1775 | "homepage": "https://symfony.com/contributors" 1776 | } 1777 | ], 1778 | "description": "Symfony BrowserKit Component", 1779 | "homepage": "https://symfony.com", 1780 | "funding": [ 1781 | { 1782 | "url": "https://symfony.com/sponsor", 1783 | "type": "custom" 1784 | }, 1785 | { 1786 | "url": "https://github.com/fabpot", 1787 | "type": "github" 1788 | }, 1789 | { 1790 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1791 | "type": "tidelift" 1792 | } 1793 | ], 1794 | "time": "2020-05-23T13:13:03+00:00" 1795 | }, 1796 | { 1797 | "name": "symfony/css-selector", 1798 | "version": "v5.1.0", 1799 | "source": { 1800 | "type": "git", 1801 | "url": "https://github.com/symfony/css-selector.git", 1802 | "reference": "e544e24472d4c97b2d11ade7caacd446727c6bf9" 1803 | }, 1804 | "dist": { 1805 | "type": "zip", 1806 | "url": "https://api.github.com/repos/symfony/css-selector/zipball/e544e24472d4c97b2d11ade7caacd446727c6bf9", 1807 | "reference": "e544e24472d4c97b2d11ade7caacd446727c6bf9", 1808 | "shasum": "" 1809 | }, 1810 | "require": { 1811 | "php": ">=7.2.5" 1812 | }, 1813 | "type": "library", 1814 | "extra": { 1815 | "branch-alias": { 1816 | "dev-master": "5.1-dev" 1817 | } 1818 | }, 1819 | "autoload": { 1820 | "psr-4": { 1821 | "Symfony\\Component\\CssSelector\\": "" 1822 | }, 1823 | "exclude-from-classmap": [ 1824 | "/Tests/" 1825 | ] 1826 | }, 1827 | "notification-url": "https://packagist.org/downloads/", 1828 | "license": [ 1829 | "MIT" 1830 | ], 1831 | "authors": [ 1832 | { 1833 | "name": "Fabien Potencier", 1834 | "email": "fabien@symfony.com" 1835 | }, 1836 | { 1837 | "name": "Jean-François Simon", 1838 | "email": "jeanfrancois.simon@sensiolabs.com" 1839 | }, 1840 | { 1841 | "name": "Symfony Community", 1842 | "homepage": "https://symfony.com/contributors" 1843 | } 1844 | ], 1845 | "description": "Symfony CssSelector Component", 1846 | "homepage": "https://symfony.com", 1847 | "funding": [ 1848 | { 1849 | "url": "https://symfony.com/sponsor", 1850 | "type": "custom" 1851 | }, 1852 | { 1853 | "url": "https://github.com/fabpot", 1854 | "type": "github" 1855 | }, 1856 | { 1857 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1858 | "type": "tidelift" 1859 | } 1860 | ], 1861 | "time": "2020-05-20T17:43:50+00:00" 1862 | }, 1863 | { 1864 | "name": "symfony/dom-crawler", 1865 | "version": "v5.1.0", 1866 | "source": { 1867 | "type": "git", 1868 | "url": "https://github.com/symfony/dom-crawler.git", 1869 | "reference": "907187782c465a564f9030a0c6ace59e8821106f" 1870 | }, 1871 | "dist": { 1872 | "type": "zip", 1873 | "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/907187782c465a564f9030a0c6ace59e8821106f", 1874 | "reference": "907187782c465a564f9030a0c6ace59e8821106f", 1875 | "shasum": "" 1876 | }, 1877 | "require": { 1878 | "php": ">=7.2.5", 1879 | "symfony/polyfill-ctype": "~1.8", 1880 | "symfony/polyfill-mbstring": "~1.0", 1881 | "symfony/polyfill-php80": "^1.15" 1882 | }, 1883 | "conflict": { 1884 | "masterminds/html5": "<2.6" 1885 | }, 1886 | "require-dev": { 1887 | "masterminds/html5": "^2.6", 1888 | "symfony/css-selector": "^4.4|^5.0" 1889 | }, 1890 | "suggest": { 1891 | "symfony/css-selector": "" 1892 | }, 1893 | "type": "library", 1894 | "extra": { 1895 | "branch-alias": { 1896 | "dev-master": "5.1-dev" 1897 | } 1898 | }, 1899 | "autoload": { 1900 | "psr-4": { 1901 | "Symfony\\Component\\DomCrawler\\": "" 1902 | }, 1903 | "exclude-from-classmap": [ 1904 | "/Tests/" 1905 | ] 1906 | }, 1907 | "notification-url": "https://packagist.org/downloads/", 1908 | "license": [ 1909 | "MIT" 1910 | ], 1911 | "authors": [ 1912 | { 1913 | "name": "Fabien Potencier", 1914 | "email": "fabien@symfony.com" 1915 | }, 1916 | { 1917 | "name": "Symfony Community", 1918 | "homepage": "https://symfony.com/contributors" 1919 | } 1920 | ], 1921 | "description": "Symfony DomCrawler Component", 1922 | "homepage": "https://symfony.com", 1923 | "funding": [ 1924 | { 1925 | "url": "https://symfony.com/sponsor", 1926 | "type": "custom" 1927 | }, 1928 | { 1929 | "url": "https://github.com/fabpot", 1930 | "type": "github" 1931 | }, 1932 | { 1933 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1934 | "type": "tidelift" 1935 | } 1936 | ], 1937 | "time": "2020-05-23T13:08:13+00:00" 1938 | }, 1939 | { 1940 | "name": "symfony/http-client", 1941 | "version": "v5.1.0", 1942 | "source": { 1943 | "type": "git", 1944 | "url": "https://github.com/symfony/http-client.git", 1945 | "reference": "63342eabdc6fc6c12e6b18506a207d16687aa33f" 1946 | }, 1947 | "dist": { 1948 | "type": "zip", 1949 | "url": "https://api.github.com/repos/symfony/http-client/zipball/63342eabdc6fc6c12e6b18506a207d16687aa33f", 1950 | "reference": "63342eabdc6fc6c12e6b18506a207d16687aa33f", 1951 | "shasum": "" 1952 | }, 1953 | "require": { 1954 | "php": ">=7.2.5", 1955 | "psr/log": "^1.0", 1956 | "symfony/http-client-contracts": "^2.1.1", 1957 | "symfony/polyfill-php73": "^1.11", 1958 | "symfony/polyfill-php80": "^1.15", 1959 | "symfony/service-contracts": "^1.0|^2" 1960 | }, 1961 | "provide": { 1962 | "php-http/async-client-implementation": "*", 1963 | "php-http/client-implementation": "*", 1964 | "psr/http-client-implementation": "1.0", 1965 | "symfony/http-client-implementation": "1.1" 1966 | }, 1967 | "require-dev": { 1968 | "amphp/http-client": "^4.2.1", 1969 | "amphp/http-tunnel": "^1.0", 1970 | "amphp/socket": "^1.1", 1971 | "guzzlehttp/promises": "^1.3.1", 1972 | "nyholm/psr7": "^1.0", 1973 | "php-http/httplug": "^1.0|^2.0", 1974 | "psr/http-client": "^1.0", 1975 | "symfony/dependency-injection": "^4.4|^5.0", 1976 | "symfony/http-kernel": "^4.4|^5.0", 1977 | "symfony/process": "^4.4|^5.0" 1978 | }, 1979 | "type": "library", 1980 | "extra": { 1981 | "branch-alias": { 1982 | "dev-master": "5.1-dev" 1983 | } 1984 | }, 1985 | "autoload": { 1986 | "psr-4": { 1987 | "Symfony\\Component\\HttpClient\\": "" 1988 | }, 1989 | "exclude-from-classmap": [ 1990 | "/Tests/" 1991 | ] 1992 | }, 1993 | "notification-url": "https://packagist.org/downloads/", 1994 | "license": [ 1995 | "MIT" 1996 | ], 1997 | "authors": [ 1998 | { 1999 | "name": "Nicolas Grekas", 2000 | "email": "p@tchwork.com" 2001 | }, 2002 | { 2003 | "name": "Symfony Community", 2004 | "homepage": "https://symfony.com/contributors" 2005 | } 2006 | ], 2007 | "description": "Symfony HttpClient component", 2008 | "homepage": "https://symfony.com", 2009 | "funding": [ 2010 | { 2011 | "url": "https://symfony.com/sponsor", 2012 | "type": "custom" 2013 | }, 2014 | { 2015 | "url": "https://github.com/fabpot", 2016 | "type": "github" 2017 | }, 2018 | { 2019 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2020 | "type": "tidelift" 2021 | } 2022 | ], 2023 | "time": "2020-05-30T21:52:37+00:00" 2024 | }, 2025 | { 2026 | "name": "symfony/http-client-contracts", 2027 | "version": "v2.1.2", 2028 | "source": { 2029 | "type": "git", 2030 | "url": "https://github.com/symfony/http-client-contracts.git", 2031 | "reference": "f8bed25edc964d015bcd87f1fec5734963931910" 2032 | }, 2033 | "dist": { 2034 | "type": "zip", 2035 | "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/f8bed25edc964d015bcd87f1fec5734963931910", 2036 | "reference": "f8bed25edc964d015bcd87f1fec5734963931910", 2037 | "shasum": "" 2038 | }, 2039 | "require": { 2040 | "php": ">=7.2.5" 2041 | }, 2042 | "suggest": { 2043 | "symfony/http-client-implementation": "" 2044 | }, 2045 | "type": "library", 2046 | "extra": { 2047 | "branch-alias": { 2048 | "dev-master": "2.1-dev" 2049 | } 2050 | }, 2051 | "autoload": { 2052 | "psr-4": { 2053 | "Symfony\\Contracts\\HttpClient\\": "" 2054 | } 2055 | }, 2056 | "notification-url": "https://packagist.org/downloads/", 2057 | "license": [ 2058 | "MIT" 2059 | ], 2060 | "authors": [ 2061 | { 2062 | "name": "Nicolas Grekas", 2063 | "email": "p@tchwork.com" 2064 | }, 2065 | { 2066 | "name": "Symfony Community", 2067 | "homepage": "https://symfony.com/contributors" 2068 | } 2069 | ], 2070 | "description": "Generic abstractions related to HTTP clients", 2071 | "homepage": "https://symfony.com", 2072 | "keywords": [ 2073 | "abstractions", 2074 | "contracts", 2075 | "decoupling", 2076 | "interfaces", 2077 | "interoperability", 2078 | "standards" 2079 | ], 2080 | "funding": [ 2081 | { 2082 | "url": "https://symfony.com/sponsor", 2083 | "type": "custom" 2084 | }, 2085 | { 2086 | "url": "https://github.com/fabpot", 2087 | "type": "github" 2088 | }, 2089 | { 2090 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2091 | "type": "tidelift" 2092 | } 2093 | ], 2094 | "time": "2020-05-25T17:37:45+00:00" 2095 | }, 2096 | { 2097 | "name": "symfony/mime", 2098 | "version": "v5.1.0", 2099 | "source": { 2100 | "type": "git", 2101 | "url": "https://github.com/symfony/mime.git", 2102 | "reference": "56261f89385f9d13cf843a5101ac72131190bc91" 2103 | }, 2104 | "dist": { 2105 | "type": "zip", 2106 | "url": "https://api.github.com/repos/symfony/mime/zipball/56261f89385f9d13cf843a5101ac72131190bc91", 2107 | "reference": "56261f89385f9d13cf843a5101ac72131190bc91", 2108 | "shasum": "" 2109 | }, 2110 | "require": { 2111 | "php": ">=7.2.5", 2112 | "symfony/polyfill-intl-idn": "^1.10", 2113 | "symfony/polyfill-mbstring": "^1.0", 2114 | "symfony/polyfill-php80": "^1.15" 2115 | }, 2116 | "conflict": { 2117 | "symfony/mailer": "<4.4" 2118 | }, 2119 | "require-dev": { 2120 | "egulias/email-validator": "^2.1.10", 2121 | "symfony/dependency-injection": "^4.4|^5.0" 2122 | }, 2123 | "type": "library", 2124 | "extra": { 2125 | "branch-alias": { 2126 | "dev-master": "5.1-dev" 2127 | } 2128 | }, 2129 | "autoload": { 2130 | "psr-4": { 2131 | "Symfony\\Component\\Mime\\": "" 2132 | }, 2133 | "exclude-from-classmap": [ 2134 | "/Tests/" 2135 | ] 2136 | }, 2137 | "notification-url": "https://packagist.org/downloads/", 2138 | "license": [ 2139 | "MIT" 2140 | ], 2141 | "authors": [ 2142 | { 2143 | "name": "Fabien Potencier", 2144 | "email": "fabien@symfony.com" 2145 | }, 2146 | { 2147 | "name": "Symfony Community", 2148 | "homepage": "https://symfony.com/contributors" 2149 | } 2150 | ], 2151 | "description": "A library to manipulate MIME messages", 2152 | "homepage": "https://symfony.com", 2153 | "keywords": [ 2154 | "mime", 2155 | "mime-type" 2156 | ], 2157 | "funding": [ 2158 | { 2159 | "url": "https://symfony.com/sponsor", 2160 | "type": "custom" 2161 | }, 2162 | { 2163 | "url": "https://github.com/fabpot", 2164 | "type": "github" 2165 | }, 2166 | { 2167 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2168 | "type": "tidelift" 2169 | } 2170 | ], 2171 | "time": "2020-05-25T12:33:44+00:00" 2172 | }, 2173 | { 2174 | "name": "symfony/panther", 2175 | "version": "v0.7.1", 2176 | "source": { 2177 | "type": "git", 2178 | "url": "https://github.com/symfony/panther.git", 2179 | "reference": "fcc7cfd2168db167daeb21ac5510fe06f2816a83" 2180 | }, 2181 | "dist": { 2182 | "type": "zip", 2183 | "url": "https://api.github.com/repos/symfony/panther/zipball/fcc7cfd2168db167daeb21ac5510fe06f2816a83", 2184 | "reference": "fcc7cfd2168db167daeb21ac5510fe06f2816a83", 2185 | "shasum": "" 2186 | }, 2187 | "require": { 2188 | "php": ">=7.1", 2189 | "php-webdriver/webdriver": "^1.8.1", 2190 | "symfony/browser-kit": "^4.3 || ^5.0", 2191 | "symfony/dom-crawler": "^4.3 || ^5.0", 2192 | "symfony/http-client": "^4.3 || ^5.0", 2193 | "symfony/polyfill-php72": "^1.9", 2194 | "symfony/process": "^3.4 || ^4.0 || ^5.0" 2195 | }, 2196 | "conflict": { 2197 | "symfony/framework-bundle": ">=4.3 <4.3.4" 2198 | }, 2199 | "require-dev": { 2200 | "fabpot/goutte": "^3.2.3", 2201 | "guzzlehttp/guzzle": "^6.3", 2202 | "symfony/css-selector": "^3.4 || ^4.0 || ^5.0", 2203 | "symfony/framework-bundle": "^3.4 || ^4.0 || ^5.0", 2204 | "symfony/mime": "^4.3 || ^5.0", 2205 | "symfony/phpunit-bridge": "^4.3.3 || ^5.0" 2206 | }, 2207 | "type": "library", 2208 | "extra": { 2209 | "branch-alias": { 2210 | "dev-master": "1.0.x-dev" 2211 | } 2212 | }, 2213 | "autoload": { 2214 | "psr-4": { 2215 | "Symfony\\Component\\Panther\\": "src/" 2216 | } 2217 | }, 2218 | "notification-url": "https://packagist.org/downloads/", 2219 | "license": [ 2220 | "MIT" 2221 | ], 2222 | "authors": [ 2223 | { 2224 | "name": "Kévin Dunglas", 2225 | "email": "dunglas@gmail.com", 2226 | "homepage": "https://dunglas.fr" 2227 | }, 2228 | { 2229 | "name": "Symfony Community", 2230 | "homepage": "https://symfony.com/contributors" 2231 | } 2232 | ], 2233 | "description": "A browser testing and web scraping library for PHP and Symfony.", 2234 | "homepage": "https://dunglas.fr", 2235 | "keywords": [ 2236 | "e2e", 2237 | "scraping", 2238 | "selenium", 2239 | "symfony", 2240 | "testing", 2241 | "webdriver" 2242 | ], 2243 | "time": "2020-02-24T12:05:49+00:00" 2244 | }, 2245 | { 2246 | "name": "symfony/polyfill-ctype", 2247 | "version": "v1.17.0", 2248 | "source": { 2249 | "type": "git", 2250 | "url": "https://github.com/symfony/polyfill-ctype.git", 2251 | "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" 2252 | }, 2253 | "dist": { 2254 | "type": "zip", 2255 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", 2256 | "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", 2257 | "shasum": "" 2258 | }, 2259 | "require": { 2260 | "php": ">=5.3.3" 2261 | }, 2262 | "suggest": { 2263 | "ext-ctype": "For best performance" 2264 | }, 2265 | "type": "library", 2266 | "extra": { 2267 | "branch-alias": { 2268 | "dev-master": "1.17-dev" 2269 | } 2270 | }, 2271 | "autoload": { 2272 | "psr-4": { 2273 | "Symfony\\Polyfill\\Ctype\\": "" 2274 | }, 2275 | "files": [ 2276 | "bootstrap.php" 2277 | ] 2278 | }, 2279 | "notification-url": "https://packagist.org/downloads/", 2280 | "license": [ 2281 | "MIT" 2282 | ], 2283 | "authors": [ 2284 | { 2285 | "name": "Gert de Pagter", 2286 | "email": "BackEndTea@gmail.com" 2287 | }, 2288 | { 2289 | "name": "Symfony Community", 2290 | "homepage": "https://symfony.com/contributors" 2291 | } 2292 | ], 2293 | "description": "Symfony polyfill for ctype functions", 2294 | "homepage": "https://symfony.com", 2295 | "keywords": [ 2296 | "compatibility", 2297 | "ctype", 2298 | "polyfill", 2299 | "portable" 2300 | ], 2301 | "funding": [ 2302 | { 2303 | "url": "https://symfony.com/sponsor", 2304 | "type": "custom" 2305 | }, 2306 | { 2307 | "url": "https://github.com/fabpot", 2308 | "type": "github" 2309 | }, 2310 | { 2311 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2312 | "type": "tidelift" 2313 | } 2314 | ], 2315 | "time": "2020-05-12T16:14:59+00:00" 2316 | }, 2317 | { 2318 | "name": "symfony/polyfill-intl-idn", 2319 | "version": "v1.17.0", 2320 | "source": { 2321 | "type": "git", 2322 | "url": "https://github.com/symfony/polyfill-intl-idn.git", 2323 | "reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a" 2324 | }, 2325 | "dist": { 2326 | "type": "zip", 2327 | "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/3bff59ea7047e925be6b7f2059d60af31bb46d6a", 2328 | "reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a", 2329 | "shasum": "" 2330 | }, 2331 | "require": { 2332 | "php": ">=5.3.3", 2333 | "symfony/polyfill-mbstring": "^1.3", 2334 | "symfony/polyfill-php72": "^1.10" 2335 | }, 2336 | "suggest": { 2337 | "ext-intl": "For best performance" 2338 | }, 2339 | "type": "library", 2340 | "extra": { 2341 | "branch-alias": { 2342 | "dev-master": "1.17-dev" 2343 | } 2344 | }, 2345 | "autoload": { 2346 | "psr-4": { 2347 | "Symfony\\Polyfill\\Intl\\Idn\\": "" 2348 | }, 2349 | "files": [ 2350 | "bootstrap.php" 2351 | ] 2352 | }, 2353 | "notification-url": "https://packagist.org/downloads/", 2354 | "license": [ 2355 | "MIT" 2356 | ], 2357 | "authors": [ 2358 | { 2359 | "name": "Laurent Bassin", 2360 | "email": "laurent@bassin.info" 2361 | }, 2362 | { 2363 | "name": "Symfony Community", 2364 | "homepage": "https://symfony.com/contributors" 2365 | } 2366 | ], 2367 | "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", 2368 | "homepage": "https://symfony.com", 2369 | "keywords": [ 2370 | "compatibility", 2371 | "idn", 2372 | "intl", 2373 | "polyfill", 2374 | "portable", 2375 | "shim" 2376 | ], 2377 | "funding": [ 2378 | { 2379 | "url": "https://symfony.com/sponsor", 2380 | "type": "custom" 2381 | }, 2382 | { 2383 | "url": "https://github.com/fabpot", 2384 | "type": "github" 2385 | }, 2386 | { 2387 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2388 | "type": "tidelift" 2389 | } 2390 | ], 2391 | "time": "2020-05-12T16:47:27+00:00" 2392 | }, 2393 | { 2394 | "name": "symfony/polyfill-mbstring", 2395 | "version": "v1.17.0", 2396 | "source": { 2397 | "type": "git", 2398 | "url": "https://github.com/symfony/polyfill-mbstring.git", 2399 | "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" 2400 | }, 2401 | "dist": { 2402 | "type": "zip", 2403 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", 2404 | "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", 2405 | "shasum": "" 2406 | }, 2407 | "require": { 2408 | "php": ">=5.3.3" 2409 | }, 2410 | "suggest": { 2411 | "ext-mbstring": "For best performance" 2412 | }, 2413 | "type": "library", 2414 | "extra": { 2415 | "branch-alias": { 2416 | "dev-master": "1.17-dev" 2417 | } 2418 | }, 2419 | "autoload": { 2420 | "psr-4": { 2421 | "Symfony\\Polyfill\\Mbstring\\": "" 2422 | }, 2423 | "files": [ 2424 | "bootstrap.php" 2425 | ] 2426 | }, 2427 | "notification-url": "https://packagist.org/downloads/", 2428 | "license": [ 2429 | "MIT" 2430 | ], 2431 | "authors": [ 2432 | { 2433 | "name": "Nicolas Grekas", 2434 | "email": "p@tchwork.com" 2435 | }, 2436 | { 2437 | "name": "Symfony Community", 2438 | "homepage": "https://symfony.com/contributors" 2439 | } 2440 | ], 2441 | "description": "Symfony polyfill for the Mbstring extension", 2442 | "homepage": "https://symfony.com", 2443 | "keywords": [ 2444 | "compatibility", 2445 | "mbstring", 2446 | "polyfill", 2447 | "portable", 2448 | "shim" 2449 | ], 2450 | "funding": [ 2451 | { 2452 | "url": "https://symfony.com/sponsor", 2453 | "type": "custom" 2454 | }, 2455 | { 2456 | "url": "https://github.com/fabpot", 2457 | "type": "github" 2458 | }, 2459 | { 2460 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2461 | "type": "tidelift" 2462 | } 2463 | ], 2464 | "time": "2020-05-12T16:47:27+00:00" 2465 | }, 2466 | { 2467 | "name": "symfony/polyfill-php72", 2468 | "version": "v1.17.0", 2469 | "source": { 2470 | "type": "git", 2471 | "url": "https://github.com/symfony/polyfill-php72.git", 2472 | "reference": "f048e612a3905f34931127360bdd2def19a5e582" 2473 | }, 2474 | "dist": { 2475 | "type": "zip", 2476 | "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", 2477 | "reference": "f048e612a3905f34931127360bdd2def19a5e582", 2478 | "shasum": "" 2479 | }, 2480 | "require": { 2481 | "php": ">=5.3.3" 2482 | }, 2483 | "type": "library", 2484 | "extra": { 2485 | "branch-alias": { 2486 | "dev-master": "1.17-dev" 2487 | } 2488 | }, 2489 | "autoload": { 2490 | "psr-4": { 2491 | "Symfony\\Polyfill\\Php72\\": "" 2492 | }, 2493 | "files": [ 2494 | "bootstrap.php" 2495 | ] 2496 | }, 2497 | "notification-url": "https://packagist.org/downloads/", 2498 | "license": [ 2499 | "MIT" 2500 | ], 2501 | "authors": [ 2502 | { 2503 | "name": "Nicolas Grekas", 2504 | "email": "p@tchwork.com" 2505 | }, 2506 | { 2507 | "name": "Symfony Community", 2508 | "homepage": "https://symfony.com/contributors" 2509 | } 2510 | ], 2511 | "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", 2512 | "homepage": "https://symfony.com", 2513 | "keywords": [ 2514 | "compatibility", 2515 | "polyfill", 2516 | "portable", 2517 | "shim" 2518 | ], 2519 | "funding": [ 2520 | { 2521 | "url": "https://symfony.com/sponsor", 2522 | "type": "custom" 2523 | }, 2524 | { 2525 | "url": "https://github.com/fabpot", 2526 | "type": "github" 2527 | }, 2528 | { 2529 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2530 | "type": "tidelift" 2531 | } 2532 | ], 2533 | "time": "2020-05-12T16:47:27+00:00" 2534 | }, 2535 | { 2536 | "name": "symfony/polyfill-php73", 2537 | "version": "v1.17.0", 2538 | "source": { 2539 | "type": "git", 2540 | "url": "https://github.com/symfony/polyfill-php73.git", 2541 | "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc" 2542 | }, 2543 | "dist": { 2544 | "type": "zip", 2545 | "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc", 2546 | "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc", 2547 | "shasum": "" 2548 | }, 2549 | "require": { 2550 | "php": ">=5.3.3" 2551 | }, 2552 | "type": "library", 2553 | "extra": { 2554 | "branch-alias": { 2555 | "dev-master": "1.17-dev" 2556 | } 2557 | }, 2558 | "autoload": { 2559 | "psr-4": { 2560 | "Symfony\\Polyfill\\Php73\\": "" 2561 | }, 2562 | "files": [ 2563 | "bootstrap.php" 2564 | ], 2565 | "classmap": [ 2566 | "Resources/stubs" 2567 | ] 2568 | }, 2569 | "notification-url": "https://packagist.org/downloads/", 2570 | "license": [ 2571 | "MIT" 2572 | ], 2573 | "authors": [ 2574 | { 2575 | "name": "Nicolas Grekas", 2576 | "email": "p@tchwork.com" 2577 | }, 2578 | { 2579 | "name": "Symfony Community", 2580 | "homepage": "https://symfony.com/contributors" 2581 | } 2582 | ], 2583 | "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", 2584 | "homepage": "https://symfony.com", 2585 | "keywords": [ 2586 | "compatibility", 2587 | "polyfill", 2588 | "portable", 2589 | "shim" 2590 | ], 2591 | "funding": [ 2592 | { 2593 | "url": "https://symfony.com/sponsor", 2594 | "type": "custom" 2595 | }, 2596 | { 2597 | "url": "https://github.com/fabpot", 2598 | "type": "github" 2599 | }, 2600 | { 2601 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2602 | "type": "tidelift" 2603 | } 2604 | ], 2605 | "time": "2020-05-12T16:47:27+00:00" 2606 | }, 2607 | { 2608 | "name": "symfony/polyfill-php80", 2609 | "version": "v1.17.0", 2610 | "source": { 2611 | "type": "git", 2612 | "url": "https://github.com/symfony/polyfill-php80.git", 2613 | "reference": "5e30b2799bc1ad68f7feb62b60a73743589438dd" 2614 | }, 2615 | "dist": { 2616 | "type": "zip", 2617 | "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/5e30b2799bc1ad68f7feb62b60a73743589438dd", 2618 | "reference": "5e30b2799bc1ad68f7feb62b60a73743589438dd", 2619 | "shasum": "" 2620 | }, 2621 | "require": { 2622 | "php": ">=7.0.8" 2623 | }, 2624 | "type": "library", 2625 | "extra": { 2626 | "branch-alias": { 2627 | "dev-master": "1.17-dev" 2628 | } 2629 | }, 2630 | "autoload": { 2631 | "psr-4": { 2632 | "Symfony\\Polyfill\\Php80\\": "" 2633 | }, 2634 | "files": [ 2635 | "bootstrap.php" 2636 | ], 2637 | "classmap": [ 2638 | "Resources/stubs" 2639 | ] 2640 | }, 2641 | "notification-url": "https://packagist.org/downloads/", 2642 | "license": [ 2643 | "MIT" 2644 | ], 2645 | "authors": [ 2646 | { 2647 | "name": "Ion Bazan", 2648 | "email": "ion.bazan@gmail.com" 2649 | }, 2650 | { 2651 | "name": "Nicolas Grekas", 2652 | "email": "p@tchwork.com" 2653 | }, 2654 | { 2655 | "name": "Symfony Community", 2656 | "homepage": "https://symfony.com/contributors" 2657 | } 2658 | ], 2659 | "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", 2660 | "homepage": "https://symfony.com", 2661 | "keywords": [ 2662 | "compatibility", 2663 | "polyfill", 2664 | "portable", 2665 | "shim" 2666 | ], 2667 | "funding": [ 2668 | { 2669 | "url": "https://symfony.com/sponsor", 2670 | "type": "custom" 2671 | }, 2672 | { 2673 | "url": "https://github.com/fabpot", 2674 | "type": "github" 2675 | }, 2676 | { 2677 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2678 | "type": "tidelift" 2679 | } 2680 | ], 2681 | "time": "2020-05-12T16:47:27+00:00" 2682 | }, 2683 | { 2684 | "name": "symfony/process", 2685 | "version": "v5.1.0", 2686 | "source": { 2687 | "type": "git", 2688 | "url": "https://github.com/symfony/process.git", 2689 | "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1" 2690 | }, 2691 | "dist": { 2692 | "type": "zip", 2693 | "url": "https://api.github.com/repos/symfony/process/zipball/7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", 2694 | "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", 2695 | "shasum": "" 2696 | }, 2697 | "require": { 2698 | "php": ">=7.2.5", 2699 | "symfony/polyfill-php80": "^1.15" 2700 | }, 2701 | "type": "library", 2702 | "extra": { 2703 | "branch-alias": { 2704 | "dev-master": "5.1-dev" 2705 | } 2706 | }, 2707 | "autoload": { 2708 | "psr-4": { 2709 | "Symfony\\Component\\Process\\": "" 2710 | }, 2711 | "exclude-from-classmap": [ 2712 | "/Tests/" 2713 | ] 2714 | }, 2715 | "notification-url": "https://packagist.org/downloads/", 2716 | "license": [ 2717 | "MIT" 2718 | ], 2719 | "authors": [ 2720 | { 2721 | "name": "Fabien Potencier", 2722 | "email": "fabien@symfony.com" 2723 | }, 2724 | { 2725 | "name": "Symfony Community", 2726 | "homepage": "https://symfony.com/contributors" 2727 | } 2728 | ], 2729 | "description": "Symfony Process Component", 2730 | "homepage": "https://symfony.com", 2731 | "funding": [ 2732 | { 2733 | "url": "https://symfony.com/sponsor", 2734 | "type": "custom" 2735 | }, 2736 | { 2737 | "url": "https://github.com/fabpot", 2738 | "type": "github" 2739 | }, 2740 | { 2741 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2742 | "type": "tidelift" 2743 | } 2744 | ], 2745 | "time": "2020-05-30T20:35:19+00:00" 2746 | }, 2747 | { 2748 | "name": "symfony/service-contracts", 2749 | "version": "v2.1.2", 2750 | "source": { 2751 | "type": "git", 2752 | "url": "https://github.com/symfony/service-contracts.git", 2753 | "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b" 2754 | }, 2755 | "dist": { 2756 | "type": "zip", 2757 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/66a8f0957a3ca54e4f724e49028ab19d75a8918b", 2758 | "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b", 2759 | "shasum": "" 2760 | }, 2761 | "require": { 2762 | "php": ">=7.2.5", 2763 | "psr/container": "^1.0" 2764 | }, 2765 | "suggest": { 2766 | "symfony/service-implementation": "" 2767 | }, 2768 | "type": "library", 2769 | "extra": { 2770 | "branch-alias": { 2771 | "dev-master": "2.1-dev" 2772 | } 2773 | }, 2774 | "autoload": { 2775 | "psr-4": { 2776 | "Symfony\\Contracts\\Service\\": "" 2777 | } 2778 | }, 2779 | "notification-url": "https://packagist.org/downloads/", 2780 | "license": [ 2781 | "MIT" 2782 | ], 2783 | "authors": [ 2784 | { 2785 | "name": "Nicolas Grekas", 2786 | "email": "p@tchwork.com" 2787 | }, 2788 | { 2789 | "name": "Symfony Community", 2790 | "homepage": "https://symfony.com/contributors" 2791 | } 2792 | ], 2793 | "description": "Generic abstractions related to writing services", 2794 | "homepage": "https://symfony.com", 2795 | "keywords": [ 2796 | "abstractions", 2797 | "contracts", 2798 | "decoupling", 2799 | "interfaces", 2800 | "interoperability", 2801 | "standards" 2802 | ], 2803 | "funding": [ 2804 | { 2805 | "url": "https://symfony.com/sponsor", 2806 | "type": "custom" 2807 | }, 2808 | { 2809 | "url": "https://github.com/fabpot", 2810 | "type": "github" 2811 | }, 2812 | { 2813 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2814 | "type": "tidelift" 2815 | } 2816 | ], 2817 | "time": "2020-05-20T17:43:50+00:00" 2818 | }, 2819 | { 2820 | "name": "theseer/tokenizer", 2821 | "version": "1.1.3", 2822 | "source": { 2823 | "type": "git", 2824 | "url": "https://github.com/theseer/tokenizer.git", 2825 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" 2826 | }, 2827 | "dist": { 2828 | "type": "zip", 2829 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 2830 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 2831 | "shasum": "" 2832 | }, 2833 | "require": { 2834 | "ext-dom": "*", 2835 | "ext-tokenizer": "*", 2836 | "ext-xmlwriter": "*", 2837 | "php": "^7.0" 2838 | }, 2839 | "type": "library", 2840 | "autoload": { 2841 | "classmap": [ 2842 | "src/" 2843 | ] 2844 | }, 2845 | "notification-url": "https://packagist.org/downloads/", 2846 | "license": [ 2847 | "BSD-3-Clause" 2848 | ], 2849 | "authors": [ 2850 | { 2851 | "name": "Arne Blankerts", 2852 | "email": "arne@blankerts.de", 2853 | "role": "Developer" 2854 | } 2855 | ], 2856 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2857 | "time": "2019-06-13T22:48:21+00:00" 2858 | }, 2859 | { 2860 | "name": "webmozart/assert", 2861 | "version": "1.8.0", 2862 | "source": { 2863 | "type": "git", 2864 | "url": "https://github.com/webmozart/assert.git", 2865 | "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" 2866 | }, 2867 | "dist": { 2868 | "type": "zip", 2869 | "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", 2870 | "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", 2871 | "shasum": "" 2872 | }, 2873 | "require": { 2874 | "php": "^5.3.3 || ^7.0", 2875 | "symfony/polyfill-ctype": "^1.8" 2876 | }, 2877 | "conflict": { 2878 | "vimeo/psalm": "<3.9.1" 2879 | }, 2880 | "require-dev": { 2881 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" 2882 | }, 2883 | "type": "library", 2884 | "autoload": { 2885 | "psr-4": { 2886 | "Webmozart\\Assert\\": "src/" 2887 | } 2888 | }, 2889 | "notification-url": "https://packagist.org/downloads/", 2890 | "license": [ 2891 | "MIT" 2892 | ], 2893 | "authors": [ 2894 | { 2895 | "name": "Bernhard Schussek", 2896 | "email": "bschussek@gmail.com" 2897 | } 2898 | ], 2899 | "description": "Assertions to validate method input/output with nice error messages.", 2900 | "keywords": [ 2901 | "assert", 2902 | "check", 2903 | "validate" 2904 | ], 2905 | "time": "2020-04-18T12:12:48+00:00" 2906 | } 2907 | ], 2908 | "aliases": [], 2909 | "minimum-stability": "stable", 2910 | "stability-flags": [], 2911 | "prefer-stable": false, 2912 | "prefer-lowest": false, 2913 | "platform": [], 2914 | "platform-dev": [], 2915 | "plugin-api-version": "1.1.0" 2916 | } 2917 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasnoback/php-for-the-web/86e62c951f2cbb599f998cff82b702d4c15df904/data/.gitkeep -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | composer: 5 | image: composer:latest 6 | volumes: 7 | - ./:/app 8 | working_dir: /app 9 | 10 | php: 11 | image: php:7.4-cli-alpine 12 | volumes: 13 | - ./:/app 14 | working_dir: /app 15 | 16 | webserver: 17 | image: matthiasnoback/php-for-the-web-webserver 18 | build: docker/webserver/ 19 | volumes: 20 | - ./:/app 21 | ports: 22 | - "8000:8000" 23 | 24 | challenge_chapter_04: 25 | image: matthiasnoback/php-for-the-web-webserver 26 | volumes: 27 | - ./:/app 28 | ports: 29 | - "8001:8000" 30 | command: challenge_chapter_04/public/ 31 | 32 | challenge_chapter_07: 33 | image: matthiasnoback/php-for-the-web-webserver 34 | volumes: 35 | - ./:/app 36 | ports: 37 | - "8002:8000" 38 | command: challenge_chapter_07/public/ 39 | 40 | challenge_chapter_08: 41 | image: matthiasnoback/php-for-the-web-webserver 42 | volumes: 43 | - ./:/app 44 | ports: 45 | - "8003:8000" 46 | command: challenge_chapter_08/public/ 47 | 48 | challenge_chapter_09: 49 | image: matthiasnoback/php-for-the-web-webserver 50 | volumes: 51 | - ./:/app 52 | ports: 53 | - "8004:8000" 54 | command: challenge_chapter_09/public/ 55 | -------------------------------------------------------------------------------- /docker/webserver/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.4-cli-alpine 2 | EXPOSE 8000 3 | WORKDIR /app 4 | STOPSIGNAL SIGTERM 5 | COPY php.ini $PHP_INI_DIR/conf.d/ 6 | ENTRYPOINT [ "php", "-S", "0.0.0.0:8000", "-t"] 7 | CMD ["public/"] 8 | -------------------------------------------------------------------------------- /docker/webserver/php.ini: -------------------------------------------------------------------------------- 1 | ; Show errors in the server log 2 | log_errors = On 3 | 4 | ; Show errors in the response body 5 | display_errors = On 6 | 7 | ; -1 means report all PHP errors 8 | error_reporting = -1 9 | 10 | ; Allow large files to be uploaded 11 | upload_max_filesize = 10M 12 | -------------------------------------------------------------------------------- /pages/404.php: -------------------------------------------------------------------------------- 1 | 5 |

Page not found

6 |

Sorry, we could not find the page you requested.

7 | 35 |

Go back to the list

36 |

Create a new tour

37 | 34 |

Go back to the list

35 |

Edit tour

36 | 9 |

An error occurred

10 |
6 | isset($submittedData['destination']) 7 | ? (string)$submittedData['destination'] 8 | : '', 9 | 'number_of_tickets_available' => 10 | isset($submittedData['number_of_tickets_available']) 11 | ? (int)$submittedData['number_of_tickets_available'] 12 | : 0, 13 | 'is_accessible' => 14 | isset($submittedData['is_accessible']) 15 | ? true 16 | : false 17 | ]; 18 | 19 | if ( 20 | isset($files['picture']['error']) 21 | && $files['picture']['error'] === UPLOAD_ERR_OK 22 | ) { 23 | $normalizedData['picture'] = $files['picture']['tmp_name']; 24 | } 25 | 26 | return $normalizedData; 27 | } 28 | 29 | function validate_normalized_data(array $normalizedData): array 30 | { 31 | $formErrors = []; 32 | 33 | if ($normalizedData['destination'] === '') { 34 | $formErrors['destination'] = 'Please provide a destination'; 35 | } 36 | 37 | if ($normalizedData['number_of_tickets_available'] < 1) { 38 | $formErrors['number_of_tickets_available'] = 39 | 'Number of tickets available should be at least 1'; 40 | } 41 | 42 | if (!isset($normalizedData['picture'])) { 43 | $formErrors['picture'] = 'Please upload a picture'; 44 | } elseif (is_uploaded_file($normalizedData['picture'])) { 45 | $finfo = new finfo(FILEINFO_MIME_TYPE); 46 | $mimeType = $finfo->file($normalizedData['picture']); 47 | if ($mimeType !== 'image/jpeg') { 48 | $formErrors['picture'] = 'Please provide a JPG image'; 49 | } 50 | } 51 | 52 | return $formErrors; 53 | } 54 | 55 | function load_all_tours_data(): array 56 | { 57 | $toursJsonFile = __DIR__ . '/../../data/tours.json'; 58 | if (!file_exists($toursJsonFile)) { 59 | return []; 60 | } 61 | 62 | $jsonData = file_get_contents($toursJsonFile); 63 | 64 | return json_decode($jsonData, true); 65 | } 66 | 67 | function save_all_tours(array $toursData): void 68 | { 69 | $toursJsonFile = __DIR__ . '/../../data/tours.json'; 70 | 71 | $jsonData = json_encode($toursData, JSON_PRETTY_PRINT); 72 | 73 | file_put_contents($toursJsonFile, $jsonData); 74 | } 75 | 76 | function load_tour_data(int $id): array 77 | { 78 | $toursData = load_all_tours_data(); 79 | 80 | foreach ($toursData as $tourData) { 81 | if ($tourData['id'] === $id) { 82 | return $tourData; 83 | } 84 | } 85 | 86 | throw new RuntimeException('Could not find tour with ID ' . $id); 87 | } 88 | 89 | function save_tour_data(array $modifiedTourData): void 90 | { 91 | $toursData = load_all_tours_data(); 92 | 93 | foreach ($toursData as $key => $tourData) { 94 | if ($tourData['id'] === $modifiedTourData['id']) { 95 | $toursData[$key] = $modifiedTourData; 96 | } 97 | } 98 | 99 | save_all_tours($toursData); 100 | } 101 | 102 | function delete_tour(int $id): void 103 | { 104 | $toursData = load_all_tours_data(); 105 | 106 | foreach ($toursData as $key => $tourData) { 107 | if ($tourData['id'] === $id) { 108 | $toursData[$key]['is_deleted'] = true; 109 | } 110 | } 111 | 112 | save_all_tours($toursData); 113 | } 114 | 115 | function process_image_upload(array $normalizedData): array 116 | { 117 | if (is_uploaded_file($normalizedData['picture'])) { 118 | $filename = 'tour-' . $normalizedData['id'] . '.jpg'; 119 | $picturePath = __DIR__ . '/../../public/uploads/' . $filename; 120 | 121 | // Move the uploaded file to `public/uploads/`: 122 | move_uploaded_file($_FILES['picture']['tmp_name'], $picturePath); 123 | 124 | // Set the filename so it will be saved too: 125 | $normalizedData['picture'] = $filename; 126 | } 127 | 128 | return $normalizedData; 129 | } 130 | -------------------------------------------------------------------------------- /pages/homepage.php: -------------------------------------------------------------------------------- 1 | 6 |

This is the homepage

7 |

Get yourself a random number

8 | 18 |

19 | Add a tour 20 |

21 | 24 |

There are no tours (yet).

25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 54 | 60 | 65 | 77 | 78 | 81 | 82 |
DestinationNumber of ticketsIs accessibleActions
44 | 48 | 52 | 53 | 55 | 59 | 61 | 64 | 66 | Edit 69 | 70 |
71 | 74 | 75 |
76 |
83 | '$2y$10$1sXx3dPquOicIl53Y7XRdOqyS4P6flYXXpxHpTC83ZnusdxpEPtXe' 6 | ]; 7 | 8 | if (isset($_POST['username'], $_POST['password'])) { 9 | // The user has submitted the login form 10 | 11 | if (isset($users[$_POST['username']])) { 12 | // The provided username is correct, now validate the password 13 | $expectedPasswordHash = $users[$_POST['username']]; 14 | 15 | if (password_verify($_POST['password'], $expectedPasswordHash)) { 16 | // The provided password is also correct 17 | 18 | // Remember the username of the user who just logged in 19 | $_SESSION['authenticated_user'] = $_POST['username']; 20 | 21 | // Redirect to /secret 22 | header('Location: /secret'); 23 | exit; 24 | } 25 | } 26 | } 27 | ?> 28 | 32 |
33 |
34 | 37 | 38 |
39 |
40 | 43 | 44 |
45 |
46 | 47 |
48 |
49 | 15 |
16 |
17 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /pages/oops.php: -------------------------------------------------------------------------------- 1 | 15 | Cat : 16 | Picture <?php echo $i; ?> 18 | 21 |
22 |
23 | 32 | 47 |
48 |
49 | 52 | 57 |
58 |
59 | 60 |
61 |
62 | 63 | -------------------------------------------------------------------------------- /pages/random.php: -------------------------------------------------------------------------------- 1 | 8 |

Your lucky number is:

9 | 5) { ?> 10 |

Nice, !

13 | 14 |

15 | 16 | Now show me kittens! 17 | 18 |

19 | 20 | -------------------------------------------------------------------------------- /pages/secret.php: -------------------------------------------------------------------------------- 1 | 7 | 11 |

Here's something special for users who are logged in:

12 |

An elephpant

13 | 2 |
3 | 6 | 11 | 14 | 15 | 18 |
19 |
20 | 23 | 31 | 34 | 35 | 36 | 37 | 40 |
41 |
42 | 52 |
53 |
54 | 57 | 60 | Current picture 63 | 66 | 67 | 70 | 71 | 72 | 73 | 76 |
77 |
78 | 79 |
80 | 81 | -------------------------------------------------------------------------------- /pages/tour.php: -------------------------------------------------------------------------------- 1 | 16 |

Back to the list

17 |

Edit this tour

18 |

Tour to

21 | 24 |

25 | Illustration 28 |

29 | 32 |

This tour is .

35 |

There are tickets available.

38 | 2 | 7 | 8 | 9 | tests 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/apple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasnoback/php-for-the-web/86e62c951f2cbb599f998cff82b702d4c15df904/public/apple.jpg -------------------------------------------------------------------------------- /public/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasnoback/php-for-the-web/86e62c951f2cbb599f998cff82b702d4c15df904/public/cat.jpg -------------------------------------------------------------------------------- /public/elephpant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasnoback/php-for-the-web/86e62c951f2cbb599f998cff82b702d4c15df904/public/elephpant.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasnoback/php-for-the-web/86e62c951f2cbb599f998cff82b702d4c15df904/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 'oops.php', 6 | '/create-tour' => 'create-tour.php', 7 | '/list-tours' => 'list-tours.php', 8 | '/edit-tour' => 'edit-tour.php', 9 | '/delete-tour' => 'delete-tour.php', 10 | '/tour' => 'tour.php', 11 | '/login' => 'login.php', 12 | '/logout' => 'logout.php', 13 | '/name' => 'name.php', 14 | '/pictures' => 'pictures.php', 15 | '/random' => 'random.php', 16 | '/secret' => 'secret.php', 17 | '/' => 'homepage.php' 18 | ]; 19 | 20 | $pathInfo = $_SERVER['PATH_INFO'] ?? '/'; 21 | if (isset($urlMap[$pathInfo])) { 22 | // Load a specific page script 23 | include(__DIR__ . '/../pages/' . $urlMap[$pathInfo]); 24 | } else { 25 | // Return a 404 status code 26 | header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); 27 | include(__DIR__ . '/../pages/404.php'); 28 | } 29 | -------------------------------------------------------------------------------- /secret.txt: -------------------------------------------------------------------------------- 1 | MySeCrEtPaSsWoRd 2 | -------------------------------------------------------------------------------- /tests/ChallengeChapter04Test.php: -------------------------------------------------------------------------------- 1 | filter('.message')->text() 19 | ); 20 | } 21 | 22 | protected function setUp(): void 23 | { 24 | $this->client = self::createHttpBrowserClient( 25 | [ 26 | 'webServerDir' => __DIR__ . '/../challenge_chapter_04/public/' 27 | ] 28 | ); 29 | } 30 | 31 | /** 32 | * @test 33 | */ 34 | public function it_shows_the_english_message_by_default(): void 35 | { 36 | $response = $this->client->request('GET', self::$baseUri . '/congratulations.php'); 37 | 38 | self::assertMessageIs($response, 'Congratulations!'); 39 | } 40 | 41 | /** 42 | * @test 43 | */ 44 | public function it_shows_the_dutch_language_when_dutch_is_selected(): void 45 | { 46 | $this->client->request('GET', self::$baseUri . '/congratulations.php'); 47 | 48 | $response = $this->client->submitForm( 49 | 'Submit', 50 | [ 51 | 'language' => 'nl' 52 | ], 53 | 'GET' 54 | ); 55 | 56 | self::assertMessageIs($response, 'Gefeliciteerd!'); 57 | } 58 | 59 | /** 60 | * @test 61 | */ 62 | public function it_remembers_the_selected_language(): void 63 | { 64 | $this->client->request('GET', self::$baseUri . '/congratulations.php'); 65 | 66 | $this->client->submitForm( 67 | 'Submit', 68 | [ 69 | 'language' => 'nl' 70 | ], 71 | 'GET' 72 | ); 73 | 74 | $response = $this->client->request('GET', self::$baseUri . '/congratulations.php');; 75 | 76 | self::assertMessageIs($response, 'Gefeliciteerd!'); 77 | } 78 | 79 | /** 80 | * @test 81 | */ 82 | public function it_uses_the_default_language_if_the_provided_language_is_invalid(): void 83 | { 84 | $response = $this->client->request('GET', self::$baseUri . '/congratulations.php?language=invalid'); 85 | 86 | self::assertMessageIs($response, 'Congratulations!'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/ChallengeChapter07Test.php: -------------------------------------------------------------------------------- 1 | client->request('GET', '/tasks'); 16 | 17 | return strpos($response->filter('ul.tasks')->text(), $task) !== false; 18 | } 19 | 20 | protected function setUp(): void 21 | { 22 | $this->client = self::createHttpBrowserClient( 23 | [ 24 | 'webServerDir' => __DIR__ . '/../challenge_chapter_07/public/' 25 | ] 26 | ); 27 | } 28 | 29 | protected function tearDown(): void 30 | { 31 | $this->client->request('GET', '/logout'); 32 | } 33 | 34 | /** 35 | * @test 36 | */ 37 | public function you_will_be_redirected_to_the_login_page_first(): void 38 | { 39 | $response = $this->client->request('GET', self::$baseUri . '/tasks'); 40 | self::assertStringContainsString('/login', $response->getUri(), 'Expected to be redirected to the login page'); 41 | } 42 | 43 | /** 44 | * @test 45 | */ 46 | public function after_logging_in_you_have_access_to_the_list_of_tasks(): void 47 | { 48 | $this->login(); 49 | 50 | $response = $this->client->request('GET', self::$baseUri . '/tasks'); 51 | 52 | self::assertStringContainsString('Manage tasks', $response->text()); 53 | } 54 | 55 | /** 56 | * @test 57 | */ 58 | public function you_can_create_a_task(): void 59 | { 60 | $this->login(); 61 | 62 | $this->client->request('GET', self::$baseUri . '/tasks'); 63 | 64 | $task = 'Build some Lego'; 65 | 66 | $this->client->submitForm( 67 | 'Create', 68 | [ 69 | 'task' => $task 70 | ] 71 | ); 72 | 73 | self::assertTrue($this->currentUserHasTask($task)); 74 | } 75 | 76 | /** 77 | * @test 78 | */ 79 | public function you_can_not_provide_an_empty_string_as_a_task(): void 80 | { 81 | $this->login(); 82 | 83 | $this->client->request('GET', self::$baseUri . '/tasks'); 84 | 85 | $response = $this->client->submitForm( 86 | 'Create', 87 | [ 88 | 'task' => '' 89 | ] 90 | ); 91 | 92 | self::assertCount(0, $response->filter('ul.tasks li')); 93 | } 94 | 95 | /** 96 | * @test 97 | */ 98 | public function you_can_not_see_other_tasks_of_other_users(): void 99 | { 100 | $this->loginAs('matthias'); 101 | 102 | $task = 'Build some Lego'; 103 | $this->createTask($task); 104 | 105 | $this->loginAs('tomas'); 106 | 107 | self::assertFalse($this->currentUserHasTask($task)); 108 | } 109 | 110 | private function login(): void 111 | { 112 | $this->loginAs('matthias'); 113 | } 114 | 115 | private function loginAs(string $username): void 116 | { 117 | $this->client->request('GET', self::$baseUri . '/login'); 118 | 119 | $this->client->submitForm( 120 | 'Submit', 121 | [ 122 | 'username' => $username, 123 | 'password' => 'test' 124 | ] 125 | ); 126 | } 127 | 128 | private function createTask(string $task): void 129 | { 130 | $this->client->request('GET', self::$baseUri . '/tasks'); 131 | 132 | $this->client->submitForm( 133 | 'Create', 134 | [ 135 | 'task' => $task 136 | ] 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/ChallengeChapter08Test.php: -------------------------------------------------------------------------------- 1 | client->request('GET', '/list-tasks'); 17 | 18 | return strpos($response->filter('ul.tasks')->text(), $task) !== false; 19 | } 20 | 21 | protected function setUp(): void 22 | { 23 | $tasksJsonFile = __DIR__ . '/../challenge_chapter_08/data/tasks.json'; 24 | if (is_file($tasksJsonFile)) { 25 | unlink($tasksJsonFile); 26 | } 27 | 28 | $this->client = self::createHttpBrowserClient( 29 | [ 30 | 'webServerDir' => __DIR__ . '/../challenge_chapter_08/public/' 31 | ] 32 | ); 33 | } 34 | 35 | protected function tearDown(): void 36 | { 37 | $this->client->request('GET', '/logout'); 38 | } 39 | 40 | /** 41 | * @test 42 | * @dataProvider securePaths 43 | */ 44 | public function you_need_to_be_logged_in(string $path): void 45 | { 46 | $response = $this->client->request('GET', self::$baseUri . $path); 47 | self::assertStringContainsString('/login', $response->getUri(), 'Expected to be redirected to the login page'); 48 | } 49 | 50 | public function securePaths(): Generator 51 | { 52 | yield ['/list-tasks']; 53 | yield ['/create-task']; 54 | } 55 | 56 | /** 57 | * @test 58 | */ 59 | public function after_logging_in_you_have_access_to_the_list_of_tasks(): void 60 | { 61 | $this->login(); 62 | 63 | $response = $this->client->request('GET', self::$baseUri . '/list-tasks'); 64 | 65 | self::assertStringContainsString('Manage tasks', $response->text()); 66 | } 67 | 68 | /** 69 | * @test 70 | */ 71 | public function you_can_create_a_task(): void 72 | { 73 | $this->login(); 74 | 75 | $task = 'Build some Lego'; 76 | 77 | $this->createTask($task); 78 | 79 | self::assertTrue($this->currentUserHasTask($task)); 80 | } 81 | 82 | /** 83 | * @test 84 | */ 85 | public function you_can_not_provide_an_empty_string_as_a_task(): void 86 | { 87 | $this->login(); 88 | 89 | $response = $this->createTask(''); 90 | 91 | self::assertStringContainsString( 92 | 'Task can not be empty', 93 | $response->filter('strong')->text() 94 | ); 95 | } 96 | 97 | /** 98 | * @test 99 | */ 100 | public function you_can_not_see_other_tasks_of_other_users(): void 101 | { 102 | $this->loginAs('matthias'); 103 | 104 | $task = 'Build some Lego'; 105 | $this->createTask($task); 106 | 107 | $this->loginAs('tomas'); 108 | 109 | self::assertFalse($this->currentUserHasTask($task)); 110 | } 111 | 112 | private function login(): void 113 | { 114 | $this->loginAs('matthias'); 115 | } 116 | 117 | private function loginAs(string $username): void 118 | { 119 | $this->client->request('GET', self::$baseUri . '/login'); 120 | 121 | $this->client->submitForm( 122 | 'Submit', 123 | [ 124 | 'username' => $username, 125 | 'password' => 'test' 126 | ] 127 | ); 128 | } 129 | 130 | private function createTask(string $task): Crawler 131 | { 132 | $this->client->request('GET', self::$baseUri . '/create-task'); 133 | 134 | return $this->client->submitForm( 135 | 'Create', 136 | [ 137 | 'task' => $task 138 | ] 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/ChallengeChapter09Test.php: -------------------------------------------------------------------------------- 1 | client->request('GET', '/list-tasks'); 17 | 18 | return strpos($response->filter('ul.tasks')->text(), $task) !== false; 19 | } 20 | 21 | protected function setUp(): void 22 | { 23 | $tasksJsonFile = __DIR__ . '/../challenge_chapter_09/data/tasks.json'; 24 | if (is_file($tasksJsonFile)) { 25 | unlink($tasksJsonFile); 26 | } 27 | 28 | $this->client = self::createHttpBrowserClient( 29 | [ 30 | 'webServerDir' => __DIR__ . '/../challenge_chapter_09/public/' 31 | ] 32 | ); 33 | } 34 | 35 | protected function tearDown(): void 36 | { 37 | $this->client->request('GET', '/logout'); 38 | } 39 | 40 | /** 41 | * @test 42 | * @dataProvider securePaths 43 | */ 44 | public function you_need_to_be_logged_in(string $path): void 45 | { 46 | $response = $this->client->request('GET', self::$baseUri . $path); 47 | self::assertStringContainsString('/login', $response->getUri(), 'Expected to be redirected to the login page'); 48 | } 49 | 50 | public function securePaths(): Generator 51 | { 52 | yield ['/list-tasks']; 53 | yield ['/create-task']; 54 | } 55 | 56 | /** 57 | * @test 58 | */ 59 | public function after_logging_in_you_have_access_to_the_list_of_tasks(): void 60 | { 61 | $this->login(); 62 | 63 | $response = $this->client->request('GET', self::$baseUri . '/list-tasks'); 64 | 65 | self::assertStringContainsString('Manage tasks', $response->text()); 66 | } 67 | 68 | /** 69 | * @test 70 | */ 71 | public function you_can_create_a_task(): void 72 | { 73 | $this->login(); 74 | 75 | $task = 'Build some Lego'; 76 | 77 | $this->createTask($task); 78 | 79 | self::assertTrue($this->currentUserHasTask($task)); 80 | } 81 | 82 | /** 83 | * @test 84 | */ 85 | public function you_can_edit_a_task(): void 86 | { 87 | $this->login(); 88 | 89 | $task = 'Build some Lego'; 90 | $this->createTask($task); 91 | 92 | $response = $this->client->clickLink('Edit'); 93 | 94 | self::assertEquals($task, $response->filter('input#task')->attr('value')); 95 | 96 | $newTask = 'Buy some Lego'; 97 | 98 | $this->client->submitForm( 99 | 'Save', 100 | [ 101 | 'task' => $newTask 102 | ] 103 | ); 104 | 105 | self::assertTrue($this->currentUserHasTask($newTask)); 106 | } 107 | 108 | /** 109 | * @test 110 | */ 111 | public function you_can_not_provide_an_empty_string_as_a_task(): void 112 | { 113 | $this->login(); 114 | 115 | $response = $this->createTask(''); 116 | 117 | self::assertStringContainsString( 118 | 'Task can not be empty', 119 | $response->filter('strong')->text() 120 | ); 121 | } 122 | 123 | /** 124 | * @test 125 | */ 126 | public function you_can_not_see_other_tasks_of_other_users(): void 127 | { 128 | $this->loginAs('matthias'); 129 | 130 | $task = 'Build some Lego'; 131 | $this->createTask($task); 132 | 133 | $this->loginAs('tomas'); 134 | 135 | self::assertFalse($this->currentUserHasTask($task)); 136 | } 137 | 138 | /** 139 | * @test 140 | */ 141 | public function you_can_not_edit_the_task_of_another_user(): void 142 | { 143 | $this->loginAs('matthias'); 144 | 145 | $task = 'Build some Lego'; 146 | $this->createTask($task); 147 | 148 | $this->loginAs('tomas'); 149 | 150 | // Task 1 will be owned by matthias, but tomas is the logged in user 151 | $response = $this->client->request('GET', self::$baseUri . '/edit-task?id=1'); 152 | 153 | self::assertStringContainsString('You can not edit a task created by another user', $response->text()); 154 | } 155 | 156 | /** 157 | * @test 158 | */ 159 | public function you_can_mark_a_task_as_done_and_it_disappears_from_the_list(): void 160 | { 161 | $this->loginAs('matthias'); 162 | 163 | $task = 'Build some Lego'; 164 | $this->createTask($task); 165 | 166 | $this->client->submitForm('Done'); 167 | 168 | self::assertFalse($this->currentUserHasTask($task)); 169 | } 170 | 171 | /** 172 | * @test 173 | */ 174 | public function you_can_not_mark_the_task_of_another_user_as_done(): void 175 | { 176 | $this->loginAs('matthias'); 177 | 178 | $task = 'Build some Lego'; 179 | $this->createTask($task); 180 | 181 | $this->loginAs('tomas'); 182 | 183 | // Task 1 will be owned by matthias, but tomas is the logged in user 184 | $response = $this->client->request('POST', self::$baseUri . '/mark-as-done', ['id' => '1']); 185 | 186 | self::assertStringContainsString('You can not mark a task created by another user as done', $response->text()); 187 | 188 | $this->loginAs('matthias'); 189 | 190 | self::assertTrue($this->currentUserHasTask($task)); 191 | } 192 | 193 | private function login(): void 194 | { 195 | $this->loginAs('matthias'); 196 | } 197 | 198 | private function loginAs(string $username): void 199 | { 200 | $this->client->request('GET', self::$baseUri . '/login'); 201 | 202 | $this->client->submitForm( 203 | 'Submit', 204 | [ 205 | 'username' => $username, 206 | 'password' => 'test' 207 | ] 208 | ); 209 | } 210 | 211 | private function createTask(string $task): Crawler 212 | { 213 | $this->client->request('GET', self::$baseUri . '/create-task'); 214 | 215 | return $this->client->submitForm( 216 | 'Save', 217 | [ 218 | 'task' => $task 219 | ] 220 | ); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /tests/HomepageTest.php: -------------------------------------------------------------------------------- 1 | request('GET', self::$baseUri . '/'); 18 | 19 | self::assertStringContainsString( 20 | 'This is the homepage', 21 | $response->filter('h1')->text() 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/PicturesTest.php: -------------------------------------------------------------------------------- 1 | request('GET', self::$baseUri . '/pictures'); 19 | 20 | $response = $client->submitForm( 21 | 'Submit', 22 | [ 23 | 'picture' => 'cat.jpg', 24 | 'number' => 2 25 | ], 26 | 'GET' 27 | ); 28 | 29 | $imgElements = $response->filter('img'); 30 | 31 | // We expect 2 elements: 32 | self::assertCount(2, $imgElements); 33 | 34 | $imgElements->each( 35 | function (Crawler $imgElement): void { 36 | // The src of each element should contain cat.jpg: 37 | self::assertStringContainsString( 38 | 'cat.jpg', 39 | $imgElement->attr('src') 40 | ); 41 | } 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/ToursTest.php: -------------------------------------------------------------------------------- 1 | request('GET', self::$baseUri . '/create-tour'); 26 | 27 | $client->submitForm( 28 | 'Save', 29 | [ 30 | 'destination' => 'Berlin', 31 | 'number_of_tickets_available' => 10, 32 | 'is_accessible' => 'yes', 33 | 'picture' => __DIR__ . '/berlin.jpg' 34 | ] 35 | ); 36 | 37 | $response = $client->request('GET', '/list-tours'); 38 | 39 | self::assertStringContainsString( 40 | 'Berlin', 41 | $response->filter('td')->text() 42 | ); 43 | } 44 | 45 | /** 46 | * @test 47 | */ 48 | public function edit_an_existing_tour(): void 49 | { 50 | $client = self::createHttpBrowserClient(); 51 | 52 | $client->request('GET', self::$baseUri . '/create-tour'); 53 | 54 | $client->submitForm( 55 | 'Save', 56 | [ 57 | 'destination' => 'Berlin', 58 | 'number_of_tickets_available' => 10, 59 | 'is_accessible' => 'yes', 60 | 'picture' => __DIR__ . '/berlin.jpg' 61 | ] 62 | ); 63 | 64 | // No need to go to /list-tours; we should be redirected to that page 65 | 66 | // There will be only one tour and we're going to edit it now: 67 | $client->clickLink('Edit'); 68 | 69 | $client->submitForm( 70 | 'Save', 71 | [ 72 | 'destination' => 'Paris', 73 | 'number_of_tickets_available' => 5, 74 | 'is_accessible' => false, 75 | 'picture' => __DIR__ . '/paris.jpg' 76 | ] 77 | ); 78 | 79 | // We should be back on /list-tours 80 | 81 | // The destination is a link to the details page 82 | $response = $client->clickLink('Paris'); 83 | 84 | self::assertStringContainsString( 85 | 'Tour to Paris', 86 | $response->filter('h1')->text() 87 | ); 88 | self::assertStringContainsString( 89 | 'This tour is not accessible', 90 | $response->filter('.accessibility-info')->text() 91 | ); 92 | self::assertStringContainsString( 93 | 'There are 5 tickets available', 94 | $response->text() 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/berlin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasnoback/php-for-the-web/86e62c951f2cbb599f998cff82b702d4c15df904/tests/berlin.jpg -------------------------------------------------------------------------------- /tests/paris.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasnoback/php-for-the-web/86e62c951f2cbb599f998cff82b702d4c15df904/tests/paris.jpg --------------------------------------------------------------------------------