├── .gitignore ├── Resources ├── js │ └── scripts.js └── css │ └── styles.css ├── Src ├── Configs │ ├── app.php │ └── database.php ├── Exception │ ├── BadRequestException.php │ ├── NotFoundException.php │ ├── DatabaseConnectionException.php │ ├── InvalidArgumentException.php │ ├── InvalidLogLevelArgument.php │ ├── MissingArgumentException.php │ ├── exception.php │ ├── ExceptionHandler.php │ └── BaseException.php ├── Contracts │ ├── DatabaseConnectionInterface.php │ ├── RepositoryInterface.php │ └── LoggerInterface.php ├── Entity │ ├── Entity.php │ └── BugReport.php ├── Repository │ ├── BugReportRepository.php │ └── Repository.php ├── read.php ├── Logger │ ├── LogLevel.php │ └── Logger.php ├── Helpers │ ├── Config.php │ ├── HttpClient.php │ ├── DbQueryBuilderFactory.php │ └── App.php ├── Database │ ├── AbstractConnection.php │ ├── PDOQueryBuilder.php │ ├── Query.php │ ├── MySQLiConnection.php │ ├── PDOConnection.php │ ├── MySQLiQueryBuilder.php │ └── QueryBuilder.php ├── delete.php ├── add.php └── update.php ├── header.php ├── composer.json ├── phpunit.xml ├── Tests ├── Functional │ ├── HomepageTest.php │ └── CrudTest.php └── Units │ ├── ApplicationTest.php │ ├── LoggerTest.php │ ├── DatabaseConnectionTest.php │ ├── RepositoryTest.php │ └── QueryBuilderTest.php ├── LICENSE.md ├── README.md ├── addModal.php ├── store.sql ├── index.php └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor/ 3 | /Src/Logger/*.log 4 | -------------------------------------------------------------------------------- /Resources/js/scripts.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // Activate tooltip 3 | $('[data-toggle="tooltip"]').tooltip(); 4 | }); -------------------------------------------------------------------------------- /Src/Configs/app.php: -------------------------------------------------------------------------------- 1 | 'Bug Report App', 6 | 'env' => 'local', 7 | 'debug' => true, 8 | 'log_path' => __DIR__ . '/../Logger', 9 | ]; -------------------------------------------------------------------------------- /Src/Exception/BadRequestException.php: -------------------------------------------------------------------------------- 1 | findAll(); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "terdia/bug-report-app", 3 | "require": {}, 4 | "require-dev": { 5 | "phpunit/phpunit": "^7" 6 | }, 7 | "autoload": { 8 | "psr-4": { 9 | "App\\":"Src" 10 | } 11 | }, 12 | "autoload-dev": { 13 | "psr-4": { 14 | "Tests\\": "Tests" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Src/Logger/LogLevel.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'driver' => 'mysql', 7 | 'host' => 'localhost', 8 | 'db_name' => 'bug_app', 9 | 'db_username' => 'root', 10 | 'db_user_password' => '', 11 | 'default_fetch' => PDO::FETCH_OBJ, 12 | ], 13 | 'mysqli' => [ 14 | 'host' => 'localhost', 15 | 'db_name' => 'bug_app', 16 | 'db_username' => 'root', 17 | 'db_user_password' => '', 18 | 'default_fetch' => MYSQLI_ASSOC, 19 | ], 20 | ]; -------------------------------------------------------------------------------- /Src/Contracts/RepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | Tests/Unit 13 | 14 | 15 | 16 | Tests/Functional 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Src/Exception/ExceptionHandler.php: -------------------------------------------------------------------------------- 1 | isDebugMode()){ 17 | var_dump($exception); 18 | }else{ 19 | echo "This should not have happened, please try again"; 20 | } 21 | exit; 22 | } 23 | 24 | public function convertWarningsAndNoticesToException($severity, $message, $file, $line) 25 | { 26 | throw new ErrorException($message, $severity, $severity, $file, $line); 27 | } 28 | } -------------------------------------------------------------------------------- /Tests/Functional/HomepageTest.php: -------------------------------------------------------------------------------- 1 | get('http://localhost/bug-report-app/index.php'); 16 | $response = json_decode($response, true); 17 | self::assertEquals(200, $response['statusCode']); 18 | self::assertStringContainsString('Bug Report App', $response['content']); 19 | self::assertStringContainsString('

Manage Bug Reports

', $response['content']); 20 | } 21 | } -------------------------------------------------------------------------------- /Src/Contracts/LoggerInterface.php: -------------------------------------------------------------------------------- 1 | isRunningFromConsole()); 23 | self::assertSame('test', $application->getEnvironment()); 24 | self::assertNotNull($application->getLogPath()); 25 | self::assertInstanceOf(\DateTime::class, $application->getServerTime()); 26 | } 27 | } -------------------------------------------------------------------------------- /Src/Exception/BaseException.php: -------------------------------------------------------------------------------- 1 | data = $data; 23 | parent::__construct($message, $code, $previous); 24 | } 25 | 26 | public function setExtraData(string $key, $value):void 27 | { 28 | $this->data[$key] = $value; 29 | } 30 | 31 | public function getExtraData(): array 32 | { 33 | if(count($this->data) === 0){ 34 | return $this->data; 35 | } 36 | return json_decode(json_encode($this->data), true); 37 | } 38 | } -------------------------------------------------------------------------------- /Src/Helpers/Config.php: -------------------------------------------------------------------------------- 1 | $statusCode, 'content' => $response]); 22 | } 23 | 24 | public function get(string $url) 25 | { 26 | $handler = curl_init(); 27 | curl_setopt($handler, CURLOPT_URL, $url); 28 | curl_setopt($handler, CURLOPT_RETURNTRANSFER, true); 29 | 30 | $response = curl_exec($handler); 31 | $statusCode = curl_getinfo($handler, CURLINFO_HTTP_CODE); 32 | curl_close($handler); 33 | 34 | return json_encode(['statusCode' => $statusCode, 'content' => $response]); 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Osayawe Ogbemudia Terry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Src/Database/AbstractConnection.php: -------------------------------------------------------------------------------- 1 | credentials = $credentials; 21 | if(!$this->credentialsHaveRequiredKeys($this->credentials)) { 22 | throw new MissingArgumentException( 23 | sprintf( 24 | 'Database connection credentials are not mapped correctly, required key: %s', 25 | implode(',', static::REQUIRED_CONNECTION_KEYS) 26 | ) 27 | ); 28 | } 29 | } 30 | 31 | private function credentialsHaveRequiredKeys(array $credentials): bool 32 | { 33 | $matches = array_intersect(static::REQUIRED_CONNECTION_KEYS, array_keys($credentials)); 34 | return count($matches) === count(static::REQUIRED_CONNECTION_KEYS); 35 | } 36 | 37 | abstract protected function parseCredentials(array $credentials): array; 38 | } -------------------------------------------------------------------------------- /Src/Database/PDOQueryBuilder.php: -------------------------------------------------------------------------------- 1 | statement->fetchAll(); 15 | } 16 | 17 | public function count() 18 | { 19 | return $this->statement->rowCount(); 20 | } 21 | 22 | public function lastInsertedId() 23 | { 24 | return $this->connection->lastInsertId(); 25 | } 26 | 27 | public function prepare($query) 28 | { 29 | return $this->connection->prepare($query); 30 | } 31 | 32 | public function execute($statement) 33 | { 34 | $statement->execute($this->bindings); 35 | $this->bindings = []; 36 | $this->placeholders = []; 37 | return $statement; 38 | } 39 | 40 | public function fetchInto($className) 41 | { 42 | return $this->statement->fetchAll(PDO::FETCH_CLASS, $className); 43 | } 44 | 45 | public function beginTransaction() 46 | { 47 | $this->connection->beginTransaction(); 48 | } 49 | 50 | public function affected() 51 | { 52 | return $this->count(); 53 | } 54 | } -------------------------------------------------------------------------------- /Src/delete.php: -------------------------------------------------------------------------------- 1 | find((int) $reportId); 22 | $repository->delete($bugReport); 23 | 24 | }catch (Throwable $exception){ 25 | $logger->critical($exception->getMessage(), $_POST); 26 | throw new BadRequestException($exception->getMessage(), [$exception], 400); 27 | } 28 | 29 | $logger->info( 30 | 'bug report deleted', 31 | ['id' => $bugReport->getId(), 'type' => $bugReport->getReportType(),] 32 | ); 33 | $bugReports = $repository->findAll(); 34 | } 35 | -------------------------------------------------------------------------------- /Src/Database/Query.php: -------------------------------------------------------------------------------- 1 | fields, $this->table, implode(' and ', $this->placeholders) 18 | ); 19 | break; 20 | case self::DML_TYPE_INSERT: 21 | return sprintf( 22 | "INSERT INTO %s (%s) VALUES (%s)", 23 | $this->table, $this->fields, implode(',', $this->placeholders) 24 | ); 25 | break; 26 | case self::DML_TYPE_UPDATE: 27 | return sprintf( 28 | "UPDATE %s SET %s WHERE %s", 29 | $this->table, implode(', ', $this->fields), implode(' and ', $this->placeholders) 30 | ); 31 | break; 32 | case self::DML_TYPE_DELETE: 33 | return sprintf( 34 | "DELETE FROM %s WHERE %s", 35 | $this->table, implode(' and ', $this->placeholders) 36 | ); 37 | break; 38 | default: 39 | throw new \InvalidArgumentException('Dml type not supported'); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Src/Helpers/DbQueryBuilderFactory.php: -------------------------------------------------------------------------------- 1 | connect(); 29 | return new PDOQueryBuilder($connection); 30 | break; 31 | case 'mysqli': 32 | $connection = (new MySQLiConnection($credentials))->connect(); 33 | return new MySQLiQueryBuilder($connection); 34 | break; 35 | default: 36 | throw new DatabaseConnectionException( 37 | "Connection type is not recognize internally", [ 38 | 'type' => $connectionType 39 | ] 40 | ); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Src/add.php: -------------------------------------------------------------------------------- 1 | setReportType($reportType); 19 | $bugReport->setEmail($email); 20 | $bugReport->setLink($link); 21 | $bugReport->setMessage($message); 22 | 23 | $logger = new Logger; 24 | try{ 25 | /** @var QueryBuilder $queryBuilder */ 26 | $queryBuilder = DbQueryBuilderFactory::make(); 27 | /** @var BugReportRepository $repository */ 28 | $repository = new BugReportRepository($queryBuilder); 29 | /** @var BugReport $newReport */ 30 | $newReport = $repository->create($bugReport); 31 | }catch (Throwable $exception){ 32 | $logger->critical($exception->getMessage(), $_POST); 33 | throw new BadRequestException($exception->getMessage(), [$exception], 400); 34 | } 35 | 36 | $logger->info( 37 | 'new bug report created', 38 | ['id' => $newReport->getId(), 'type' => $newReport->getReportType(),] 39 | ); 40 | $bugReports = $repository->findAll(); 41 | } -------------------------------------------------------------------------------- /Src/Helpers/App.php: -------------------------------------------------------------------------------- 1 | config = Config::get('app'); 17 | } 18 | 19 | public function isDebugMode(): bool 20 | { 21 | if(!isset($this->config['debug'])){ 22 | return false; 23 | } 24 | return $this->config['debug']; 25 | } 26 | 27 | public function getEnvironment(): string 28 | { 29 | if(!isset($this->config['env'])){ 30 | return 'production'; 31 | } 32 | return $this->isTestMode() ? 'test' : $this->config['env']; 33 | } 34 | 35 | public function getLogPath(): string 36 | { 37 | if(!isset($this->config['log_path'])){ 38 | throw new \Exception('Log path is not defined'); 39 | } 40 | return $this->config['log_path']; 41 | } 42 | 43 | public function isRunningFromConsole(): bool 44 | { 45 | return php_sapi_name() == 'cli' || php_sapi_name() == 'phpbg'; 46 | } 47 | 48 | public function getServerTime(): DateTimeInterface 49 | { 50 | return new DateTime('now', new DateTimeZone('Europe/Berlin')); 51 | } 52 | 53 | public function isTestMode(): bool 54 | { 55 | if($this->isRunningFromConsole() && defined('PHPUNIT_RUNNING') && PHPUNIT_RUNNING == true) { 56 | return true; 57 | } 58 | return false; 59 | } 60 | } -------------------------------------------------------------------------------- /Src/update.php: -------------------------------------------------------------------------------- 1 | find((int) $reportId); 26 | $bugReport->setReportType($reportType); 27 | $bugReport->setEmail($email); 28 | $bugReport->setLink($link); 29 | $bugReport->setMessage($message); 30 | $newReport = $repository->update($bugReport); 31 | 32 | }catch (Throwable $exception){ 33 | $logger->critical($exception->getMessage(), $_POST); 34 | throw new BadRequestException($exception->getMessage(), [$exception], 400); 35 | } 36 | 37 | $logger->info( 38 | 'bug report updated', 39 | ['id' => $newReport->getId(), 'type' => $newReport->getReportType(),] 40 | ); 41 | $bugReports = $repository->findAll(); 42 | } 43 | -------------------------------------------------------------------------------- /Src/Database/MySQLiConnection.php: -------------------------------------------------------------------------------- 1 | report_mode = MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ERROR; 38 | $credentials = $this->parseCredentials($this->credentials); 39 | try{ 40 | $this->connection = new mysqli(...$credentials); 41 | }catch (Throwable $exception){ 42 | throw new DatabaseConnectionException( 43 | $exception->getMessage(), 44 | $this->credentials, 45 | 500 46 | ); 47 | } 48 | return $this; 49 | } 50 | 51 | public function getConnection(): mysqli 52 | { 53 | return $this->connection; 54 | } 55 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bug-report-app 2 | 3 | Build A bug Tracking App with PHP OOP no Dependencies 4 | 5 | Your support is needed, [Enrol in Course](https://devscreencast.com/courses/build-a-bug-tracking-app-with-php-oop-no-dependencies). 6 | 7 | Object Oriented PHP & TDD with PHPUnit from Scratch 8 | 9 | As a PHP developer it won't take long to realize that there are dozens of outdated tutorials and articles on the internet, if you want to learn how to write better code, with fewer bugs, you will need to know how to write clean object oriented code and unit tests. 10 | 11 | This course is designed to get you up and running as fast as possible with Test Driven Development with object oriented PHP and PHPUnit without any dependencies. We'll quickly cover OOP basics, then dive into some of the more advanced features of the language. Don't be tricked by other courses that only teach you basic and outdated stuffs! 12 | 13 | This is the only course that will teach you Test Driven Development in Object Oriented PHP and PHPUnit. This course will give you ample opportunities to strike out on your own and start working on your own programs. 14 | 15 | In this course you will: 16 | 17 | Understand Object Oriented PHP 18 | 19 | Learn test-driven development (TDD) 20 | 21 | Learn how and why you should use dependency inversion principle and dependency injection 22 | 23 | Implement some common design patterns using PHP 24 | 25 | Build a database wrapper with support for PDO and MySQLi 26 | 27 | Gain a sense of when to use basic language features 28 | 29 | PHP is one of the most popular and widely used programming languages. Get job-ready with PHP today by enrolling now! 30 | -------------------------------------------------------------------------------- /Src/Database/PDOConnection.php: -------------------------------------------------------------------------------- 1 | parseCredentials($this->credentials); 27 | try{ 28 | $this->connection = new PDO(...$credentials); 29 | $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 30 | $this->connection->setAttribute( 31 | PDO::ATTR_DEFAULT_FETCH_MODE, 32 | $this->credentials['default_fetch'] 33 | ); 34 | }catch (PDOException $exception){ 35 | throw new DatabaseConnectionException($exception->getMessage(), $this->credentials, 500); 36 | } 37 | return $this; 38 | } 39 | 40 | public function getConnection(): PDO 41 | { 42 | return $this->connection; 43 | } 44 | 45 | protected function parseCredentials(array $credentials): array 46 | { 47 | $dsn = sprintf( 48 | '%s:host=%s;dbname=%s', 49 | $credentials['driver'], 50 | $credentials['host'], 51 | $credentials['db_name'] 52 | ); 53 | 54 | return [$dsn, $credentials['db_username'], $credentials['db_user_password']]; 55 | } 56 | } -------------------------------------------------------------------------------- /Tests/Units/LoggerTest.php: -------------------------------------------------------------------------------- 1 | logger = new Logger; 24 | parent::setUp(); 25 | } 26 | 27 | public function testItImplementsTheLoggerInterface() 28 | { 29 | self::assertInstanceOf(LoggerInterface::class, $this->logger); 30 | } 31 | 32 | public function testItCanCreateDifferentTypesOfLogLevel() 33 | { 34 | $this->logger->info('Testing Info logs'); 35 | $this->logger->error('Testing Error logs'); 36 | $this->logger->log(LogLevel::ALERT, 'Testing Alert logs'); 37 | $app = new App; 38 | 39 | $fileName = sprintf("%s/%s-%s.log", $app->getLogPath(), 'test', date("j.n.Y")); 40 | self::assertFileExists($fileName); 41 | 42 | $contentOfLogFile = file_get_contents($fileName); 43 | self::assertStringContainsString('Testing Info logs', $contentOfLogFile); 44 | self::assertStringContainsString('Testing Error logs', $contentOfLogFile); 45 | self::assertStringContainsString(LogLevel::ALERT, $contentOfLogFile); 46 | unlink($fileName); 47 | self::assertFileNotExists($fileName); 48 | } 49 | 50 | public function testItThrowsInvalidLogLevelArgumentExceptionWhenGivenAWrongLogLevel() 51 | { 52 | self::expectException(InvalidLogLevelArgument::class); 53 | $this->logger->log('invalid', 'Testing invalid log level'); 54 | } 55 | } -------------------------------------------------------------------------------- /Src/Entity/BugReport.php: -------------------------------------------------------------------------------- 1 | id; 19 | } 20 | 21 | public function setReportType(string $reportType): BugReport 22 | { 23 | $this->report_type = $reportType; 24 | return $this; 25 | } 26 | 27 | public function getReportType(): string 28 | { 29 | return $this->report_type; 30 | } 31 | 32 | public function getEmail (): string 33 | { 34 | return $this->email; 35 | } 36 | 37 | public function setEmail (string $email): BugReport 38 | { 39 | $this->email = $email; 40 | return $this; 41 | } 42 | 43 | public function getLink (): ?string 44 | { 45 | return $this->link; 46 | } 47 | 48 | public function setLink (?string $link): BugReport 49 | { 50 | $this->link = $link; 51 | return $this; 52 | } 53 | 54 | public function getMessage (): string 55 | { 56 | return $this->message; 57 | } 58 | 59 | public function setMessage (string $message): BugReport 60 | { 61 | $this->message = $message; 62 | return $this; 63 | } 64 | 65 | public function getCreatedAt(): string 66 | { 67 | return $this->created_at; 68 | } 69 | 70 | public function toArray (): array 71 | { 72 | return [ 73 | 'report_type' => $this->getReportType(), 74 | 'email' => $this->getEmail(), 75 | 'message' => $this->getMessage(), 76 | 'link' => $this->getLink(), 77 | 'created_at' => date('Y-m-d H:i:s'), 78 | ]; 79 | } 80 | } -------------------------------------------------------------------------------- /Tests/Units/DatabaseConnectionTest.php: -------------------------------------------------------------------------------- 1 | getCredentials('pdo'); 28 | $pdoHandler = (new PDOConnection($credentials))->connect(); 29 | self::assertInstanceOf(DatabaseConnectionInterface::class, $pdoHandler); 30 | return $pdoHandler; 31 | } 32 | 33 | /** @depends testItCanConnectToDatabaseWithPdoApi */ 34 | public function testItIsAValidPdoConnection(DatabaseConnectionInterface $handler) 35 | { 36 | self::assertInstanceOf(\PDO::class, $handler->getConnection()); 37 | } 38 | 39 | public function testItCanConnectToDatabaseWithMysqliApi() 40 | { 41 | $credentials = $this->getCredentials('mysqli'); 42 | $handler = (new MySQLiConnection($credentials))->connect(); 43 | self::assertInstanceOf(DatabaseConnectionInterface::class, $handler); 44 | return $handler; 45 | } 46 | 47 | /** @depends testItCanConnectToDatabaseWithMysqliApi */ 48 | public function testItIsAValidMysqliConnection(DatabaseConnectionInterface $handler) 49 | { 50 | self::assertInstanceOf(\mysqli::class, $handler->getConnection()); 51 | } 52 | 53 | private function getCredentials(string $type) 54 | { 55 | return array_merge( 56 | Config::get('database', $type), 57 | ['db_name' => 'bug_app_testing'] 58 | ); 59 | } 60 | } -------------------------------------------------------------------------------- /addModal.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 | 42 | -------------------------------------------------------------------------------- /Src/Logger/Logger.php: -------------------------------------------------------------------------------- 1 | addRecord(LogLevel::EMERGENCY, $message, $context); 19 | } 20 | 21 | public function alert(string $message, array $context = []) 22 | { 23 | $this->addRecord(LogLevel::ALERT, $message, $context); 24 | } 25 | 26 | public function critical(string $message, array $context = []) 27 | { 28 | $this->addRecord(LogLevel::CRITICAL, $message, $context); 29 | } 30 | 31 | public function error(string $message, array $context = []) 32 | { 33 | $this->addRecord(LogLevel::ERROR, $message, $context); 34 | } 35 | 36 | public function warning(string $message, array $context = []) 37 | { 38 | $this->addRecord(LogLevel::WARNING, $message, $context); 39 | } 40 | 41 | public function notice(string $message, array $context = []) 42 | { 43 | $this->addRecord(LogLevel::NOTICE, $message, $context); 44 | } 45 | 46 | public function info(string $message, array $context = []) 47 | { 48 | $this->addRecord(LogLevel::INFO, $message, $context); 49 | } 50 | 51 | public function debug(string $message, array $context = []) 52 | { 53 | $this->addRecord(LogLevel::DEBUG, $message, $context); 54 | } 55 | 56 | public function log(string $level, string $message, array $context = []) 57 | { 58 | $object = new ReflectionClass(LogLevel::class); 59 | $validLogLevelsArray = $object->getConstants(); 60 | if(!in_array($level, $validLogLevelsArray)){ 61 | throw new InvalidLogLevelArgument($level, $validLogLevelsArray); 62 | } 63 | $this->addRecord($level, $message, $context); 64 | } 65 | 66 | private function addRecord(string $level, string $message, array $context = []) 67 | { 68 | $application = new App; 69 | $date = $application->getServerTime()->format('Y-m-d H:i:s'); 70 | $logPath = $application->getLogPath(); 71 | $env = $application->getEnvironment(); 72 | $details = sprintf( 73 | "%s - Level: %s - Message: %s - Context: %s", $date, $level, $message, json_encode($context) 74 | ).PHP_EOL; 75 | 76 | $fileName = sprintf("%s/%s-%s.log", $logPath, $env, date("j.n.Y")); 77 | file_put_contents($fileName, $details, FILE_APPEND); 78 | } 79 | } -------------------------------------------------------------------------------- /Src/Repository/Repository.php: -------------------------------------------------------------------------------- 1 | queryBuilder = $queryBuilder; 22 | } 23 | 24 | public function find (int $id): ?object 25 | { 26 | return $this->findOneBy('id', $id); 27 | } 28 | 29 | public function findOneBy (string $field, $value): ?object 30 | { 31 | $result = $this->queryBuilder 32 | ->table(static::$table) 33 | ->select() 34 | ->where($field, $value) 35 | ->runQuery() 36 | ->fetchInto(static::$className); 37 | return ($result) ? $result[0] : null; 38 | } 39 | 40 | public function findBy (array $criteria) 41 | { 42 | $this->queryBuilder->table(static::$table)->select(); 43 | foreach ($criteria as $criterion){ 44 | $this->queryBuilder->where(...$criterion); 45 | } 46 | return $this->queryBuilder->runQuery()->fetchInto(static::$className); 47 | } 48 | 49 | public function findAll () 50 | { 51 | return $this->queryBuilder 52 | ->raw("SELECT * FROM reports") 53 | ->fetchInto(static::$className); 54 | } 55 | 56 | public function sql (string $query) 57 | { 58 | return $this->queryBuilder->raw($query)->fetchInto(static::$className); 59 | } 60 | 61 | public function create (Entity $entity): object 62 | { 63 | $id = $this->queryBuilder->table(static::$table)->create($entity->toArray()); 64 | return $this->find($id); 65 | } 66 | 67 | public function update (Entity $entity, array $conditions = []): object 68 | { 69 | $this->queryBuilder->table(static::$table)->update($entity->toArray()); 70 | foreach ($conditions as $condition){ 71 | $this->queryBuilder->where(...$condition); 72 | } 73 | $this->queryBuilder->where('id', $entity->getId())->runQuery(); 74 | return $this->find($entity->getId()); 75 | } 76 | 77 | public function delete (Entity $entity, array $conditions = []): void 78 | { 79 | $this->queryBuilder->table(static::$table)->delete($entity->toArray()); 80 | foreach ($conditions as $condition){ 81 | $this->queryBuilder->where(...$condition); 82 | } 83 | $this->queryBuilder->where('id', $entity->getId())->runQuery(); 84 | } 85 | } -------------------------------------------------------------------------------- /Tests/Units/RepositoryTest.php: -------------------------------------------------------------------------------- 1 | queryBuilder = DbQueryBuilderFactory::make( 23 | 'database', 'pdo', ['db_name' => 'bug_app_testing'] 24 | ); 25 | $this->queryBuilder->beginTransaction(); 26 | 27 | $this->bugReportRepository = new BugReportRepository($this->queryBuilder); 28 | parent::setUp(); 29 | } 30 | 31 | public function testItCanCreateRecordWithEntity() 32 | { 33 | $newBugReport = $this->createBugReport(); 34 | self::assertInstanceOf(BugReport::class, $newBugReport); 35 | self::assertSame('Type 2', $newBugReport->getReportType()); 36 | self::assertSame('https://testing-link.com', $newBugReport->getLink()); 37 | self::assertSame('This is a dummy message', $newBugReport->getMessage()); 38 | self::assertSame('email@test.com', $newBugReport->getEmail()); 39 | } 40 | 41 | public function testItCanUpdateAGivenEntity() 42 | { 43 | $newBugReport = $this->createBugReport(); 44 | $bugReport = $this->bugReportRepository->find($newBugReport->getId()); 45 | $bugReport 46 | ->setMessage('this is from update method') 47 | ->setLink('https://newlink.com/image.png'); 48 | $updatedReport = $this->bugReportRepository->update($bugReport); 49 | 50 | self::assertInstanceOf(BugReport::class, $updatedReport); 51 | self::assertSame('this is from update method', $updatedReport->getMessage()); 52 | self::assertSame('https://newlink.com/image.png', $updatedReport->getLink()); 53 | } 54 | 55 | public function testItCanDeleteAGivenEntity() 56 | { 57 | $newBugReport = $this->createBugReport(); 58 | $this->bugReportRepository->delete($newBugReport); 59 | $bugReport = $this->bugReportRepository->find($newBugReport->getId()); 60 | self::assertNull($bugReport); 61 | } 62 | 63 | public function testItCanFindByCriteria() 64 | { 65 | $newBugReport = $this->createBugReport(); 66 | $report = $this->bugReportRepository->findBy([ 67 | ['report_type', '=', 'Type 2',], 68 | ['email', 'email@test.com',], 69 | ]); 70 | self::assertIsArray($report); 71 | 72 | /** @var BugReport $bugReport */ 73 | $bugReport = $report[0]; 74 | self::assertSame('Type 2', $bugReport->getReportType()); 75 | self::assertSame('email@test.com', $bugReport->getEmail()); 76 | } 77 | 78 | private function createBugReport(): BugReport 79 | { 80 | $bugReport = new BugReport(); 81 | $bugReport->setReportType('Type 2') 82 | ->setLink('https://testing-link.com') 83 | ->setMessage('This is a dummy message') 84 | ->setEmail('email@test.com'); 85 | 86 | return $this->bugReportRepository->create($bugReport); 87 | } 88 | 89 | public function tearDown() 90 | { 91 | $this->queryBuilder->rollback(); 92 | parent::tearDown(); 93 | } 94 | } -------------------------------------------------------------------------------- /Src/Database/MySQLiQueryBuilder.php: -------------------------------------------------------------------------------- 1 | resultSet){ 23 | $this->resultSet = $this->statement->get_result(); 24 | if($this->resultSet){ 25 | while($object = $this->resultSet->fetch_object()){ 26 | $results[] = $object; 27 | } 28 | $this->results = $results; 29 | } 30 | } 31 | return $this->results; 32 | } 33 | 34 | public function count() 35 | { 36 | if(!$this->resultSet) { 37 | $this->get(); 38 | } 39 | return $this->resultSet ? $this->resultSet->num_rows : false; 40 | } 41 | 42 | public function lastInsertedId() 43 | { 44 | return $this->connection->insert_id; 45 | } 46 | 47 | public function prepare($query) 48 | { 49 | return $this->connection->prepare($query); 50 | } 51 | 52 | public function execute($statement) 53 | { 54 | if(!$statement) { 55 | throw new InvalidArgumentException('MySQLi statement is false'); 56 | } 57 | 58 | if($this->bindings){ 59 | $bindings = $this->parseBindings($this->bindings); 60 | $reflectionObj = new \ReflectionClass('mysqli_stmt'); 61 | $method = $reflectionObj->getMethod('bind_param'); 62 | $method->invokeArgs($statement, $bindings); 63 | } 64 | $statement->execute(); 65 | $this->bindings = []; 66 | $this->placeholders = []; 67 | 68 | return $statement; 69 | } 70 | 71 | private function parseBindings(array $params) 72 | { 73 | $bindings = []; 74 | $count = count($params); 75 | if($count === 0){ 76 | return $this->bindings; 77 | } 78 | 79 | $bindingTypes = $this->parseBindingTypes(); //"sids" 80 | $bindings[] = & $bindingTypes; 81 | for($index = 0; $index < $count; $index++){ 82 | $bindings[] = & $params[$index]; 83 | } 84 | return $bindings; 85 | } 86 | 87 | public function parseBindingTypes() 88 | { 89 | $bindingTypes = []; 90 | foreach ($this->bindings as $binding){ 91 | if(is_int($binding)){ 92 | $bindingTypes[] = self::PARAM_TYPE_INT; 93 | } 94 | if(is_string($binding)){ 95 | $bindingTypes[] = self::PARAM_TYPE_STRING; 96 | } 97 | 98 | if(is_float($binding)){ 99 | $bindingTypes[] = self::PARAM_TYPE_DOUBLE; 100 | } 101 | } 102 | return implode('', $bindingTypes); 103 | } 104 | 105 | public function fetchInto($className) 106 | { 107 | $results = []; 108 | $this->resultSet = $this->statement->get_result(); 109 | while($object = $this->resultSet->fetch_object($className)){ 110 | $results[] = $object; 111 | } 112 | return $this->results = $results; 113 | } 114 | 115 | public function beginTransaction() 116 | { 117 | $this->connection->begin_transaction(); 118 | } 119 | 120 | public function affected() 121 | { 122 | $this->statement->store_result(); 123 | return $this->statement->affected_rows; 124 | } 125 | } -------------------------------------------------------------------------------- /Tests/Functional/CrudTest.php: -------------------------------------------------------------------------------- 1 | queryBuilder = DbQueryBuilderFactory::make(); 26 | $this->repository = new BugReportRepository($this->queryBuilder); 27 | $this->client = new HttpClient(); 28 | parent::setUp(); 29 | } 30 | 31 | public function testItCanCreateReportUsingPostRequest() 32 | { 33 | $postData = $this->getPostData(['add' => true]); 34 | $response = $this->client->post("http://localhost/bug-report-app/Src/add.php", $postData); 35 | $response = json_decode($response, true); 36 | self::assertEquals(200, $response['statusCode']); 37 | 38 | $result = $this->repository->findBy([ 39 | ['report_type', '=', 'Audio'], 40 | ['link', '=', 'https://example.com'], 41 | ['email', '=', 'test@example.com'], 42 | ]); 43 | 44 | /** @var BugReport $bugReport */ 45 | $bugReport = $result[0] ?? []; 46 | self::assertInstanceOf(BugReport::class, $bugReport); 47 | self::assertSame('Audio', $bugReport->getReportType()); 48 | self::assertSame('https://example.com', $bugReport->getLink()); 49 | self::assertSame('test@example.com', $bugReport->getEmail()); 50 | 51 | return $bugReport; 52 | } 53 | 54 | /** @depends testItCanCreateReportUsingPostRequest */ 55 | public function testItCanUpdateReportUsingPostRequest(BugReport $bugReport) 56 | { 57 | $postData = $this->getPostData([ 58 | 'update' => true, 59 | 'message' => 'The video on PHP OOP has audio issues, please check and fix it', 60 | 'link' => 'https://updated.com', 61 | 'reportId' => $bugReport->getId(), 62 | ]); 63 | $response = $this->client->post("http://localhost/bug-report-app/Src/update.php", $postData); 64 | $response = json_decode($response, true); 65 | self::assertEquals(200, $response['statusCode']); 66 | 67 | /** @var BugReport $result */ 68 | $result = $this->repository->find($bugReport->getId()); 69 | 70 | self::assertInstanceOf(BugReport::class, $result); 71 | self::assertSame( 72 | 'The video on PHP OOP has audio issues, please check and fix it', 73 | $result->getMessage() 74 | ); 75 | self::assertSame('https://updated.com', $result->getLink()); 76 | 77 | return $result; 78 | } 79 | 80 | /** @depends testItCanUpdateReportUsingPostRequest */ 81 | public function testItCanDeleteReportUsingPostRequest(BugReport $bugReport) 82 | { 83 | $postData = [ 84 | 'delete' => true, 85 | 'reportId' => $bugReport->getId(), 86 | ]; 87 | $response = $this->client->post("http://localhost/bug-report-app/Src/delete.php", $postData); 88 | $response = json_decode($response, true); 89 | self::assertEquals(200, $response['statusCode']); 90 | 91 | /** @var BugReport $result */ 92 | $result = $this->repository->find($bugReport->getId()); 93 | self::assertNull($result); 94 | } 95 | 96 | private function getPostData(array $options = []): array 97 | { 98 | return array_merge([ 99 | 'reportType' => 'Audio', 100 | 'message' => 'The video on xxx has audio issues, please check and fix it', 101 | 'email' => 'test@example.com', 102 | 'link' => 'https://example.com', 103 | ], $options); 104 | } 105 | } -------------------------------------------------------------------------------- /Tests/Units/QueryBuilderTest.php: -------------------------------------------------------------------------------- 1 | queryBuilder = DbQueryBuilderFactory::make( 21 | 'database', 'pdo', ['db_name' => 'bug_app_testing'] 22 | ); 23 | $this->queryBuilder->beginTransaction(); 24 | parent::setUp(); 25 | } 26 | 27 | public function testItCanCreateRecords() 28 | { 29 | $id = $this->insertIntoTable(); 30 | self::assertNotNull($id); 31 | } 32 | 33 | public function testInCanPerformRawQuery() 34 | { 35 | $id = $this->insertIntoTable(); 36 | $result = $this->queryBuilder->raw("SELECT * FROM reports;")->get(); 37 | self::assertNotNull($result); 38 | } 39 | 40 | public function testItCanPerformSelectQuery() 41 | { 42 | $id = $this->insertIntoTable(); 43 | $result = $this->queryBuilder 44 | ->table('reports') 45 | ->select('*') 46 | ->where('id', $id) 47 | ->runQuery() 48 | ->first(); 49 | 50 | self::assertNotNull($result); 51 | self::assertSame($id, (int) $result->id); 52 | } 53 | 54 | public function testItCanPerformSelectQueryWithMultipleWhereClause() 55 | { 56 | $id = $this->insertIntoTable(); 57 | $result = $this->queryBuilder 58 | ->table('reports') 59 | ->select('*') 60 | ->where('id', $id) 61 | ->where('report_type', 'Report Type 1') 62 | ->runQuery() 63 | ->first(); 64 | self::assertNotNull($result); 65 | self::assertSame($id, (int) $result->id); 66 | self::assertSame('Report Type 1', $result->report_type); 67 | } 68 | 69 | public function testItCanFindById() 70 | { 71 | $id = $this->insertIntoTable(); 72 | $result = $this->queryBuilder->table('reports') 73 | ->select('*')->find($id); 74 | self::assertNotNull($result); 75 | self::assertSame($id, (int) $result->id); 76 | self::assertSame('Report Type 1', $result->report_type); 77 | } 78 | 79 | public function testItCanFindOneByGivenValue() 80 | { 81 | $id = $this->insertIntoTable(); 82 | $result = $this->queryBuilder->table('reports') 83 | ->select('*')->findOneBy('report_type', 'Report Type 1'); 84 | self::assertNotNull($result); 85 | self::assertSame($id, (int) $result->id); 86 | self::assertSame('Report Type 1', $result->report_type); 87 | } 88 | 89 | public function testItCanUpdateGivenRecord() 90 | { 91 | $id = $this->insertIntoTable(); 92 | $count = $this->queryBuilder->table('reports')->update([ 93 | 'report_type' => 'Report Type 1 updated' 94 | ])->where('id', $id)->runQuery()->affected(); 95 | 96 | self::assertEquals(1, $count); 97 | $result = $this->queryBuilder->select('*')->find($id); 98 | self::assertNotNull($result); 99 | self::assertSame($id, (int) $result->id); 100 | self::assertSame('Report Type 1 updated', $result->report_type); 101 | } 102 | public function testItCanDeleteGivenId() 103 | { 104 | $id = $this->insertIntoTable(); 105 | $count = $this->queryBuilder->table('reports')->delete() 106 | ->where('id', $id)->runQuery()->affected(); 107 | 108 | self::assertEquals(1, $count); 109 | $result = $this->queryBuilder->select('*')->find($id); 110 | self::assertNull($result); 111 | } 112 | 113 | 114 | public function tearDown() 115 | { 116 | $this->queryBuilder->rollback(); 117 | parent::tearDown(); 118 | } 119 | 120 | /** 121 | * @return mixed 122 | */ 123 | private function insertIntoTable() 124 | { 125 | $data = [ 126 | 'report_type' => 'Report Type 1', 127 | 'message' => 'This is a dummy message', 128 | 'email' => 'support@devscreencast', 129 | 'link' => 'https://link.com', 130 | 'created_at' => date('Y-m-d H:i:s'), 131 | ]; 132 | return $this->queryBuilder->table('reports')->create($data); 133 | } 134 | } -------------------------------------------------------------------------------- /Src/Database/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | =', '>', '<=', '<', '<>']; 22 | const PLACEHOLDER = '?'; 23 | const COLUMNS = '*'; 24 | const DML_TYPE_SELECT = 'SELECT'; 25 | const DML_TYPE_INSERT = 'INSERT'; 26 | const DML_TYPE_UPDATE = 'UPDATE'; 27 | const DML_TYPE_DELETE = 'DELETE'; 28 | 29 | use Query; 30 | 31 | public function __construct(DatabaseConnectionInterface $databaseConnection) 32 | { 33 | $this->connection = $databaseConnection->getConnection(); 34 | } 35 | 36 | public function table($table) 37 | { 38 | $this->table = $table; 39 | return $this; 40 | } 41 | 42 | public function where($column, $operator = self::OPERATORS[0], $value = null) 43 | { 44 | if(!in_array($operator, self::OPERATORS)){ 45 | if($value === null){ 46 | $value = $operator; 47 | $operator = self::OPERATORS[0]; 48 | }else{ 49 | throw new InvalidArgumentException('Operator is not valid', ['operator' => $operator]); 50 | } 51 | } 52 | $this->parseWhere([$column => $value], $operator); 53 | 54 | return $this; 55 | } 56 | 57 | private function parseWhere(array $conditions, string $operator) 58 | { 59 | foreach ($conditions as $column => $value){ 60 | $this->placeholders[] = sprintf('%s %s %s', $column, $operator, self::PLACEHOLDER); 61 | $this->bindings[] = $value; 62 | } 63 | return $this; 64 | } 65 | 66 | public function select(string $fields = self::COLUMNS) 67 | { 68 | $this->operation = self::DML_TYPE_SELECT; 69 | $this->fields = $fields; 70 | return $this; 71 | } 72 | 73 | public function create(array $data) 74 | { 75 | $this->fields = '`' . implode('`, `', array_keys($data)) . '`'; 76 | foreach ($data as $value){ 77 | $this->placeholders[] = self::PLACEHOLDER; 78 | $this->bindings[] = $value; 79 | } 80 | $query = $this->prepare($this->getQuery(self::DML_TYPE_INSERT)); 81 | $this->statement = $this->execute($query); 82 | 83 | return (int) $this->lastInsertedId(); 84 | } 85 | 86 | public function update(array $data) 87 | { 88 | $this->fields = []; 89 | $this->operation = self::DML_TYPE_UPDATE; 90 | foreach ($data as $column => $value){ 91 | $this->fields[] = sprintf('%s%s%s', $column, self::OPERATORS[0], "'$value'"); 92 | } 93 | return $this; 94 | } 95 | 96 | public function delete() 97 | { 98 | $this->operation = self::DML_TYPE_DELETE; 99 | return $this; 100 | } 101 | 102 | public function raw($query) 103 | { 104 | $query = $this->prepare($query); 105 | $this->statement = $this->execute($query); 106 | return $this; 107 | } 108 | 109 | public function find($id) 110 | { 111 | return $this->where('id', $id)->runQuery()->first(); 112 | } 113 | 114 | public function findOneBy(string $field, $value) 115 | { 116 | return $this->where($field, $value)->runQuery()->first(); 117 | } 118 | 119 | public function first() 120 | { 121 | return $this->count() ? $this->get()[0] : null; 122 | } 123 | 124 | public function rollback(): void 125 | { 126 | $this->connection->rollback(); 127 | } 128 | 129 | public function runQuery() 130 | { 131 | $query = $this->prepare($this->getQuery($this->operation)); 132 | $this->statement = $this->execute($query); 133 | return $this; 134 | } 135 | 136 | abstract public function get(); 137 | abstract public function count(); 138 | abstract public function lastInsertedId(); 139 | abstract public function prepare($query); 140 | abstract public function execute($statement); 141 | abstract public function fetchInto($className); 142 | abstract public function beginTransaction(); 143 | abstract public function affected(); 144 | } -------------------------------------------------------------------------------- /Resources/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #566787; 3 | background: #f5f5f5; 4 | font-family: 'Varela Round', sans-serif; 5 | font-size: 13px; 6 | } 7 | .table-wrapper { 8 | background: #fff; 9 | padding: 20px 25px; 10 | margin: 30px 0; 11 | border-radius: 3px; 12 | box-shadow: 0 1px 1px rgba(0,0,0,.05); 13 | } 14 | .table-title { 15 | padding-bottom: 15px; 16 | background: #435d7d; 17 | color: #fff; 18 | padding: 16px 30px; 19 | margin: -20px -25px 10px; 20 | border-radius: 3px 3px 0 0; 21 | } 22 | .table-title h2 { 23 | margin: 5px 0 0; 24 | font-size: 24px; 25 | } 26 | .table-title .btn-group { 27 | float: right; 28 | } 29 | .table-title .btn { 30 | color: #fff; 31 | float: right; 32 | font-size: 13px; 33 | border: none; 34 | min-width: 50px; 35 | border-radius: 2px; 36 | border: none; 37 | outline: none !important; 38 | margin-left: 10px; 39 | } 40 | .table-title .btn i { 41 | float: left; 42 | font-size: 21px; 43 | margin-right: 5px; 44 | } 45 | .table-title .btn span { 46 | float: left; 47 | margin-top: 2px; 48 | } 49 | table.table tr th, table.table tr td { 50 | border-color: #e9e9e9; 51 | padding: 12px 15px; 52 | vertical-align: middle; 53 | } 54 | table.table tr th:first-child { 55 | width: 60px; 56 | } 57 | table.table tr th:last-child { 58 | width: 100px; 59 | } 60 | table.table-striped tbody tr:nth-of-type(odd) { 61 | background-color: #fcfcfc; 62 | } 63 | table.table-striped.table-hover tbody tr:hover { 64 | background: #f5f5f5; 65 | } 66 | table.table th i { 67 | font-size: 13px; 68 | margin: 0 5px; 69 | cursor: pointer; 70 | } 71 | table.table td:last-child i { 72 | opacity: 0.9; 73 | font-size: 22px; 74 | margin: 0 5px; 75 | } 76 | table.table td a { 77 | font-weight: bold; 78 | color: #566787; 79 | display: inline-block; 80 | text-decoration: none; 81 | outline: none !important; 82 | } 83 | table.table td a:hover { 84 | color: #2196F3; 85 | } 86 | table.table td a.edit { 87 | color: #FFC107; 88 | } 89 | table.table td a.delete { 90 | color: #F44336; 91 | } 92 | table.table td i { 93 | font-size: 19px; 94 | } 95 | table.table .avatar { 96 | border-radius: 50%; 97 | vertical-align: middle; 98 | margin-right: 10px; 99 | } 100 | .pagination { 101 | float: right; 102 | margin: 0 0 5px; 103 | } 104 | .pagination li a { 105 | border: none; 106 | font-size: 13px; 107 | min-width: 30px; 108 | min-height: 30px; 109 | color: #999; 110 | margin: 0 2px; 111 | line-height: 30px; 112 | border-radius: 2px !important; 113 | text-align: center; 114 | padding: 0 6px; 115 | } 116 | .pagination li a:hover { 117 | color: #666; 118 | } 119 | .pagination li.active a, .pagination li.active a.page-link { 120 | background: #03A9F4; 121 | } 122 | .pagination li.active a:hover { 123 | background: #0397d6; 124 | } 125 | .pagination li.disabled i { 126 | color: #ccc; 127 | } 128 | .pagination li i { 129 | font-size: 16px; 130 | padding-top: 6px 131 | } 132 | .hint-text { 133 | float: left; 134 | margin-top: 10px; 135 | font-size: 13px; 136 | } 137 | /* Custom checkbox */ 138 | .custom-checkbox { 139 | position: relative; 140 | } 141 | .custom-checkbox input[type="checkbox"] { 142 | opacity: 0; 143 | position: absolute; 144 | margin: 5px 0 0 3px; 145 | z-index: 9; 146 | } 147 | .custom-checkbox label:before{ 148 | width: 18px; 149 | height: 18px; 150 | } 151 | .custom-checkbox label:before { 152 | content: ''; 153 | margin-right: 10px; 154 | display: inline-block; 155 | vertical-align: text-top; 156 | background: white; 157 | border: 1px solid #bbb; 158 | border-radius: 2px; 159 | box-sizing: border-box; 160 | z-index: 2; 161 | } 162 | .custom-checkbox input[type="checkbox"]:checked + label:after { 163 | content: ''; 164 | position: absolute; 165 | left: 6px; 166 | top: 3px; 167 | width: 6px; 168 | height: 11px; 169 | border: solid #000; 170 | border-width: 0 3px 3px 0; 171 | transform: inherit; 172 | z-index: 3; 173 | transform: rotateZ(45deg); 174 | } 175 | .custom-checkbox input[type="checkbox"]:checked + label:before { 176 | border-color: #03A9F4; 177 | background: #03A9F4; 178 | } 179 | .custom-checkbox input[type="checkbox"]:checked + label:after { 180 | border-color: #fff; 181 | } 182 | .custom-checkbox input[type="checkbox"]:disabled + label:before { 183 | color: #b8b8b8; 184 | cursor: auto; 185 | box-shadow: none; 186 | background: #ddd; 187 | } 188 | /* Modal styles */ 189 | .modal .modal-dialog { 190 | max-width: 400px; 191 | } 192 | .modal .modal-header, .modal .modal-body, .modal .modal-footer { 193 | padding: 20px 30px; 194 | } 195 | .modal .modal-content { 196 | border-radius: 3px; 197 | } 198 | .modal .modal-footer { 199 | background: #ecf0f1; 200 | border-radius: 0 0 3px 3px; 201 | } 202 | .modal .modal-title { 203 | display: inline-block; 204 | } 205 | .modal .form-control { 206 | border-radius: 2px; 207 | box-shadow: none; 208 | border-color: #dddddd; 209 | } 210 | .modal textarea.form-control { 211 | resize: vertical; 212 | } 213 | .modal .btn { 214 | border-radius: 2px; 215 | min-width: 100px; 216 | } 217 | .modal form label { 218 | font-weight: normal; 219 | } -------------------------------------------------------------------------------- /store.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 4.8.3 3 | -- https://www.phpmyadmin.net/ 4 | -- 5 | -- Host: 127.0.0.1 6 | -- Generation Time: Mar 30, 2019 at 10:11 PM 7 | -- Server version: 10.1.36-MariaDB 8 | -- PHP Version: 7.2.11 9 | 10 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 11 | SET AUTOCOMMIT = 0; 12 | START TRANSACTION; 13 | SET time_zone = "+00:00"; 14 | 15 | 16 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 17 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 18 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 19 | /*!40101 SET NAMES utf8mb4 */; 20 | 21 | -- 22 | -- Database: `store` 23 | -- 24 | 25 | -- -------------------------------------------------------- 26 | 27 | -- 28 | -- Table structure for table `categories` 29 | -- 30 | 31 | CREATE TABLE `categories` ( 32 | `id` int(11) NOT NULL, 33 | `name` varchar(255) NOT NULL, 34 | `slug` varchar(255) NOT NULL, 35 | `created_at` timestamp NULL DEFAULT NULL, 36 | `updated_at` timestamp NULL DEFAULT NULL, 37 | `deleted_at` timestamp NULL DEFAULT NULL 38 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 39 | 40 | -- 41 | -- Dumping data for table `categories` 42 | -- 43 | 44 | INSERT INTO `categories` (`id`, `name`, `slug`, `created_at`, `updated_at`, `deleted_at`) VALUES 45 | (1, 'Trousers', 'trousers', '2019-03-27 19:28:22', '2019-03-27 19:28:22', NULL), 46 | (2, 'Bottoms', 'bottoms', '2019-03-27 19:28:38', '2019-03-27 19:28:38', NULL), 47 | (3, 'Tops', 'tops', '2019-03-27 19:28:49', '2019-03-27 19:28:49', NULL), 48 | (4, 'Accessories', 'accessories', '2019-03-27 19:29:01', '2019-03-27 19:29:01', NULL), 49 | (5, 'Shoes', 'shoes', '2019-03-27 19:29:22', '2019-03-27 19:29:22', NULL); 50 | 51 | -- -------------------------------------------------------- 52 | 53 | -- 54 | -- Table structure for table `orders` 55 | -- 56 | 57 | CREATE TABLE `orders` ( 58 | `id` int(11) NOT NULL, 59 | `user_id` int(11) NOT NULL, 60 | `product_id` int(11) NOT NULL, 61 | `unit_price` float NOT NULL, 62 | `total` float NOT NULL, 63 | `status` varchar(255) NOT NULL, 64 | `order_no` varchar(255) DEFAULT NULL, 65 | `created_at` timestamp NULL DEFAULT NULL, 66 | `updated_at` timestamp NULL DEFAULT NULL, 67 | `deleted_at` timestamp NULL DEFAULT NULL 68 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 69 | 70 | -- -------------------------------------------------------- 71 | 72 | -- 73 | -- Table structure for table `payments` 74 | -- 75 | 76 | CREATE TABLE `payments` ( 77 | `id` int(11) NOT NULL, 78 | `user_id` int(255) NOT NULL, 79 | `order_no` varchar(255) NOT NULL, 80 | `amount` float NOT NULL, 81 | `status` varchar(255) NOT NULL, 82 | `created_at` timestamp NULL DEFAULT NULL, 83 | `updated_at` timestamp NULL DEFAULT NULL, 84 | `deleted_at` timestamp NULL DEFAULT NULL 85 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 86 | 87 | -- -------------------------------------------------------- 88 | 89 | -- 90 | -- Table structure for table `products` 91 | -- 92 | 93 | CREATE TABLE `products` ( 94 | `id` int(11) NOT NULL, 95 | `name` varchar(255) NOT NULL, 96 | `price` float NOT NULL, 97 | `description` text NOT NULL, 98 | `category_id` int(11) NOT NULL, 99 | `sub_category_id` int(11) NOT NULL, 100 | `image_path` varchar(255) NOT NULL, 101 | `created_at` timestamp NULL DEFAULT NULL, 102 | `updated_at` timestamp NULL DEFAULT NULL, 103 | `deleted_at` timestamp NULL DEFAULT NULL 104 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 105 | 106 | -- -------------------------------------------------------- 107 | 108 | -- 109 | -- Table structure for table `sub_categories` 110 | -- 111 | 112 | CREATE TABLE `sub_categories` ( 113 | `id` int(11) NOT NULL, 114 | `name` varchar(255) NOT NULL, 115 | `slug` varchar(255) NOT NULL, 116 | `category_id` int(11) NOT NULL, 117 | `created_at` timestamp NULL DEFAULT NULL, 118 | `updated_at` timestamp NULL DEFAULT NULL, 119 | `deleted_at` timestamp NULL DEFAULT NULL 120 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 121 | 122 | -- 123 | -- Dumping data for table `sub_categories` 124 | -- 125 | 126 | INSERT INTO `sub_categories` (`id`, `name`, `slug`, `category_id`, `created_at`, `updated_at`, `deleted_at`) VALUES 127 | (3, 'Sneakers', 'sneakers', 5, '2019-03-29 00:48:16', '2019-03-29 00:48:16', NULL), 128 | (4, 'loafers', 'loafers', 5, '2019-03-29 00:49:20', '2019-03-29 00:49:20', NULL); 129 | 130 | -- -------------------------------------------------------- 131 | 132 | -- 133 | -- Table structure for table `users` 134 | -- 135 | 136 | CREATE TABLE `users` ( 137 | `id` int(11) NOT NULL, 138 | `username` varchar(255) NOT NULL, 139 | `fullname` varchar(255) NOT NULL, 140 | `email` varchar(255) NOT NULL, 141 | `password` varchar(255) NOT NULL, 142 | `address` text NOT NULL, 143 | `role` varchar(50) NOT NULL, 144 | `created_at` timestamp NULL DEFAULT NULL, 145 | `updated_at` timestamp NULL DEFAULT NULL, 146 | `deleted_at` timestamp NULL DEFAULT NULL 147 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 148 | 149 | -- 150 | -- Indexes for dumped tables 151 | -- 152 | 153 | -- 154 | -- Indexes for table `categories` 155 | -- 156 | ALTER TABLE `categories` 157 | ADD PRIMARY KEY (`id`); 158 | 159 | -- 160 | -- Indexes for table `orders` 161 | -- 162 | ALTER TABLE `orders` 163 | ADD PRIMARY KEY (`id`); 164 | 165 | -- 166 | -- Indexes for table `payments` 167 | -- 168 | ALTER TABLE `payments` 169 | ADD PRIMARY KEY (`id`); 170 | 171 | -- 172 | -- Indexes for table `products` 173 | -- 174 | ALTER TABLE `products` 175 | ADD PRIMARY KEY (`id`), 176 | ADD UNIQUE KEY `name` (`name`); 177 | 178 | -- 179 | -- Indexes for table `sub_categories` 180 | -- 181 | ALTER TABLE `sub_categories` 182 | ADD PRIMARY KEY (`id`); 183 | 184 | -- 185 | -- Indexes for table `users` 186 | -- 187 | ALTER TABLE `users` 188 | ADD PRIMARY KEY (`id`); 189 | 190 | -- 191 | -- AUTO_INCREMENT for dumped tables 192 | -- 193 | 194 | -- 195 | -- AUTO_INCREMENT for table `categories` 196 | -- 197 | ALTER TABLE `categories` 198 | MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; 199 | 200 | -- 201 | -- AUTO_INCREMENT for table `sub_categories` 202 | -- 203 | ALTER TABLE `sub_categories` 204 | MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; 205 | COMMIT; 206 | 207 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 208 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 209 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 210 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Bug Report App 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 |
25 |

Manage Bug Reports

26 |
27 |
28 | Add Report 29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 120 | 121 | 122 | 123 | 124 | 125 |
Report TypeEmailMessageLinkActions
getReportType(); ?>getEmail(); ?>getMessage(); ?>getLink(); ?> 52 | 53 | 54 | 55 | 56 | 96 | 97 | 98 | 119 |
126 |
127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "6eb683164343ed0e11c8dd28342af671", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.2.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "a2c590166b2133a4633738648b6b064edae0814a" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", 21 | "reference": "a2c590166b2133a4633738648b6b064edae0814a", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1" 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 | "time": "2019-03-17T17:37:11+00:00" 65 | }, 66 | { 67 | "name": "myclabs/deep-copy", 68 | "version": "1.8.1", 69 | "source": { 70 | "type": "git", 71 | "url": "https://github.com/myclabs/DeepCopy.git", 72 | "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" 73 | }, 74 | "dist": { 75 | "type": "zip", 76 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", 77 | "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", 78 | "shasum": "" 79 | }, 80 | "require": { 81 | "php": "^7.1" 82 | }, 83 | "replace": { 84 | "myclabs/deep-copy": "self.version" 85 | }, 86 | "require-dev": { 87 | "doctrine/collections": "^1.0", 88 | "doctrine/common": "^2.6", 89 | "phpunit/phpunit": "^7.1" 90 | }, 91 | "type": "library", 92 | "autoload": { 93 | "psr-4": { 94 | "DeepCopy\\": "src/DeepCopy/" 95 | }, 96 | "files": [ 97 | "src/DeepCopy/deep_copy.php" 98 | ] 99 | }, 100 | "notification-url": "https://packagist.org/downloads/", 101 | "license": [ 102 | "MIT" 103 | ], 104 | "description": "Create deep copies (clones) of your objects", 105 | "keywords": [ 106 | "clone", 107 | "copy", 108 | "duplicate", 109 | "object", 110 | "object graph" 111 | ], 112 | "time": "2018-06-11T23:09:50+00:00" 113 | }, 114 | { 115 | "name": "phar-io/manifest", 116 | "version": "1.0.3", 117 | "source": { 118 | "type": "git", 119 | "url": "https://github.com/phar-io/manifest.git", 120 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" 121 | }, 122 | "dist": { 123 | "type": "zip", 124 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 125 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 126 | "shasum": "" 127 | }, 128 | "require": { 129 | "ext-dom": "*", 130 | "ext-phar": "*", 131 | "phar-io/version": "^2.0", 132 | "php": "^5.6 || ^7.0" 133 | }, 134 | "type": "library", 135 | "extra": { 136 | "branch-alias": { 137 | "dev-master": "1.0.x-dev" 138 | } 139 | }, 140 | "autoload": { 141 | "classmap": [ 142 | "src/" 143 | ] 144 | }, 145 | "notification-url": "https://packagist.org/downloads/", 146 | "license": [ 147 | "BSD-3-Clause" 148 | ], 149 | "authors": [ 150 | { 151 | "name": "Arne Blankerts", 152 | "email": "arne@blankerts.de", 153 | "role": "Developer" 154 | }, 155 | { 156 | "name": "Sebastian Heuer", 157 | "email": "sebastian@phpeople.de", 158 | "role": "Developer" 159 | }, 160 | { 161 | "name": "Sebastian Bergmann", 162 | "email": "sebastian@phpunit.de", 163 | "role": "Developer" 164 | } 165 | ], 166 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 167 | "time": "2018-07-08T19:23:20+00:00" 168 | }, 169 | { 170 | "name": "phar-io/version", 171 | "version": "2.0.1", 172 | "source": { 173 | "type": "git", 174 | "url": "https://github.com/phar-io/version.git", 175 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" 176 | }, 177 | "dist": { 178 | "type": "zip", 179 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", 180 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", 181 | "shasum": "" 182 | }, 183 | "require": { 184 | "php": "^5.6 || ^7.0" 185 | }, 186 | "type": "library", 187 | "autoload": { 188 | "classmap": [ 189 | "src/" 190 | ] 191 | }, 192 | "notification-url": "https://packagist.org/downloads/", 193 | "license": [ 194 | "BSD-3-Clause" 195 | ], 196 | "authors": [ 197 | { 198 | "name": "Arne Blankerts", 199 | "email": "arne@blankerts.de", 200 | "role": "Developer" 201 | }, 202 | { 203 | "name": "Sebastian Heuer", 204 | "email": "sebastian@phpeople.de", 205 | "role": "Developer" 206 | }, 207 | { 208 | "name": "Sebastian Bergmann", 209 | "email": "sebastian@phpunit.de", 210 | "role": "Developer" 211 | } 212 | ], 213 | "description": "Library for handling version information and constraints", 214 | "time": "2018-07-08T19:19:57+00:00" 215 | }, 216 | { 217 | "name": "phpdocumentor/reflection-common", 218 | "version": "1.0.1", 219 | "source": { 220 | "type": "git", 221 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 222 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" 223 | }, 224 | "dist": { 225 | "type": "zip", 226 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 227 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 228 | "shasum": "" 229 | }, 230 | "require": { 231 | "php": ">=5.5" 232 | }, 233 | "require-dev": { 234 | "phpunit/phpunit": "^4.6" 235 | }, 236 | "type": "library", 237 | "extra": { 238 | "branch-alias": { 239 | "dev-master": "1.0.x-dev" 240 | } 241 | }, 242 | "autoload": { 243 | "psr-4": { 244 | "phpDocumentor\\Reflection\\": [ 245 | "src" 246 | ] 247 | } 248 | }, 249 | "notification-url": "https://packagist.org/downloads/", 250 | "license": [ 251 | "MIT" 252 | ], 253 | "authors": [ 254 | { 255 | "name": "Jaap van Otterdijk", 256 | "email": "opensource@ijaap.nl" 257 | } 258 | ], 259 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 260 | "homepage": "http://www.phpdoc.org", 261 | "keywords": [ 262 | "FQSEN", 263 | "phpDocumentor", 264 | "phpdoc", 265 | "reflection", 266 | "static analysis" 267 | ], 268 | "time": "2017-09-11T18:02:19+00:00" 269 | }, 270 | { 271 | "name": "phpdocumentor/reflection-docblock", 272 | "version": "4.3.0", 273 | "source": { 274 | "type": "git", 275 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 276 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08" 277 | }, 278 | "dist": { 279 | "type": "zip", 280 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", 281 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08", 282 | "shasum": "" 283 | }, 284 | "require": { 285 | "php": "^7.0", 286 | "phpdocumentor/reflection-common": "^1.0.0", 287 | "phpdocumentor/type-resolver": "^0.4.0", 288 | "webmozart/assert": "^1.0" 289 | }, 290 | "require-dev": { 291 | "doctrine/instantiator": "~1.0.5", 292 | "mockery/mockery": "^1.0", 293 | "phpunit/phpunit": "^6.4" 294 | }, 295 | "type": "library", 296 | "extra": { 297 | "branch-alias": { 298 | "dev-master": "4.x-dev" 299 | } 300 | }, 301 | "autoload": { 302 | "psr-4": { 303 | "phpDocumentor\\Reflection\\": [ 304 | "src/" 305 | ] 306 | } 307 | }, 308 | "notification-url": "https://packagist.org/downloads/", 309 | "license": [ 310 | "MIT" 311 | ], 312 | "authors": [ 313 | { 314 | "name": "Mike van Riel", 315 | "email": "me@mikevanriel.com" 316 | } 317 | ], 318 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 319 | "time": "2017-11-30T07:14:17+00:00" 320 | }, 321 | { 322 | "name": "phpdocumentor/type-resolver", 323 | "version": "0.4.0", 324 | "source": { 325 | "type": "git", 326 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 327 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" 328 | }, 329 | "dist": { 330 | "type": "zip", 331 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", 332 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", 333 | "shasum": "" 334 | }, 335 | "require": { 336 | "php": "^5.5 || ^7.0", 337 | "phpdocumentor/reflection-common": "^1.0" 338 | }, 339 | "require-dev": { 340 | "mockery/mockery": "^0.9.4", 341 | "phpunit/phpunit": "^5.2||^4.8.24" 342 | }, 343 | "type": "library", 344 | "extra": { 345 | "branch-alias": { 346 | "dev-master": "1.0.x-dev" 347 | } 348 | }, 349 | "autoload": { 350 | "psr-4": { 351 | "phpDocumentor\\Reflection\\": [ 352 | "src/" 353 | ] 354 | } 355 | }, 356 | "notification-url": "https://packagist.org/downloads/", 357 | "license": [ 358 | "MIT" 359 | ], 360 | "authors": [ 361 | { 362 | "name": "Mike van Riel", 363 | "email": "me@mikevanriel.com" 364 | } 365 | ], 366 | "time": "2017-07-14T14:27:02+00:00" 367 | }, 368 | { 369 | "name": "phpspec/prophecy", 370 | "version": "1.8.0", 371 | "source": { 372 | "type": "git", 373 | "url": "https://github.com/phpspec/prophecy.git", 374 | "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" 375 | }, 376 | "dist": { 377 | "type": "zip", 378 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", 379 | "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", 380 | "shasum": "" 381 | }, 382 | "require": { 383 | "doctrine/instantiator": "^1.0.2", 384 | "php": "^5.3|^7.0", 385 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", 386 | "sebastian/comparator": "^1.1|^2.0|^3.0", 387 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 388 | }, 389 | "require-dev": { 390 | "phpspec/phpspec": "^2.5|^3.2", 391 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" 392 | }, 393 | "type": "library", 394 | "extra": { 395 | "branch-alias": { 396 | "dev-master": "1.8.x-dev" 397 | } 398 | }, 399 | "autoload": { 400 | "psr-0": { 401 | "Prophecy\\": "src/" 402 | } 403 | }, 404 | "notification-url": "https://packagist.org/downloads/", 405 | "license": [ 406 | "MIT" 407 | ], 408 | "authors": [ 409 | { 410 | "name": "Konstantin Kudryashov", 411 | "email": "ever.zet@gmail.com", 412 | "homepage": "http://everzet.com" 413 | }, 414 | { 415 | "name": "Marcello Duarte", 416 | "email": "marcello.duarte@gmail.com" 417 | } 418 | ], 419 | "description": "Highly opinionated mocking framework for PHP 5.3+", 420 | "homepage": "https://github.com/phpspec/prophecy", 421 | "keywords": [ 422 | "Double", 423 | "Dummy", 424 | "fake", 425 | "mock", 426 | "spy", 427 | "stub" 428 | ], 429 | "time": "2018-08-05T17:53:17+00:00" 430 | }, 431 | { 432 | "name": "phpunit/php-code-coverage", 433 | "version": "6.1.4", 434 | "source": { 435 | "type": "git", 436 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 437 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" 438 | }, 439 | "dist": { 440 | "type": "zip", 441 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 442 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 443 | "shasum": "" 444 | }, 445 | "require": { 446 | "ext-dom": "*", 447 | "ext-xmlwriter": "*", 448 | "php": "^7.1", 449 | "phpunit/php-file-iterator": "^2.0", 450 | "phpunit/php-text-template": "^1.2.1", 451 | "phpunit/php-token-stream": "^3.0", 452 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 453 | "sebastian/environment": "^3.1 || ^4.0", 454 | "sebastian/version": "^2.0.1", 455 | "theseer/tokenizer": "^1.1" 456 | }, 457 | "require-dev": { 458 | "phpunit/phpunit": "^7.0" 459 | }, 460 | "suggest": { 461 | "ext-xdebug": "^2.6.0" 462 | }, 463 | "type": "library", 464 | "extra": { 465 | "branch-alias": { 466 | "dev-master": "6.1-dev" 467 | } 468 | }, 469 | "autoload": { 470 | "classmap": [ 471 | "src/" 472 | ] 473 | }, 474 | "notification-url": "https://packagist.org/downloads/", 475 | "license": [ 476 | "BSD-3-Clause" 477 | ], 478 | "authors": [ 479 | { 480 | "name": "Sebastian Bergmann", 481 | "email": "sebastian@phpunit.de", 482 | "role": "lead" 483 | } 484 | ], 485 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 486 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 487 | "keywords": [ 488 | "coverage", 489 | "testing", 490 | "xunit" 491 | ], 492 | "time": "2018-10-31T16:06:48+00:00" 493 | }, 494 | { 495 | "name": "phpunit/php-file-iterator", 496 | "version": "2.0.2", 497 | "source": { 498 | "type": "git", 499 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 500 | "reference": "050bedf145a257b1ff02746c31894800e5122946" 501 | }, 502 | "dist": { 503 | "type": "zip", 504 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", 505 | "reference": "050bedf145a257b1ff02746c31894800e5122946", 506 | "shasum": "" 507 | }, 508 | "require": { 509 | "php": "^7.1" 510 | }, 511 | "require-dev": { 512 | "phpunit/phpunit": "^7.1" 513 | }, 514 | "type": "library", 515 | "extra": { 516 | "branch-alias": { 517 | "dev-master": "2.0.x-dev" 518 | } 519 | }, 520 | "autoload": { 521 | "classmap": [ 522 | "src/" 523 | ] 524 | }, 525 | "notification-url": "https://packagist.org/downloads/", 526 | "license": [ 527 | "BSD-3-Clause" 528 | ], 529 | "authors": [ 530 | { 531 | "name": "Sebastian Bergmann", 532 | "email": "sebastian@phpunit.de", 533 | "role": "lead" 534 | } 535 | ], 536 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 537 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 538 | "keywords": [ 539 | "filesystem", 540 | "iterator" 541 | ], 542 | "time": "2018-09-13T20:33:42+00:00" 543 | }, 544 | { 545 | "name": "phpunit/php-text-template", 546 | "version": "1.2.1", 547 | "source": { 548 | "type": "git", 549 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 550 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 551 | }, 552 | "dist": { 553 | "type": "zip", 554 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 555 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 556 | "shasum": "" 557 | }, 558 | "require": { 559 | "php": ">=5.3.3" 560 | }, 561 | "type": "library", 562 | "autoload": { 563 | "classmap": [ 564 | "src/" 565 | ] 566 | }, 567 | "notification-url": "https://packagist.org/downloads/", 568 | "license": [ 569 | "BSD-3-Clause" 570 | ], 571 | "authors": [ 572 | { 573 | "name": "Sebastian Bergmann", 574 | "email": "sebastian@phpunit.de", 575 | "role": "lead" 576 | } 577 | ], 578 | "description": "Simple template engine.", 579 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 580 | "keywords": [ 581 | "template" 582 | ], 583 | "time": "2015-06-21T13:50:34+00:00" 584 | }, 585 | { 586 | "name": "phpunit/php-timer", 587 | "version": "2.1.1", 588 | "source": { 589 | "type": "git", 590 | "url": "https://github.com/sebastianbergmann/php-timer.git", 591 | "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" 592 | }, 593 | "dist": { 594 | "type": "zip", 595 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", 596 | "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", 597 | "shasum": "" 598 | }, 599 | "require": { 600 | "php": "^7.1" 601 | }, 602 | "require-dev": { 603 | "phpunit/phpunit": "^7.0" 604 | }, 605 | "type": "library", 606 | "extra": { 607 | "branch-alias": { 608 | "dev-master": "2.1-dev" 609 | } 610 | }, 611 | "autoload": { 612 | "classmap": [ 613 | "src/" 614 | ] 615 | }, 616 | "notification-url": "https://packagist.org/downloads/", 617 | "license": [ 618 | "BSD-3-Clause" 619 | ], 620 | "authors": [ 621 | { 622 | "name": "Sebastian Bergmann", 623 | "email": "sebastian@phpunit.de", 624 | "role": "lead" 625 | } 626 | ], 627 | "description": "Utility class for timing", 628 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 629 | "keywords": [ 630 | "timer" 631 | ], 632 | "time": "2019-02-20T10:12:59+00:00" 633 | }, 634 | { 635 | "name": "phpunit/php-token-stream", 636 | "version": "3.0.1", 637 | "source": { 638 | "type": "git", 639 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 640 | "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" 641 | }, 642 | "dist": { 643 | "type": "zip", 644 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", 645 | "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", 646 | "shasum": "" 647 | }, 648 | "require": { 649 | "ext-tokenizer": "*", 650 | "php": "^7.1" 651 | }, 652 | "require-dev": { 653 | "phpunit/phpunit": "^7.0" 654 | }, 655 | "type": "library", 656 | "extra": { 657 | "branch-alias": { 658 | "dev-master": "3.0-dev" 659 | } 660 | }, 661 | "autoload": { 662 | "classmap": [ 663 | "src/" 664 | ] 665 | }, 666 | "notification-url": "https://packagist.org/downloads/", 667 | "license": [ 668 | "BSD-3-Clause" 669 | ], 670 | "authors": [ 671 | { 672 | "name": "Sebastian Bergmann", 673 | "email": "sebastian@phpunit.de" 674 | } 675 | ], 676 | "description": "Wrapper around PHP's tokenizer extension.", 677 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 678 | "keywords": [ 679 | "tokenizer" 680 | ], 681 | "time": "2018-10-30T05:52:18+00:00" 682 | }, 683 | { 684 | "name": "phpunit/phpunit", 685 | "version": "7.5.7", 686 | "source": { 687 | "type": "git", 688 | "url": "https://github.com/sebastianbergmann/phpunit.git", 689 | "reference": "eb343b86753d26de07ecba7868fa983104361948" 690 | }, 691 | "dist": { 692 | "type": "zip", 693 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/eb343b86753d26de07ecba7868fa983104361948", 694 | "reference": "eb343b86753d26de07ecba7868fa983104361948", 695 | "shasum": "" 696 | }, 697 | "require": { 698 | "doctrine/instantiator": "^1.1", 699 | "ext-dom": "*", 700 | "ext-json": "*", 701 | "ext-libxml": "*", 702 | "ext-mbstring": "*", 703 | "ext-xml": "*", 704 | "myclabs/deep-copy": "^1.7", 705 | "phar-io/manifest": "^1.0.2", 706 | "phar-io/version": "^2.0", 707 | "php": "^7.1", 708 | "phpspec/prophecy": "^1.7", 709 | "phpunit/php-code-coverage": "^6.0.7", 710 | "phpunit/php-file-iterator": "^2.0.1", 711 | "phpunit/php-text-template": "^1.2.1", 712 | "phpunit/php-timer": "^2.1", 713 | "sebastian/comparator": "^3.0", 714 | "sebastian/diff": "^3.0", 715 | "sebastian/environment": "^4.0", 716 | "sebastian/exporter": "^3.1", 717 | "sebastian/global-state": "^2.0", 718 | "sebastian/object-enumerator": "^3.0.3", 719 | "sebastian/resource-operations": "^2.0", 720 | "sebastian/version": "^2.0.1" 721 | }, 722 | "conflict": { 723 | "phpunit/phpunit-mock-objects": "*" 724 | }, 725 | "require-dev": { 726 | "ext-pdo": "*" 727 | }, 728 | "suggest": { 729 | "ext-soap": "*", 730 | "ext-xdebug": "*", 731 | "phpunit/php-invoker": "^2.0" 732 | }, 733 | "bin": [ 734 | "phpunit" 735 | ], 736 | "type": "library", 737 | "extra": { 738 | "branch-alias": { 739 | "dev-master": "7.5-dev" 740 | } 741 | }, 742 | "autoload": { 743 | "classmap": [ 744 | "src/" 745 | ] 746 | }, 747 | "notification-url": "https://packagist.org/downloads/", 748 | "license": [ 749 | "BSD-3-Clause" 750 | ], 751 | "authors": [ 752 | { 753 | "name": "Sebastian Bergmann", 754 | "email": "sebastian@phpunit.de", 755 | "role": "lead" 756 | } 757 | ], 758 | "description": "The PHP Unit Testing framework.", 759 | "homepage": "https://phpunit.de/", 760 | "keywords": [ 761 | "phpunit", 762 | "testing", 763 | "xunit" 764 | ], 765 | "time": "2019-03-16T07:31:17+00:00" 766 | }, 767 | { 768 | "name": "sebastian/code-unit-reverse-lookup", 769 | "version": "1.0.1", 770 | "source": { 771 | "type": "git", 772 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 773 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" 774 | }, 775 | "dist": { 776 | "type": "zip", 777 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 778 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 779 | "shasum": "" 780 | }, 781 | "require": { 782 | "php": "^5.6 || ^7.0" 783 | }, 784 | "require-dev": { 785 | "phpunit/phpunit": "^5.7 || ^6.0" 786 | }, 787 | "type": "library", 788 | "extra": { 789 | "branch-alias": { 790 | "dev-master": "1.0.x-dev" 791 | } 792 | }, 793 | "autoload": { 794 | "classmap": [ 795 | "src/" 796 | ] 797 | }, 798 | "notification-url": "https://packagist.org/downloads/", 799 | "license": [ 800 | "BSD-3-Clause" 801 | ], 802 | "authors": [ 803 | { 804 | "name": "Sebastian Bergmann", 805 | "email": "sebastian@phpunit.de" 806 | } 807 | ], 808 | "description": "Looks up which function or method a line of code belongs to", 809 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 810 | "time": "2017-03-04T06:30:41+00:00" 811 | }, 812 | { 813 | "name": "sebastian/comparator", 814 | "version": "3.0.2", 815 | "source": { 816 | "type": "git", 817 | "url": "https://github.com/sebastianbergmann/comparator.git", 818 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" 819 | }, 820 | "dist": { 821 | "type": "zip", 822 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", 823 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", 824 | "shasum": "" 825 | }, 826 | "require": { 827 | "php": "^7.1", 828 | "sebastian/diff": "^3.0", 829 | "sebastian/exporter": "^3.1" 830 | }, 831 | "require-dev": { 832 | "phpunit/phpunit": "^7.1" 833 | }, 834 | "type": "library", 835 | "extra": { 836 | "branch-alias": { 837 | "dev-master": "3.0-dev" 838 | } 839 | }, 840 | "autoload": { 841 | "classmap": [ 842 | "src/" 843 | ] 844 | }, 845 | "notification-url": "https://packagist.org/downloads/", 846 | "license": [ 847 | "BSD-3-Clause" 848 | ], 849 | "authors": [ 850 | { 851 | "name": "Jeff Welch", 852 | "email": "whatthejeff@gmail.com" 853 | }, 854 | { 855 | "name": "Volker Dusch", 856 | "email": "github@wallbash.com" 857 | }, 858 | { 859 | "name": "Bernhard Schussek", 860 | "email": "bschussek@2bepublished.at" 861 | }, 862 | { 863 | "name": "Sebastian Bergmann", 864 | "email": "sebastian@phpunit.de" 865 | } 866 | ], 867 | "description": "Provides the functionality to compare PHP values for equality", 868 | "homepage": "https://github.com/sebastianbergmann/comparator", 869 | "keywords": [ 870 | "comparator", 871 | "compare", 872 | "equality" 873 | ], 874 | "time": "2018-07-12T15:12:46+00:00" 875 | }, 876 | { 877 | "name": "sebastian/diff", 878 | "version": "3.0.2", 879 | "source": { 880 | "type": "git", 881 | "url": "https://github.com/sebastianbergmann/diff.git", 882 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" 883 | }, 884 | "dist": { 885 | "type": "zip", 886 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", 887 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", 888 | "shasum": "" 889 | }, 890 | "require": { 891 | "php": "^7.1" 892 | }, 893 | "require-dev": { 894 | "phpunit/phpunit": "^7.5 || ^8.0", 895 | "symfony/process": "^2 || ^3.3 || ^4" 896 | }, 897 | "type": "library", 898 | "extra": { 899 | "branch-alias": { 900 | "dev-master": "3.0-dev" 901 | } 902 | }, 903 | "autoload": { 904 | "classmap": [ 905 | "src/" 906 | ] 907 | }, 908 | "notification-url": "https://packagist.org/downloads/", 909 | "license": [ 910 | "BSD-3-Clause" 911 | ], 912 | "authors": [ 913 | { 914 | "name": "Kore Nordmann", 915 | "email": "mail@kore-nordmann.de" 916 | }, 917 | { 918 | "name": "Sebastian Bergmann", 919 | "email": "sebastian@phpunit.de" 920 | } 921 | ], 922 | "description": "Diff implementation", 923 | "homepage": "https://github.com/sebastianbergmann/diff", 924 | "keywords": [ 925 | "diff", 926 | "udiff", 927 | "unidiff", 928 | "unified diff" 929 | ], 930 | "time": "2019-02-04T06:01:07+00:00" 931 | }, 932 | { 933 | "name": "sebastian/environment", 934 | "version": "4.1.0", 935 | "source": { 936 | "type": "git", 937 | "url": "https://github.com/sebastianbergmann/environment.git", 938 | "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656" 939 | }, 940 | "dist": { 941 | "type": "zip", 942 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656", 943 | "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656", 944 | "shasum": "" 945 | }, 946 | "require": { 947 | "php": "^7.1" 948 | }, 949 | "require-dev": { 950 | "phpunit/phpunit": "^7.5" 951 | }, 952 | "suggest": { 953 | "ext-posix": "*" 954 | }, 955 | "type": "library", 956 | "extra": { 957 | "branch-alias": { 958 | "dev-master": "4.1-dev" 959 | } 960 | }, 961 | "autoload": { 962 | "classmap": [ 963 | "src/" 964 | ] 965 | }, 966 | "notification-url": "https://packagist.org/downloads/", 967 | "license": [ 968 | "BSD-3-Clause" 969 | ], 970 | "authors": [ 971 | { 972 | "name": "Sebastian Bergmann", 973 | "email": "sebastian@phpunit.de" 974 | } 975 | ], 976 | "description": "Provides functionality to handle HHVM/PHP environments", 977 | "homepage": "http://www.github.com/sebastianbergmann/environment", 978 | "keywords": [ 979 | "Xdebug", 980 | "environment", 981 | "hhvm" 982 | ], 983 | "time": "2019-02-01T05:27:49+00:00" 984 | }, 985 | { 986 | "name": "sebastian/exporter", 987 | "version": "3.1.0", 988 | "source": { 989 | "type": "git", 990 | "url": "https://github.com/sebastianbergmann/exporter.git", 991 | "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" 992 | }, 993 | "dist": { 994 | "type": "zip", 995 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", 996 | "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", 997 | "shasum": "" 998 | }, 999 | "require": { 1000 | "php": "^7.0", 1001 | "sebastian/recursion-context": "^3.0" 1002 | }, 1003 | "require-dev": { 1004 | "ext-mbstring": "*", 1005 | "phpunit/phpunit": "^6.0" 1006 | }, 1007 | "type": "library", 1008 | "extra": { 1009 | "branch-alias": { 1010 | "dev-master": "3.1.x-dev" 1011 | } 1012 | }, 1013 | "autoload": { 1014 | "classmap": [ 1015 | "src/" 1016 | ] 1017 | }, 1018 | "notification-url": "https://packagist.org/downloads/", 1019 | "license": [ 1020 | "BSD-3-Clause" 1021 | ], 1022 | "authors": [ 1023 | { 1024 | "name": "Jeff Welch", 1025 | "email": "whatthejeff@gmail.com" 1026 | }, 1027 | { 1028 | "name": "Volker Dusch", 1029 | "email": "github@wallbash.com" 1030 | }, 1031 | { 1032 | "name": "Bernhard Schussek", 1033 | "email": "bschussek@2bepublished.at" 1034 | }, 1035 | { 1036 | "name": "Sebastian Bergmann", 1037 | "email": "sebastian@phpunit.de" 1038 | }, 1039 | { 1040 | "name": "Adam Harvey", 1041 | "email": "aharvey@php.net" 1042 | } 1043 | ], 1044 | "description": "Provides the functionality to export PHP variables for visualization", 1045 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1046 | "keywords": [ 1047 | "export", 1048 | "exporter" 1049 | ], 1050 | "time": "2017-04-03T13:19:02+00:00" 1051 | }, 1052 | { 1053 | "name": "sebastian/global-state", 1054 | "version": "2.0.0", 1055 | "source": { 1056 | "type": "git", 1057 | "url": "https://github.com/sebastianbergmann/global-state.git", 1058 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" 1059 | }, 1060 | "dist": { 1061 | "type": "zip", 1062 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1063 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 1064 | "shasum": "" 1065 | }, 1066 | "require": { 1067 | "php": "^7.0" 1068 | }, 1069 | "require-dev": { 1070 | "phpunit/phpunit": "^6.0" 1071 | }, 1072 | "suggest": { 1073 | "ext-uopz": "*" 1074 | }, 1075 | "type": "library", 1076 | "extra": { 1077 | "branch-alias": { 1078 | "dev-master": "2.0-dev" 1079 | } 1080 | }, 1081 | "autoload": { 1082 | "classmap": [ 1083 | "src/" 1084 | ] 1085 | }, 1086 | "notification-url": "https://packagist.org/downloads/", 1087 | "license": [ 1088 | "BSD-3-Clause" 1089 | ], 1090 | "authors": [ 1091 | { 1092 | "name": "Sebastian Bergmann", 1093 | "email": "sebastian@phpunit.de" 1094 | } 1095 | ], 1096 | "description": "Snapshotting of global state", 1097 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1098 | "keywords": [ 1099 | "global state" 1100 | ], 1101 | "time": "2017-04-27T15:39:26+00:00" 1102 | }, 1103 | { 1104 | "name": "sebastian/object-enumerator", 1105 | "version": "3.0.3", 1106 | "source": { 1107 | "type": "git", 1108 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1109 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" 1110 | }, 1111 | "dist": { 1112 | "type": "zip", 1113 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1114 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1115 | "shasum": "" 1116 | }, 1117 | "require": { 1118 | "php": "^7.0", 1119 | "sebastian/object-reflector": "^1.1.1", 1120 | "sebastian/recursion-context": "^3.0" 1121 | }, 1122 | "require-dev": { 1123 | "phpunit/phpunit": "^6.0" 1124 | }, 1125 | "type": "library", 1126 | "extra": { 1127 | "branch-alias": { 1128 | "dev-master": "3.0.x-dev" 1129 | } 1130 | }, 1131 | "autoload": { 1132 | "classmap": [ 1133 | "src/" 1134 | ] 1135 | }, 1136 | "notification-url": "https://packagist.org/downloads/", 1137 | "license": [ 1138 | "BSD-3-Clause" 1139 | ], 1140 | "authors": [ 1141 | { 1142 | "name": "Sebastian Bergmann", 1143 | "email": "sebastian@phpunit.de" 1144 | } 1145 | ], 1146 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1147 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1148 | "time": "2017-08-03T12:35:26+00:00" 1149 | }, 1150 | { 1151 | "name": "sebastian/object-reflector", 1152 | "version": "1.1.1", 1153 | "source": { 1154 | "type": "git", 1155 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1156 | "reference": "773f97c67f28de00d397be301821b06708fca0be" 1157 | }, 1158 | "dist": { 1159 | "type": "zip", 1160 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", 1161 | "reference": "773f97c67f28de00d397be301821b06708fca0be", 1162 | "shasum": "" 1163 | }, 1164 | "require": { 1165 | "php": "^7.0" 1166 | }, 1167 | "require-dev": { 1168 | "phpunit/phpunit": "^6.0" 1169 | }, 1170 | "type": "library", 1171 | "extra": { 1172 | "branch-alias": { 1173 | "dev-master": "1.1-dev" 1174 | } 1175 | }, 1176 | "autoload": { 1177 | "classmap": [ 1178 | "src/" 1179 | ] 1180 | }, 1181 | "notification-url": "https://packagist.org/downloads/", 1182 | "license": [ 1183 | "BSD-3-Clause" 1184 | ], 1185 | "authors": [ 1186 | { 1187 | "name": "Sebastian Bergmann", 1188 | "email": "sebastian@phpunit.de" 1189 | } 1190 | ], 1191 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1192 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1193 | "time": "2017-03-29T09:07:27+00:00" 1194 | }, 1195 | { 1196 | "name": "sebastian/recursion-context", 1197 | "version": "3.0.0", 1198 | "source": { 1199 | "type": "git", 1200 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1201 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" 1202 | }, 1203 | "dist": { 1204 | "type": "zip", 1205 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1206 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1207 | "shasum": "" 1208 | }, 1209 | "require": { 1210 | "php": "^7.0" 1211 | }, 1212 | "require-dev": { 1213 | "phpunit/phpunit": "^6.0" 1214 | }, 1215 | "type": "library", 1216 | "extra": { 1217 | "branch-alias": { 1218 | "dev-master": "3.0.x-dev" 1219 | } 1220 | }, 1221 | "autoload": { 1222 | "classmap": [ 1223 | "src/" 1224 | ] 1225 | }, 1226 | "notification-url": "https://packagist.org/downloads/", 1227 | "license": [ 1228 | "BSD-3-Clause" 1229 | ], 1230 | "authors": [ 1231 | { 1232 | "name": "Jeff Welch", 1233 | "email": "whatthejeff@gmail.com" 1234 | }, 1235 | { 1236 | "name": "Sebastian Bergmann", 1237 | "email": "sebastian@phpunit.de" 1238 | }, 1239 | { 1240 | "name": "Adam Harvey", 1241 | "email": "aharvey@php.net" 1242 | } 1243 | ], 1244 | "description": "Provides functionality to recursively process PHP variables", 1245 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1246 | "time": "2017-03-03T06:23:57+00:00" 1247 | }, 1248 | { 1249 | "name": "sebastian/resource-operations", 1250 | "version": "2.0.1", 1251 | "source": { 1252 | "type": "git", 1253 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1254 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" 1255 | }, 1256 | "dist": { 1257 | "type": "zip", 1258 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", 1259 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", 1260 | "shasum": "" 1261 | }, 1262 | "require": { 1263 | "php": "^7.1" 1264 | }, 1265 | "type": "library", 1266 | "extra": { 1267 | "branch-alias": { 1268 | "dev-master": "2.0-dev" 1269 | } 1270 | }, 1271 | "autoload": { 1272 | "classmap": [ 1273 | "src/" 1274 | ] 1275 | }, 1276 | "notification-url": "https://packagist.org/downloads/", 1277 | "license": [ 1278 | "BSD-3-Clause" 1279 | ], 1280 | "authors": [ 1281 | { 1282 | "name": "Sebastian Bergmann", 1283 | "email": "sebastian@phpunit.de" 1284 | } 1285 | ], 1286 | "description": "Provides a list of PHP built-in functions that operate on resources", 1287 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1288 | "time": "2018-10-04T04:07:39+00:00" 1289 | }, 1290 | { 1291 | "name": "sebastian/version", 1292 | "version": "2.0.1", 1293 | "source": { 1294 | "type": "git", 1295 | "url": "https://github.com/sebastianbergmann/version.git", 1296 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1297 | }, 1298 | "dist": { 1299 | "type": "zip", 1300 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1301 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1302 | "shasum": "" 1303 | }, 1304 | "require": { 1305 | "php": ">=5.6" 1306 | }, 1307 | "type": "library", 1308 | "extra": { 1309 | "branch-alias": { 1310 | "dev-master": "2.0.x-dev" 1311 | } 1312 | }, 1313 | "autoload": { 1314 | "classmap": [ 1315 | "src/" 1316 | ] 1317 | }, 1318 | "notification-url": "https://packagist.org/downloads/", 1319 | "license": [ 1320 | "BSD-3-Clause" 1321 | ], 1322 | "authors": [ 1323 | { 1324 | "name": "Sebastian Bergmann", 1325 | "email": "sebastian@phpunit.de", 1326 | "role": "lead" 1327 | } 1328 | ], 1329 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1330 | "homepage": "https://github.com/sebastianbergmann/version", 1331 | "time": "2016-10-03T07:35:21+00:00" 1332 | }, 1333 | { 1334 | "name": "symfony/polyfill-ctype", 1335 | "version": "v1.11.0", 1336 | "source": { 1337 | "type": "git", 1338 | "url": "https://github.com/symfony/polyfill-ctype.git", 1339 | "reference": "82ebae02209c21113908c229e9883c419720738a" 1340 | }, 1341 | "dist": { 1342 | "type": "zip", 1343 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", 1344 | "reference": "82ebae02209c21113908c229e9883c419720738a", 1345 | "shasum": "" 1346 | }, 1347 | "require": { 1348 | "php": ">=5.3.3" 1349 | }, 1350 | "suggest": { 1351 | "ext-ctype": "For best performance" 1352 | }, 1353 | "type": "library", 1354 | "extra": { 1355 | "branch-alias": { 1356 | "dev-master": "1.11-dev" 1357 | } 1358 | }, 1359 | "autoload": { 1360 | "psr-4": { 1361 | "Symfony\\Polyfill\\Ctype\\": "" 1362 | }, 1363 | "files": [ 1364 | "bootstrap.php" 1365 | ] 1366 | }, 1367 | "notification-url": "https://packagist.org/downloads/", 1368 | "license": [ 1369 | "MIT" 1370 | ], 1371 | "authors": [ 1372 | { 1373 | "name": "Symfony Community", 1374 | "homepage": "https://symfony.com/contributors" 1375 | }, 1376 | { 1377 | "name": "Gert de Pagter", 1378 | "email": "BackEndTea@gmail.com" 1379 | } 1380 | ], 1381 | "description": "Symfony polyfill for ctype functions", 1382 | "homepage": "https://symfony.com", 1383 | "keywords": [ 1384 | "compatibility", 1385 | "ctype", 1386 | "polyfill", 1387 | "portable" 1388 | ], 1389 | "time": "2019-02-06T07:57:58+00:00" 1390 | }, 1391 | { 1392 | "name": "theseer/tokenizer", 1393 | "version": "1.1.0", 1394 | "source": { 1395 | "type": "git", 1396 | "url": "https://github.com/theseer/tokenizer.git", 1397 | "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" 1398 | }, 1399 | "dist": { 1400 | "type": "zip", 1401 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", 1402 | "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", 1403 | "shasum": "" 1404 | }, 1405 | "require": { 1406 | "ext-dom": "*", 1407 | "ext-tokenizer": "*", 1408 | "ext-xmlwriter": "*", 1409 | "php": "^7.0" 1410 | }, 1411 | "type": "library", 1412 | "autoload": { 1413 | "classmap": [ 1414 | "src/" 1415 | ] 1416 | }, 1417 | "notification-url": "https://packagist.org/downloads/", 1418 | "license": [ 1419 | "BSD-3-Clause" 1420 | ], 1421 | "authors": [ 1422 | { 1423 | "name": "Arne Blankerts", 1424 | "email": "arne@blankerts.de", 1425 | "role": "Developer" 1426 | } 1427 | ], 1428 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1429 | "time": "2017-04-07T12:08:54+00:00" 1430 | }, 1431 | { 1432 | "name": "webmozart/assert", 1433 | "version": "1.4.0", 1434 | "source": { 1435 | "type": "git", 1436 | "url": "https://github.com/webmozart/assert.git", 1437 | "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" 1438 | }, 1439 | "dist": { 1440 | "type": "zip", 1441 | "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", 1442 | "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", 1443 | "shasum": "" 1444 | }, 1445 | "require": { 1446 | "php": "^5.3.3 || ^7.0", 1447 | "symfony/polyfill-ctype": "^1.8" 1448 | }, 1449 | "require-dev": { 1450 | "phpunit/phpunit": "^4.6", 1451 | "sebastian/version": "^1.0.1" 1452 | }, 1453 | "type": "library", 1454 | "extra": { 1455 | "branch-alias": { 1456 | "dev-master": "1.3-dev" 1457 | } 1458 | }, 1459 | "autoload": { 1460 | "psr-4": { 1461 | "Webmozart\\Assert\\": "src/" 1462 | } 1463 | }, 1464 | "notification-url": "https://packagist.org/downloads/", 1465 | "license": [ 1466 | "MIT" 1467 | ], 1468 | "authors": [ 1469 | { 1470 | "name": "Bernhard Schussek", 1471 | "email": "bschussek@gmail.com" 1472 | } 1473 | ], 1474 | "description": "Assertions to validate method input/output with nice error messages.", 1475 | "keywords": [ 1476 | "assert", 1477 | "check", 1478 | "validate" 1479 | ], 1480 | "time": "2018-12-25T11:19:39+00:00" 1481 | } 1482 | ], 1483 | "aliases": [], 1484 | "minimum-stability": "stable", 1485 | "stability-flags": [], 1486 | "prefer-stable": false, 1487 | "prefer-lowest": false, 1488 | "platform": [], 1489 | "platform-dev": [] 1490 | } 1491 | --------------------------------------------------------------------------------