├── .github └── FUNDING.yml ├── .gitignore ├── Application.php ├── Auth ├── Auth.php ├── Middleware │ ├── AuthenticationMiddleware.php │ └── UserSessionMiddleware.php ├── Rbac │ ├── Entity │ │ ├── AssertionRule.php │ │ ├── Permission.php │ │ ├── RbacPermission.php │ │ ├── RbacRole.php │ │ └── Role.php │ ├── Exception │ │ └── SentinelException.php │ ├── Guard.php │ ├── Rbac.php │ └── Resource │ │ ├── BaseStorageResource.php │ │ └── StorageResource.php ├── Repository │ ├── AuthUserRepository.php │ └── PdoRepository.php ├── Sentinel.php ├── Traits │ ├── BadPropertyCallException.php │ └── ImmutableAware.php └── UserSession.php ├── Bootstrap ├── BootProviders.php └── RegisterProviders.php ├── Codefy.php ├── Console ├── Commands │ ├── CheckCommand.php │ ├── DownCommand.php │ ├── GenerateCommand.php │ ├── InitCommand.php │ ├── ListCommand.php │ ├── MakeCommand.php │ ├── MigrateCommand.php │ ├── PasswordHashCommand.php │ ├── PhpMigCommand.php │ ├── RedoCommand.php │ ├── RollbackCommand.php │ ├── ScheduleRunCommand.php │ ├── ServeCommand.php │ ├── StatusCommand.php │ ├── Traits │ │ └── MakeCommandAware.php │ ├── UlidCommand.php │ ├── UpCommand.php │ └── UuidCommand.php ├── ConsoleApplication.php ├── ConsoleCommand.php ├── ConsoleKernel.php └── Exceptions │ └── MakeCommandFileAlreadyExistsException.php ├── Contracts ├── Kernel.php ├── LoggerFactory.php ├── MailerFactory.php └── RoutingController.php ├── Factory ├── FileLoggerFactory.php ├── FileLoggerSmtpFactory.php ├── PHPMailerSmtpFactory.php └── Traits │ └── FileLoggerAware.php ├── Helpers ├── core.php └── path.php ├── Http ├── BaseController.php └── Kernel.php ├── LICENSE.md ├── Migration ├── Adapter │ ├── DbalMigrationAdapter.php │ ├── FileMigrationAdapter.php │ └── MigrationAdapter.php ├── Migration.php └── Migrator.php ├── Providers ├── ConfigServiceProvider.php ├── FlysystemServiceProvider.php └── PdoServiceProvider.php ├── README.md ├── Scheduler ├── BaseTask.php ├── Event │ ├── TaskCompleted.php │ ├── TaskFailed.php │ ├── TaskSkipped.php │ └── TaskStarted.php ├── Expressions │ ├── At.php │ ├── Daily.php │ ├── Date.php │ ├── DayOfWeek │ │ ├── Friday.php │ │ ├── Monday.php │ │ ├── Saturday.php │ │ ├── Sunday.php │ │ ├── Thursday.php │ │ ├── Tuesday.php │ │ └── Wednesday.php │ ├── EveryMinute.php │ ├── Expressional.php │ ├── Hourly.php │ ├── MonthOfYear │ │ ├── April.php │ │ ├── August.php │ │ ├── December.php │ │ ├── February.php │ │ ├── January.php │ │ ├── July.php │ │ ├── June.php │ │ ├── March.php │ │ ├── May.php │ │ ├── November.php │ │ ├── October.php │ │ └── September.php │ ├── Monthly.php │ ├── Quarterly.php │ ├── WeekDays.php │ ├── WeekEnds.php │ └── Weekly.php ├── FailedProcessor.php ├── Mutex │ ├── CacheLocker.php │ └── Locker.php ├── Processor │ ├── BaseProcessor.php │ ├── Callback.php │ ├── Dispatcher.php │ ├── Processor.php │ └── Shell.php ├── Schedule.php ├── Stack.php ├── Task.php ├── Traits │ ├── ExpressionAware.php │ ├── LiteralAware.php │ ├── MailerAware.php │ └── ScheduleValidateAware.php └── ValueObject │ └── TaskId.php ├── Stubs ├── ExampleController.stub ├── ExampleError.stub ├── ExampleMiddleware.stub ├── ExampleProvider.stub └── ExampleRepository.stub ├── Support ├── CodefyMailer.php ├── CodefyServiceProvider.php ├── LocalStorage.php ├── Password.php └── Paths.php ├── View ├── FenomView.php └── FoilView.php ├── composer.json ├── phpcs.xml ├── phpunit.xml └── tests ├── Auth ├── PermissionTest.php ├── RbacTest.php └── RoleTest.php ├── Expressions ├── DailyTest.php ├── DateTest.php ├── DayOfWeekTest.php ├── EveryMinuteTest.php ├── HourlyTest.php ├── LiteralsTest.php ├── MonthOfYearTest.php ├── MonthlyTest.php ├── QuartelyTest.php ├── WeekDaysTest.php ├── WeekEndsTest.php └── WeeklyTest.php └── Pest.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nomadicjosh] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /config/ 3 | files 4 | Scheduler/Tests 5 | storage 6 | vendor 7 | .env 8 | .phpcs-cache 9 | .php-cs-fixer.cache 10 | .phpunit.result.cache 11 | composer.lock 12 | index.php -------------------------------------------------------------------------------- /Auth/Auth.php: -------------------------------------------------------------------------------- 1 | getParsedBody(); 32 | $identity = $this->configContainer->getConfigKey(key: 'auth.pdo.fields.identity', default: 'username'); 33 | $password = $this->configContainer->getConfigKey(key: 'auth.pdo.fields.password', default: 'password'); 34 | 35 | if (! isset($params[$identity]) || ! isset($params[$password])) { 36 | return null; 37 | } 38 | 39 | $user = $this->repository->authenticate( 40 | credential: $params[$identity], 41 | password: $params[$password] 42 | ); 43 | 44 | if (null !== $user) { 45 | return $user; 46 | } 47 | 48 | return null; 49 | } 50 | 51 | /** 52 | * @throws Exception 53 | */ 54 | public function unauthorized(ServerRequestInterface $request): ResponseInterface 55 | { 56 | return $this->responseFactory 57 | ->createResponse(code: 302) 58 | ->withHeader( 59 | name: 'Location', 60 | value: $this->configContainer->getConfigKey( 61 | key: 'auth.http_redirect', 62 | default: $this->configContainer->getConfigKey(key: 'auth.login_url') 63 | ) 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Auth/Middleware/AuthenticationMiddleware.php: -------------------------------------------------------------------------------- 1 | auth->authenticate($request); 30 | if (is_null__(var: $user) || !$user instanceof SessionEntity) { 31 | return $this->auth->unauthorized($request); 32 | } 33 | return $handler->handle($request->withAttribute(self::AUTH_ATTRIBUTE, $user)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Auth/Middleware/UserSessionMiddleware.php: -------------------------------------------------------------------------------- 1 | getAttribute(AuthenticationMiddleware::AUTH_ATTRIBUTE); 33 | 34 | $this->sessionService::$options = [ 35 | 'cookie-name' => 'USERSESSID', 36 | 'cookie-lifetime' => (int) $this->configContainer->getConfigKey(key: 'cookies.lifetime', default: 86400), 37 | ]; 38 | $session = $this->sessionService->makeSession($request); 39 | 40 | /** @var UserSession $user */ 41 | $user = $session->get(type: UserSession::class); 42 | $user 43 | ->withToken(token: $userDetails->token) 44 | ->withRole(role: $userDetails->role); 45 | 46 | $request = $request->withAttribute(self::SESSION_ATTRIBUTE, $user); 47 | 48 | $response = $handler->handle($request); 49 | 50 | return $this->sessionService->commitSession($response, $session); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Auth/Rbac/Entity/AssertionRule.php: -------------------------------------------------------------------------------- 1 | permissionName; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | public function getDescription(): string 43 | { 44 | return $this->description; 45 | } 46 | 47 | /** 48 | * @param Permission $permission 49 | */ 50 | public function addChild(Permission $permission): void 51 | { 52 | $this->childrenNames[$permission->getName()] = true; 53 | } 54 | 55 | /** 56 | * @param string $permissionName 57 | */ 58 | public function removeChild(string $permissionName): void 59 | { 60 | unset($this->childrenNames[$permissionName]); 61 | } 62 | 63 | /** 64 | * @return Permission[] 65 | */ 66 | public function getChildren(): array 67 | { 68 | $result = []; 69 | $permissionNames = array_keys(array: $this->childrenNames); 70 | foreach ($permissionNames as $name) { 71 | $result[$name] = $this->rbacStorageCollection->getPermission(name: $name); 72 | } 73 | return $result; 74 | } 75 | 76 | /** 77 | * @param string $ruleClass 78 | */ 79 | public function setRuleClass(string $ruleClass): void 80 | { 81 | $this->ruleClass = $ruleClass; 82 | } 83 | 84 | /** 85 | * @return string|null; 86 | */ 87 | public function getRuleClass(): ?string 88 | { 89 | return $this->ruleClass; 90 | } 91 | 92 | /** 93 | * @param array|null $params 94 | * @return bool 95 | * @throws SentinelException 96 | */ 97 | public function checkAccess(?array $params = null): bool 98 | { 99 | $result = true; 100 | if ($ruleClass = $this->getRuleClass()) { 101 | try { 102 | $rule = new $ruleClass(); 103 | if (!$rule instanceof AssertionRule) { 104 | throw new SentinelException( 105 | sprintf( 106 | 'Rule class: %s is not an instance of %s.', 107 | $rule, 108 | AssertionRule::class 109 | ) 110 | ); 111 | } 112 | 113 | $result = $rule->execute(params: $params); 114 | } catch (Throwable $e) { 115 | throw new SentinelException(sprintf('Cannot instantiate rule class: %s.', $ruleClass)); 116 | } 117 | } 118 | return $result; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Auth/Rbac/Entity/RbacRole.php: -------------------------------------------------------------------------------- 1 | roleName; 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function getDescription(): string 42 | { 43 | return $this->description; 44 | } 45 | 46 | /** 47 | * @param Role $role 48 | */ 49 | public function addChild(Role $role): void 50 | { 51 | $this->childrenNames[$role->getName()] = true; 52 | } 53 | 54 | /** 55 | * @param string $roleName 56 | */ 57 | public function removeChild(string $roleName): void 58 | { 59 | unset($this->childrenNames[$roleName]); 60 | } 61 | 62 | /** 63 | * @return Role[] 64 | */ 65 | public function getChildren(): array 66 | { 67 | $result = []; 68 | $roleNames = array_keys(array: $this->childrenNames); 69 | foreach ($roleNames as $name) { 70 | $result[$name] = $this->rbacStorageCollection->getRole(name: $name); 71 | } 72 | return $result; 73 | } 74 | 75 | /** 76 | * @param Permission $permission 77 | */ 78 | public function addPermission(Permission $permission): void 79 | { 80 | $this->permissionNames[$permission->getName()] = true; 81 | } 82 | 83 | /** 84 | * @param string $permissionName 85 | */ 86 | public function removePermission(string $permissionName): void 87 | { 88 | unset($this->permissionNames[$permissionName]); 89 | } 90 | 91 | /** 92 | * @param bool $withChildren 93 | * @return Permission[] 94 | */ 95 | public function getPermissions(bool $withChildren = false): array 96 | { 97 | $result = []; 98 | $permissionNames = array_keys(array: $this->permissionNames); 99 | 100 | foreach ($permissionNames as $name) { 101 | $permission = $this->rbacStorageCollection->getPermission(name: $name); 102 | $result[$name] = $permission; 103 | } 104 | 105 | if ($withChildren) { 106 | foreach ($result as $permission) { 107 | $this->collectChildrenPermissions($permission, $result); 108 | } 109 | foreach ($this->getChildren() as $child) { 110 | $result = array_merge($result, $child->getPermissions(withChildren: true)); 111 | } 112 | } 113 | 114 | return $result; 115 | } 116 | 117 | /** 118 | * @param string $permissionName 119 | * @param array|null $params 120 | * @return bool 121 | */ 122 | public function checkAccess(string $permissionName, ?array $params = null): bool 123 | { 124 | $permissions = $this->getPermissions(withChildren: true); 125 | if (isset($permissions[$permissionName])) { 126 | if ($permissions[$permissionName]->checkAccess(params: $params)) { 127 | return true; 128 | } 129 | } 130 | return false; 131 | } 132 | 133 | /** 134 | * @param Permission $permission 135 | * @param $result 136 | */ 137 | protected function collectChildrenPermissions(Permission $permission, &$result): void 138 | { 139 | foreach ($permission->getChildren() as $childPermission) { 140 | $childPermissionName = $childPermission->getName(); 141 | if (!isset($result[$childPermissionName])) { 142 | $result[$childPermissionName] = $childPermission; 143 | $this->collectChildrenPermissions($childPermission, $result); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Auth/Rbac/Entity/Role.php: -------------------------------------------------------------------------------- 1 | storageResource = $storageResource; 18 | $this->storageResource->load(); 19 | } 20 | 21 | /** 22 | * @param string $name 23 | * @param string $description 24 | * 25 | * @return Role 26 | */ 27 | public function addRole(string $name, string $description = ''): Role 28 | { 29 | return $this->storageResource->addRole($name, $description); 30 | } 31 | 32 | public function addPermission(string $name, string $description = ''): Permission 33 | { 34 | return $this->storageResource->addPermission($name, $description); 35 | } 36 | 37 | /** 38 | * @return Role[] 39 | */ 40 | public function getRoles(): array 41 | { 42 | return $this->storageResource->getRoles(); 43 | } 44 | 45 | /** 46 | * @param string $name 47 | * @return Role|null 48 | */ 49 | public function getRole(string $name): ?Role 50 | { 51 | return $this->storageResource->getRole($name); 52 | } 53 | 54 | /** 55 | * @param string $name 56 | */ 57 | public function deleteRole(string $name): void 58 | { 59 | $this->storageResource->deleteRole($name); 60 | } 61 | 62 | /** 63 | * @return Permission[] 64 | */ 65 | public function getPermissions(): array 66 | { 67 | return $this->storageResource->getPermissions(); 68 | } 69 | 70 | /** 71 | * @param string $name 72 | * @return Permission|null 73 | */ 74 | public function getPermission(string $name): ?Permission 75 | { 76 | return $this->storageResource->getPermission($name); 77 | } 78 | 79 | /** 80 | * @param string $name 81 | */ 82 | public function deletePermission(string $name): void 83 | { 84 | $this->storageResource->deletePermission($name); 85 | } 86 | 87 | /** 88 | */ 89 | public function clear(): void 90 | { 91 | $this->storageResource->clear(); 92 | } 93 | 94 | private function load(): void 95 | { 96 | $this->storageResource->load(); 97 | } 98 | 99 | public function save(): void 100 | { 101 | $this->storageResource->save(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Auth/Rbac/Resource/BaseStorageResource.php: -------------------------------------------------------------------------------- 1 | roles[$name])) { 25 | throw new SentinelException(message: 'Role already exists.'); 26 | } 27 | $role = new RbacRole(roleName: $name, description: $description, rbacStorageCollection: $this); 28 | $this->roles[$name] = $role; 29 | return $role; 30 | } 31 | 32 | public function addPermission(string $name, string $description = ''): Permission 33 | { 34 | if (isset($this->permissions[$name])) { 35 | throw new SentinelException(message: 'Permission already exists.'); 36 | } 37 | 38 | $permission = new RbacPermission( 39 | permissionName: $name, 40 | description: $description, 41 | rbacStorageCollection: $this 42 | ); 43 | 44 | $this->permissions[$name] = $permission; 45 | return $permission; 46 | } 47 | 48 | public function getRoles(): array 49 | { 50 | return $this->roles; 51 | } 52 | 53 | public function getRole(string $name): ?Role 54 | { 55 | return $this->roles[$name] ?? null; 56 | } 57 | 58 | public function deleteRole(string $name): void 59 | { 60 | unset($this->roles[$name]); 61 | 62 | foreach ($this->getRoles() as $role) { 63 | $role->removeChild($name); 64 | } 65 | } 66 | 67 | public function getPermissions(): array 68 | { 69 | return $this->permissions; 70 | } 71 | 72 | public function getPermission(string $name): ?Permission 73 | { 74 | return $this->permissions[$name] ?? null; 75 | } 76 | 77 | public function deletePermission(string $name): void 78 | { 79 | unset($this->permissions[$name]); 80 | 81 | foreach ($this->getRoles() as $role) { 82 | $role->removePermission(permissionName: $name); 83 | } 84 | 85 | foreach ($this->getPermissions() as $permission) { 86 | $permission->removeChild(permissionName: $name); 87 | } 88 | } 89 | 90 | public function clear(): void 91 | { 92 | $this->roles = []; 93 | $this->permissions = []; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Auth/Rbac/Resource/StorageResource.php: -------------------------------------------------------------------------------- 1 | config->getConfigKey(key: 'auth.pdo.fields'); 28 | 29 | $sql = sprintf( 30 | "SELECT * FROM %s WHERE %s = :identity", 31 | $this->config->getConfigKey('auth.pdo.table'), 32 | $fields['identity'] 33 | ); 34 | 35 | $stmt = $this->pdo->prepare(query: $sql); 36 | if (false === $stmt) { 37 | return null; 38 | } 39 | 40 | $stmt->bindParam(':identity', $credential); 41 | $stmt->execute(); 42 | 43 | $result = $stmt->fetchObject(); 44 | if (! $result) { 45 | return null; 46 | } 47 | 48 | $passwordHash = (string) ($result->{$fields['password']} ?? ''); 49 | 50 | if (Password::verify(password: $password ?? '', hash: $passwordHash)) { 51 | $user = new class () implements SessionEntity { 52 | public ?string $token = null; 53 | public ?string $role = null; 54 | 55 | public function withToken(?string $token = null): self 56 | { 57 | $this->token = $token; 58 | return $this; 59 | } 60 | 61 | public function withRole(?string $role = null): self 62 | { 63 | $this->role = $role; 64 | return $this; 65 | } 66 | 67 | public function isEmpty(): bool 68 | { 69 | return !empty($this->token) && !empty($this->role); 70 | } 71 | }; 72 | 73 | $user 74 | ->withToken($result->token) 75 | ->withRole($result->role); 76 | 77 | return $user; 78 | } 79 | 80 | return null; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Auth/Sentinel.php: -------------------------------------------------------------------------------- 1 | {$property}) && $this->{$property} === $value) || 29 | (!isset($this->{$property}) && $value === null) 30 | ) { 31 | return $this; 32 | } 33 | 34 | $clone = clone $this; 35 | $clone->{$property} = $value; 36 | 37 | return $clone; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Auth/UserSession.php: -------------------------------------------------------------------------------- 1 | token = $token; 18 | return $this; 19 | } 20 | 21 | public function withRole(?string $role = null): self 22 | { 23 | $this->role = $role; 24 | return $this; 25 | } 26 | 27 | public function clear(): void 28 | { 29 | if (!empty($this->token)) { 30 | unset($this->token); 31 | } 32 | 33 | if (!empty($this->role)) { 34 | unset($this->role); 35 | } 36 | } 37 | 38 | public function isEmpty(): bool 39 | { 40 | return empty($this->token) && empty($this->role); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Bootstrap/BootProviders.php: -------------------------------------------------------------------------------- 1 | boot(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Bootstrap/RegisterProviders.php: -------------------------------------------------------------------------------- 1 | registerConfiguredServiceProviders(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Codefy.php: -------------------------------------------------------------------------------- 1 | setDescription(description: 'Check that all migrations have run; exit with non-zero if not.') 20 | ->setHelp( 21 | help: <<migrate:check checks that all migrations have been run and exits with a 23 | non-zero exit code if not, useful for build or deployment scripts. 24 | php codex migrate:check 25 | EOT 26 | ); 27 | } 28 | 29 | /** 30 | * @throws Exception 31 | */ 32 | public function handle(): int 33 | { 34 | $this->bootstrap(input: $this->input, output: $this->output); 35 | 36 | $versions = $this->getAdapter()->fetchAll(); 37 | $down = []; 38 | 39 | foreach ($this->getMigrations() as $migration) { 40 | if (!in_array(needle: $migration->getVersion(), haystack: $versions)) { 41 | $down[] = $migration; 42 | } 43 | } 44 | 45 | if (!empty($down)) { 46 | $table = new Table(output: $this->output); 47 | $table->setHeaders(headers: ['Status', 'Migration ID', 'Migration Name']); 48 | foreach ($down as $migration) { 49 | $table->addRow( 50 | row: [ 51 | 'down', 52 | $migration->getVersion(), 53 | "{$migration->getName()}" 54 | ] 55 | ); 56 | } 57 | 58 | $table->render(); 59 | 60 | return 1; 61 | } 62 | 63 | $this->terminalInfo(string: 'No migrations to run.'); 64 | 65 | return 0; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Console/Commands/DownCommand.php: -------------------------------------------------------------------------------- 1 | addArgument( 20 | name: 'version', 21 | mode: InputArgument::REQUIRED, 22 | description: 'The version number for the migration.' 23 | ) 24 | ->setDescription(description: 'Revert a specific migration.') 25 | ->setHelp( 26 | help: <<migrate:down command reverts a specific migration 28 | php codex migrate:down 20111018185412 29 | EOT 30 | ); 31 | } 32 | 33 | /** 34 | * @throws Exception 35 | */ 36 | public function handle(): int 37 | { 38 | $this->bootstrap(input: $this->input, output: $this->output); 39 | 40 | $migrations = $this->getMigrations(); 41 | $versions = $this->getAdapter()->fetchAll(); 42 | 43 | $version = $this->getArgument(key: 'version'); 44 | 45 | if (!in_array(needle: $version, haystack: $versions)) { 46 | $this->terminalInfo(string: 'No available migration to run down.'); 47 | return ConsoleCommand::SUCCESS; 48 | } 49 | 50 | if (!isset($migrations[$version])) { 51 | $this->terminalError(string: 'Migration version does not exist.'); 52 | return ConsoleCommand::SUCCESS; 53 | } 54 | 55 | $container = $this->getObjectMap(); 56 | $container['phpmig.migrator']->down($migrations[$version]); 57 | 58 | return ConsoleCommand::SUCCESS; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Console/Commands/InitCommand.php: -------------------------------------------------------------------------------- 1 | setDescription(description: 'Initialise this directory for use with phpmig.') 20 | ->setHelp( 21 | help: <<migrate:init command creates a skeleton bootstrap file and a migrations directory. 23 | php codex migrate:init 24 | EOT 25 | ); 26 | } 27 | 28 | public function handle(): int 29 | { 30 | $bootstrap = $this->codefy->bootStrapPath() . Application::DS . 'phpmig.php'; 31 | $migrations = $this->codefy->databasePath() . Application::DS . 'migrations'; 32 | 33 | $this->initMigrationsDir($migrations, $this->output); 34 | $this->initBootstrap($bootstrap, $migrations, $this->output); 35 | 36 | return ConsoleCommand::SUCCESS; 37 | } 38 | 39 | /** 40 | * Create migrations dir 41 | * 42 | * @param $migrations 43 | * @param OutputInterface $output 44 | * @return void 45 | */ 46 | protected function initMigrationsDir($migrations, OutputInterface $output): void 47 | { 48 | if (file_exists(filename: $migrations) && is_dir(filename: $migrations)) { 49 | $output->writeln( 50 | messages: '-- ' . 51 | $migrations . ' already exists -' . 52 | ' Place your migration files in here.' 53 | ); 54 | return; 55 | } 56 | 57 | if (false === mkdir($migrations)) { 58 | throw new RuntimeException(message: sprintf('Could not create directory "%s".', $migrations)); 59 | } 60 | 61 | $output->writeln( 62 | messages: '+d ' . 63 | $migrations . 64 | ' Place your migration files in here.' 65 | ); 66 | } 67 | 68 | /** 69 | * Create bootstrap 70 | * 71 | * @param string $bootstrap where to put bootstrap file. 72 | * @param string $migrations path to migrations dir relative to bootstrap. 73 | * @param OutputInterface $output 74 | * @return void 75 | */ 76 | protected function initBootstrap(string $bootstrap, string $migrations, OutputInterface $output): void 77 | { 78 | if (file_exists(filename: $bootstrap)) { 79 | $output->writeln( 80 | messages: '-- ' . 81 | $bootstrap . ' already exists -' . 82 | ' Create services in here.' 83 | ); 84 | return; 85 | } 86 | 87 | if (!is_writable(filename: dirname(path: $bootstrap))) { 88 | throw new RuntimeException(message: sprintf('The file "%s" is not writeable.', $bootstrap)); 89 | } 90 | 91 | $contents = <<writeln( 112 | messages: '+f ' . 113 | $bootstrap . 114 | ' Create services in here.' 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Console/Commands/ListCommand.php: -------------------------------------------------------------------------------- 1 | output); 34 | $table->setHeaders([ 35 | "Class", 36 | "Command", 37 | "Expression", 38 | "Next Execution", 39 | "Run Only One Instance", 40 | ]); 41 | 42 | $jobs = $this->schedule->allProcessors(); 43 | 44 | foreach ($jobs as $job) { 45 | $nextRun = new CronExpression($job->getExpression()); 46 | 47 | $fullCommand = $job->getCommand(); 48 | if($job instanceof Callback) { 49 | $fullCommand = $job->__toString(); 50 | } 51 | 52 | $table->addRow([ 53 | get_class($job), 54 | $fullCommand, 55 | $nextRun->getExpression(), 56 | $nextRun->getNextRunDate()->format(format: 'd F Y h:i A'), 57 | $job->canRunOnlyOneInstance() ? 'yes' : 'no', 58 | ]); 59 | } 60 | 61 | $table->render(); 62 | 63 | // return value is important when using CI 64 | // to fail the build when the command fails 65 | // 0 = success, other values = fail 66 | return ConsoleCommand::SUCCESS; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Console/Commands/MakeCommand.php: -------------------------------------------------------------------------------- 1 | 'App\Infrastructure\Http\Controllers', 35 | 'repository' => 'App\Infrastructure\Persistence\Repository', 36 | 'provider' => 'App\Infrastructure\Providers', 37 | 'middleware' => 'App\Infrastructure\Http\Middleware', 38 | 'error' => 'App\Infrastructure\Errors', 39 | ]; 40 | 41 | protected array $args = [ 42 | [ 43 | 'resource', 44 | 'required', 45 | 'What do you want to make. You can make the following: [controller, repositories, providers, etc.].' 46 | ], 47 | ]; 48 | 49 | protected array $options = [ 50 | [ 51 | '--dir', 52 | '-d', 53 | 'optional', 54 | 'Specify the directory of your controller, model, aggregate, entity, etc.', 55 | false 56 | ], 57 | ]; 58 | 59 | public function handle(): int 60 | { 61 | $stub = $this->getArgument(key: 'resource'); 62 | $option = $this->getOptions(key: 'dir'); 63 | 64 | try { 65 | $this->resolveResource(resource: $stub, options: $option); 66 | $this->terminalQuestion(string: 'Your file was created successfully.'); 67 | return ConsoleCommand::SUCCESS; 68 | } catch (MakeCommandFileAlreadyExistsException | TypeException | RuntimeException | FilesystemException $e) { 69 | $this->terminalError(string: sprintf('%s', $e->getMessage())); 70 | } finally { 71 | return ConsoleCommand::FAILURE; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Console/Commands/MigrateCommand.php: -------------------------------------------------------------------------------- 1 | addOption( 21 | name: '--target', 22 | shortcut: '-t', 23 | mode: InputArgument::OPTIONAL, 24 | description: 'The version number to migrate to.' 25 | ) 26 | ->setDescription(description: 'Run all migrations.') 27 | ->setHelp( 28 | help: <<migrate command runs all available migrations, optionally up to a specific version 30 | php codex migrate 31 | php codex migrate -t 20111018185412 32 | EOT 33 | ); 34 | } 35 | 36 | /** 37 | * @throws Exception 38 | */ 39 | public function handle(): int 40 | { 41 | $this->bootstrap(input: $this->input, output: $this->output); 42 | 43 | $migrations = $this->getMigrations(); 44 | $versions = $this->getAdapter()->fetchAll(); 45 | 46 | $version = $this->getOptions(key: 'target'); 47 | 48 | ksort($migrations); 49 | sort($versions); 50 | 51 | 52 | if (!empty($versions)) { 53 | // Get the last run migration number 54 | $current = end($versions); 55 | } else { 56 | $current = 0; 57 | } 58 | 59 | if (null !== $version && '' !== $version) { 60 | if (0 !== $version && !isset($migrations[$version])) { 61 | return ConsoleCommand::SUCCESS; 62 | } 63 | } else { 64 | $versionNumbers = array_merge($versions, array_keys(array: $migrations)); 65 | 66 | if (empty($versionNumbers)) { 67 | return ConsoleCommand::SUCCESS; 68 | } 69 | 70 | $version = max(value: $versionNumbers); 71 | } 72 | 73 | $direction = $version > $current ? 'up' : 'down'; 74 | 75 | if ($direction === 'down') { 76 | /** 77 | * Run downs first 78 | */ 79 | krsort($migrations); 80 | foreach ($migrations as $migration) { 81 | if ($migration->getVersion() <= $version) { 82 | break; 83 | } 84 | 85 | if (in_array(needle: $migration->getVersion(), haystack: $versions)) { 86 | $objectmap = $this->getObjectMap(); 87 | $objectmap['phpmig.migrator']->down($migration); 88 | } 89 | } 90 | } 91 | 92 | ksort($migrations); 93 | foreach ($migrations as $migration) { 94 | if ($migration->getVersion() > $version) { 95 | break; 96 | } 97 | 98 | if (!in_array(needle: $migration->getVersion(), haystack: $versions)) { 99 | $objectmap = $this->getObjectMap(); 100 | $objectmap['phpmig.migrator']->up($migration); 101 | } 102 | } 103 | 104 | return ConsoleCommand::SUCCESS; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Console/Commands/PasswordHashCommand.php: -------------------------------------------------------------------------------- 1 | getArgument(key: 'password'); 29 | 30 | $hashedPassword = Password::hash(password: $password); 31 | 32 | $this->terminalRaw(string: sprintf( 33 | 'Your hashed password is: %s', 34 | $hashedPassword 35 | )); 36 | 37 | // return value is important when using CI 38 | // to fail the build when the command fails 39 | // 0 = success, other values = fail 40 | return ConsoleCommand::SUCCESS; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Console/Commands/RedoCommand.php: -------------------------------------------------------------------------------- 1 | addArgument( 21 | name: 'version', 22 | mode: InputArgument::REQUIRED, 23 | description: 'The version number for the migration' 24 | ) 25 | ->setDescription(description: 'Redo a specific migration.') 26 | ->setHelp( 27 | help: <<migrate:redo command redo a specific migration 29 | php codex migrate:redo 20111018185412 30 | EOT 31 | ); 32 | } 33 | 34 | /** 35 | * @throws Exception 36 | */ 37 | public function handle(): int 38 | { 39 | $this->bootstrap(input: $this->input, output: $this->output); 40 | 41 | $migrations = $this->getMigrations(); 42 | $versions = $this->getAdapter()->fetchAll(); 43 | 44 | $version = $this->getArgument(key: 'version'); 45 | 46 | if (!in_array(needle: $version, haystack: $versions)) { 47 | return ConsoleCommand::SUCCESS; 48 | } 49 | 50 | if (!isset($migrations[$version])) { 51 | return ConsoleCommand::SUCCESS; 52 | } 53 | 54 | $objectmap = $this->getObjectMap(); 55 | $objectmap['phpmig.migrator']->down($migrations[$version]); 56 | $objectmap['phpmig.migrator']->up($migrations[$version]); 57 | 58 | return ConsoleCommand::SUCCESS; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Console/Commands/RollbackCommand.php: -------------------------------------------------------------------------------- 1 | addOption( 21 | name: '--target', 22 | shortcut: '-t', 23 | mode: InputArgument::OPTIONAL, 24 | description: 'The version number to rollback to.' 25 | ) 26 | ->setDescription(description: 'Rollback to the last, or to a specific migration.') 27 | ->setHelp( 28 | help: <<migrate:rollback command reverts the last migration, or optionally up to a specific version 30 | php codex migrate:rollback 31 | php codex migrate:rollback -t 20111018185412 32 | EOT 33 | ); 34 | } 35 | 36 | /** 37 | * @throws Exception 38 | */ 39 | public function handle(): int 40 | { 41 | $this->bootstrap(input: $this->input, output: $this->output); 42 | 43 | $migrations = $this->getMigrations(); 44 | $versions = $this->getAdapter()->fetchAll(); 45 | 46 | $version = $this->getOptions(key: 'target'); 47 | 48 | ksort($migrations); 49 | sort($versions); 50 | 51 | // Check we have at least 1 migration to revert 52 | if (empty($versions) || $version == end($versions)) { 53 | $this->terminalError(string: 'No migrations to rollback'); 54 | return 0; 55 | } 56 | 57 | // If no target version was supplied, revert the last migration 58 | if (null === $version || '' === $version) { 59 | // Get the migration before the last run migration 60 | $prev = count($versions) - 2; 61 | $version = $prev >= 0 ? $versions[$prev] : 0; 62 | } else { 63 | // Get the first migration number 64 | $first = reset($versions); 65 | 66 | // If the target version is before the first migration, revert all migrations 67 | if ($version < $first) { 68 | $version = 0; 69 | } 70 | } 71 | 72 | // Check the target version exists 73 | if (0 !== $version && !isset($migrations[$version])) { 74 | $this->terminalError(string: "Target version ($version) not found"); 75 | return 0; 76 | } 77 | 78 | // Revert the migration(s) 79 | $objectmap = $this->getObjectMap(); 80 | krsort($migrations); 81 | foreach ($migrations as $migration) { 82 | if ($migration->getVersion() <= $version) { 83 | break; 84 | } 85 | 86 | if (in_array(needle: $migration->getVersion(), haystack: $versions)) { 87 | $objectmap['phpmig.migrator']->down($migration); 88 | } 89 | } 90 | 91 | return ConsoleCommand::SUCCESS; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Console/Commands/ScheduleRunCommand.php: -------------------------------------------------------------------------------- 1 | schedule->run(); 27 | 28 | // return value is important when using CI 29 | // to fail the build when the command fails 30 | // 0 = success, other values = fail 31 | return ConsoleCommand::SUCCESS; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Console/Commands/ServeCommand.php: -------------------------------------------------------------------------------- 1 | setDescription(description: 'Launches the CodefyPHP development server.') 45 | ->setHelp( 46 | help: <<serve command to start the development server 48 | php codex serve 49 | EOT 50 | ); 51 | } 52 | 53 | public function handle(): int 54 | { 55 | $php = escapeshellarg(empty($this->getOptions(key: 'php')) ? PHP_BINARY : $this->getOptions(key: 'php')); 56 | $host = empty($this->getOptions(key: 'host')) ? 'localhost' : $this->getOptions(key: 'host'); 57 | $port = empty($this->getOptions(key: 'port')) ? '8080' : $this->getOptions(key: 'port'); 58 | 59 | $this->terminalComment(string: sprintf('CodefyPHP development server started on http://%s:%s', $host, $port)); 60 | $this->terminalComment(string: 'Press control-C to stop.'); 61 | 62 | $docroot = escapeshellarg(public_path()); 63 | 64 | passthru($php . ' -S ' . $host . ':' . $port . ' -t ' . $docroot); 65 | 66 | // return value is important when using CI 67 | // to fail the build when the command fails 68 | // 0 = success, other values = fail 69 | return ConsoleCommand::SUCCESS; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Console/Commands/StatusCommand.php: -------------------------------------------------------------------------------- 1 | setDescription(description: 'Show the up/down status of all migrations.') 23 | ->setHelp( 24 | help: <<migrate:status command prints a list of all migrations, along with their current status 26 | php codex migrate:status 27 | EOT 28 | ); 29 | } 30 | 31 | /** 32 | * @throws Exception 33 | */ 34 | public function handle(): int 35 | { 36 | $this->bootstrap(input: $this->input, output: $this->output); 37 | 38 | $versions = $this->getAdapter()->fetchAll(); 39 | 40 | $table = new Table(output: $this->output); 41 | $table->setHeaders(headers: ['Status', 'Migration ID', 'Migration Name']); 42 | 43 | foreach ($this->getMigrations() as $migration) { 44 | if (in_array($migration->getVersion(), $versions)) { 45 | $status = 'up'; 46 | unset($versions[array_search($migration->getVersion(), $versions)]); 47 | } else { 48 | $status = 'down'; 49 | } 50 | 51 | $table->addRow( 52 | row: [ 53 | $status, 54 | sprintf(' %14s ', $migration->getVersion()), 55 | "{$migration->getName()}" 56 | ] 57 | ); 58 | } 59 | 60 | foreach ($versions as $missing) { 61 | $table->addRow( 62 | row: [ 63 | 'up', 64 | sprintf(' %14s ', $missing), 65 | '** MISSING **' 66 | ] 67 | ); 68 | } 69 | 70 | $table->render(); 71 | 72 | // print status 73 | return ConsoleCommand::SUCCESS; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Console/Commands/UlidCommand.php: -------------------------------------------------------------------------------- 1 | terminalRaw(string: sprintf( 27 | 'Ulid: %s', 28 | $ulid 29 | )); 30 | 31 | // return value is important when using CI 32 | // to fail the build when the command fails 33 | // 0 = success, other values = fail 34 | return ConsoleCommand::SUCCESS; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Console/Commands/UpCommand.php: -------------------------------------------------------------------------------- 1 | addArgument( 21 | name: 'version', 22 | mode: InputArgument::REQUIRED, 23 | description: 'The version number for the migration.' 24 | ) 25 | ->setDescription(description: 'Run a specific migration.') 26 | ->setHelp( 27 | help: <<migrate:up command runs a specific migration 29 | php codex migrate:up 20111018185121 30 | EOT 31 | ); 32 | } 33 | 34 | /** 35 | * @throws Exception 36 | */ 37 | public function handle(): int 38 | { 39 | $this->bootstrap(input: $this->input, output: $this->output); 40 | 41 | $migrations = $this->getMigrations(); 42 | $versions = $this->getAdapter()->fetchAll(); 43 | 44 | $version = $this->getArgument(key: 'version'); 45 | 46 | if (in_array(needle: $version, haystack: $versions)) { 47 | $this->terminalInfo(string: 'Migration version already exists.'); 48 | return 0; 49 | } 50 | 51 | if (!isset($migrations[$version])) { 52 | $this->terminalError(string: 'No migration by that version exists.'); 53 | return 0; 54 | } 55 | 56 | $objectmap = $this->getObjectMap(); 57 | $objectmap['phpmig.migrator']->up($migrations[$version]); 58 | 59 | return ConsoleCommand::SUCCESS; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Console/Commands/UuidCommand.php: -------------------------------------------------------------------------------- 1 | terminalRaw(string: sprintf( 27 | 'Uuid: %s', 28 | $uuid 29 | )); 30 | 31 | // return value is important when using CI 32 | // to fail the build when the command fails 33 | // 0 = success, other values = fail 34 | return ConsoleCommand::SUCCESS; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Console/ConsoleApplication.php: -------------------------------------------------------------------------------- 1 | getCommandName(input: $input = $input ?: new ArgvInput()); 30 | 31 | return parent::run(input: $input, output: $output); 32 | } 33 | 34 | /** 35 | * @throws Exception 36 | */ 37 | public function call($command, array $parameters = [], bool|OutputInterface $outputBuffer = null): int 38 | { 39 | [$command, $input] = $this->parseCommand(command: $command, parameters: $parameters); 40 | 41 | if (! $this->has($command)) { 42 | throw new CommandNotFoundException(message: sprintf('The command "%s" does not exist.', $command)); 43 | } 44 | 45 | return $this->run( 46 | $input, 47 | $this->lastOutput = ($outputBuffer instanceof OutputInterface) ? new BufferedOutput() : '' 48 | ); 49 | } 50 | 51 | /** 52 | * Parse the incoming Codex command and its input. 53 | * 54 | * @param string $command 55 | * @param array $parameters 56 | * @return array 57 | */ 58 | protected function parseCommand(string $command, array $parameters): array 59 | { 60 | if (empty($parameters)) { 61 | $command = $this->getCommandName(input: $input = new StringInput(input: $command)); 62 | } else { 63 | array_unshift($parameters, $command); 64 | 65 | $input = new ArrayInput(parameters: $parameters); 66 | } 67 | 68 | return [$command, $input]; 69 | } 70 | 71 | /** 72 | * Get the output for the last run command. 73 | * 74 | * @return string 75 | */ 76 | public function output(): string 77 | { 78 | return $this->lastOutput && method_exists($this->lastOutput, 'fetch') 79 | ? $this->lastOutput->fetch() 80 | : ''; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Console/ConsoleKernel.php: -------------------------------------------------------------------------------- 1 | codefy->booted(function () { 40 | $this->defineConsoleSchedule(); 41 | }); 42 | } 43 | 44 | protected function defineConsoleSchedule(): void 45 | { 46 | $this->codefy->share(nameOrInstance: Schedule::class); 47 | 48 | $this->codefy->prepare(name: Schedule::class, callableOrMethodStr: function () { 49 | $timeZone = new QubusDateTimeZone(timezone: $this->codefy->make( 50 | name: 'codefy.config' 51 | )->getConfigKey('app.timezone')); 52 | 53 | $mutex = $this->codefy->make(name: Locker::class); 54 | 55 | return tap(value: new Schedule(timeZone: $timeZone, mutex: $mutex), callback: function ($schedule) { 56 | $this->schedule(schedule: $schedule); 57 | }); 58 | }); 59 | 60 | $this->schedule = $this->codefy->make(name: Schedule::class); 61 | } 62 | 63 | /** 64 | * @throws Exception 65 | */ 66 | public function handle(InputInterface $input, ?OutputInterface $output = null): int 67 | { 68 | try { 69 | $this->bootstrap(); 70 | 71 | return $this->getCodex()->run(input: $input, output: $output); 72 | } catch (Throwable $ex) { 73 | FileLoggerSmtpFactory::critical(message: $ex->getMessage(), context: [self::class => 'handle()']); 74 | return 1; 75 | } 76 | } 77 | 78 | protected function schedule(Schedule $schedule): void 79 | { 80 | // 81 | } 82 | 83 | protected function commands(): void 84 | { 85 | // 86 | } 87 | 88 | /** 89 | * Registers a command. 90 | * 91 | * @param Command $command 92 | * @return void 93 | */ 94 | public function registerCommand(Command $command): void 95 | { 96 | $this->getCodex()->add(command: $command); 97 | } 98 | 99 | /** 100 | * Gets all the commands registered. 101 | * 102 | * @return array 103 | */ 104 | public function all(): array 105 | { 106 | $this->bootstrap(); 107 | 108 | return $this->getCodex()->all(); 109 | } 110 | 111 | /** 112 | * Get the output for the last run command. 113 | * 114 | * @return string 115 | */ 116 | public function output(): string 117 | { 118 | $this->bootstrap(); 119 | 120 | return $this->getCodex()->output(); 121 | } 122 | 123 | /** 124 | * Bootstrap the console kernel. 125 | * 126 | * @return void 127 | */ 128 | public function bootstrap(): void 129 | { 130 | if (! $this->codefy->hasBeenBootstrapped()) { 131 | $this->codefy->bootstrapWith($this->bootstrappers()); 132 | } 133 | 134 | if (false === $this->commandsLoaded) { 135 | $this->commands(); 136 | 137 | $this->commandsLoaded = true; 138 | } 139 | } 140 | 141 | /** 142 | * Retrieve the Codex instance. 143 | * 144 | * @return Codex 145 | */ 146 | protected function getCodex(): Codex 147 | { 148 | if (null === $this->codex) { 149 | return $this->codex = new Codex(codefy: $this->codefy); 150 | } 151 | 152 | return $this->codex; 153 | } 154 | 155 | /** 156 | * Run a Codex console command by name. 157 | * 158 | * @param string $command 159 | * @param array $parameters 160 | * @param bool|OutputInterface|null $outputBuffer 161 | * @return int 162 | * 163 | * @throws CommandNotFoundException 164 | * @throws Exception 165 | */ 166 | public function call(string $command, array $parameters = [], bool|OutputInterface $outputBuffer = null): int 167 | { 168 | $this->bootstrap(); 169 | 170 | return $this->getCodex()->call(command: $command, parameters: $parameters, outputBuffer: $outputBuffer); 171 | } 172 | 173 | /** 174 | * Get the bootstrappers. 175 | * 176 | * @return string[] 177 | */ 178 | protected function bootstrappers(): array 179 | { 180 | return $this->bootstrappers; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Console/Exceptions/MakeCommandFileAlreadyExistsException.php: -------------------------------------------------------------------------------- 1 | attach( 31 | object: new FileLogger(filesystem: $filesystem, threshold: LogLevel::INFO) 32 | ); 33 | 34 | return new Logger(loggers: $storage); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Factory/FileLoggerSmtpFactory.php: -------------------------------------------------------------------------------- 1 | attach( 34 | object: new FileLogger(filesystem: $filesystem, threshold: LogLevel::INFO) 35 | ); 36 | 37 | $mail = PHPMailerSmtpFactory::create(); 38 | 39 | if (env(key: 'LOGGER_FROM_EMAIL') !== null && env(key: 'LOGGER_TO_EMAIL') !== null) { 40 | $storage->attach(object: new PHPMailerLogger(mailer: $mail, threshold: LogLevel::INFO, params: [ 41 | 'from' => env(key: 'LOGGER_FROM_EMAIL'), 42 | 'to' => env(key: 'LOGGER_TO_EMAIL'), 43 | 'subject' => env(key: 'LOGGER_EMAIL_SUBJECT'), 44 | ])); 45 | } 46 | 47 | return new Logger(loggers: $storage); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Factory/PHPMailerSmtpFactory.php: -------------------------------------------------------------------------------- 1 | emergency($message, $context); 25 | } 26 | 27 | /** 28 | * Action must be taken immediately. 29 | * 30 | * Example: Entire website down, database unavailable, etc. This should 31 | * trigger the SMS alerts and wake you up. 32 | * 33 | * @param string|Stringable $message 34 | * @param array $context 35 | * @return void 36 | * @throws Exception 37 | * @throws ReflectionException 38 | */ 39 | public static function alert(string|Stringable $message, array $context = []): void 40 | { 41 | self::getLogger()->alert($message, $context); 42 | } 43 | 44 | /** 45 | * Critical conditions. 46 | * 47 | * Example: Application component unavailable, unexpected exception. 48 | * 49 | * @param string|Stringable $message 50 | * @param array $context 51 | * @return void 52 | * @throws Exception 53 | * @throws ReflectionException 54 | */ 55 | public static function critical(string|Stringable $message, array $context = []): void 56 | { 57 | self::getLogger()->critical($message, $context); 58 | } 59 | 60 | /** 61 | * Runtime errors that do not require immediate action but should typically 62 | * be logged and monitored. 63 | * 64 | * @param string|Stringable $message 65 | * @param array $context 66 | * @return void 67 | * @throws Exception 68 | * @throws ReflectionException 69 | */ 70 | public static function error(string|Stringable $message, array $context = []): void 71 | { 72 | self::getLogger()->error($message, $context); 73 | } 74 | 75 | /** 76 | * Exceptional occurrences that are not errors. 77 | * 78 | * Example: Use of deprecated APIs, poor use of an API, undesirable things 79 | * that are not necessarily wrong. 80 | * 81 | * @param string|Stringable $message 82 | * @param array $context 83 | * @return void 84 | * @throws Exception 85 | * @throws ReflectionException 86 | */ 87 | public static function warning(string|Stringable $message, array $context = []): void 88 | { 89 | self::getLogger()->warning($message, $context); 90 | } 91 | 92 | /** 93 | * Normal but significant events. 94 | * 95 | * @param string|Stringable $message 96 | * @param array $context 97 | * @return void 98 | * @throws Exception 99 | * @throws ReflectionException 100 | */ 101 | public static function notice(string|Stringable $message, array $context = []): void 102 | { 103 | self::getLogger()->notice($message, $context); 104 | } 105 | 106 | /** 107 | * Interesting events. 108 | * 109 | * Example: User logs in, SQL logs. 110 | * 111 | * @param string|Stringable $message 112 | * @param array $context 113 | * @return void 114 | * @throws Exception 115 | * @throws ReflectionException 116 | */ 117 | public static function info(string|Stringable $message, array $context = []): void 118 | { 119 | self::getLogger()->info($message, $context); 120 | } 121 | 122 | /** 123 | * Detailed debug information. 124 | * 125 | * @param string|Stringable $message 126 | * @param array $context 127 | * @return void 128 | * @throws Exception 129 | * @throws ReflectionException 130 | */ 131 | public static function debug(string|Stringable $message, array $context = []): void 132 | { 133 | self::getLogger()->debug($message, $context); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Helpers/path.php: -------------------------------------------------------------------------------- 1 | base . (!is_null__(var: $path) ? Application::DS . $path : ''); 23 | } 24 | 25 | /** 26 | * Get the path to the application "src" directory. 27 | * 28 | * @param string|null $path 29 | * @return string 30 | */ 31 | function src_path(?string $path = null): string 32 | { 33 | return app(name: 'dir.path')->path . (!is_null__(var: $path) ? Application::DS . $path : ''); 34 | } 35 | 36 | /** 37 | * Get the path to the application "bootstrap" directory. 38 | * 39 | * @param string|null $path 40 | * @return string 41 | */ 42 | function bootstrap_path(?string $path = null): string 43 | { 44 | return app(name: 'dir.path')->bootstrap . (!is_null__(var: $path) ? Application::DS . $path : ''); 45 | } 46 | 47 | /** 48 | * Get the path to the application "config" directory. 49 | * 50 | * @param string|null $path 51 | * @return string 52 | */ 53 | function config_path(?string $path = null): string 54 | { 55 | return app(name: 'dir.path')->config . (!is_null__(var: $path) ? Application::DS . $path : ''); 56 | } 57 | 58 | /** 59 | * Get the path to the application "database" directory. 60 | * 61 | * @param string|null $path 62 | * @return string 63 | */ 64 | function database_path(?string $path = null): string 65 | { 66 | return app(name: 'dir.path')->database . (!is_null__(var: $path) ? Application::DS . $path : ''); 67 | } 68 | 69 | /** 70 | * Get the path to the application "locale" directory. 71 | * 72 | * @param string|null $path 73 | * @return string 74 | */ 75 | function locale_path(?string $path = null): string 76 | { 77 | return app(name: 'dir.path')->locale . (!is_null__(var: $path) ? Application::DS . $path : ''); 78 | } 79 | 80 | /** 81 | * Get the path to the application "public" directory. 82 | * 83 | * @param string|null $path 84 | * @return string 85 | */ 86 | function public_path(?string $path = null): string 87 | { 88 | return app(name: 'dir.path')->public . (!is_null__(var: $path) ? Application::DS . $path : ''); 89 | } 90 | 91 | /** 92 | * Get the path to the application "storage" directory. 93 | * 94 | * @param string|null $path 95 | * @return string 96 | */ 97 | function storage_path(?string $path = null): string 98 | { 99 | return app(name: 'dir.path')->storage . (!is_null__(var: $path) ? Application::DS . $path : ''); 100 | } 101 | 102 | /** 103 | * Get the path to the application "resource" directory. 104 | * 105 | * @param string|null $path 106 | * @return string 107 | */ 108 | function resource_path(?string $path = null): string 109 | { 110 | return app(name: 'dir.path')->resource . (!is_null__(var: $path) ? Application::DS . $path : ''); 111 | } 112 | 113 | /** 114 | * Get the path to the application "view" directory. 115 | * 116 | * @param string|null $path 117 | * @return string 118 | */ 119 | function view_path(?string $path = null): string 120 | { 121 | return app(name: 'dir.path')->view . (!is_null__(var: $path) ? Application::DS . $path : ''); 122 | } 123 | 124 | /** 125 | * Get the path to the application "vendor" directory. 126 | * 127 | * @param string|null $path 128 | * @return string 129 | */ 130 | function vendor_path(?string $path = null): string 131 | { 132 | return app(name: 'dir.path')->vendor . (!is_null__(var: $path) ? Application::DS . $path : ''); 133 | } 134 | 135 | function router_basepath(string $path): string 136 | { 137 | $fullPath = str_replace(search: $_SERVER['DOCUMENT_ROOT'], replace: '', subject: $path); 138 | 139 | $filteredPath = __observer()->filter->applyFilter('router.basepath', $fullPath); 140 | 141 | return ltrim(string: $filteredPath, characters: '/') . '/'; 142 | } 143 | -------------------------------------------------------------------------------- /Http/BaseController.php: -------------------------------------------------------------------------------- 1 | setView(view: $view ?? new NativeLoader(config('view.path'))); 26 | } 27 | 28 | /** 29 | * Gets the view instance. 30 | * 31 | * @return Renderer 32 | */ 33 | public function getView(): Renderer 34 | { 35 | return $this->view; 36 | } 37 | 38 | /** 39 | * Sets the view instance. 40 | * 41 | * @param Renderer $view 42 | * @return BaseController 43 | */ 44 | public function setView(Renderer $view): self 45 | { 46 | $this->view = $view; 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * Redirects to given $url. 53 | * 54 | * @param string $url A string. 55 | * @param int $status HTTP status code. Defaults to `302`. 56 | * @return ResponseInterface|null 57 | */ 58 | public function redirect(string $url, int $status = 302): ?ResponseInterface 59 | { 60 | return RedirectResponseFactory::create(uri: $url, status: $status); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Http/Kernel.php: -------------------------------------------------------------------------------- 1 | codefy = $codefy; 41 | $this->router = $router; 42 | $this->router->setBaseMiddleware(middleware: $this->codefy->getBaseMiddlewares()); 43 | $this->router->setBasePath(basePath: router_basepath(path: public_path())); 44 | $this->router->setDefaultNamespace(namespace: $this->codefy->getControllerNamespace()); 45 | 46 | $this->registerErrorHandler(); 47 | } 48 | 49 | public function codefy(): Application 50 | { 51 | return $this->codefy; 52 | } 53 | 54 | /** 55 | * @throws Exception 56 | */ 57 | protected function dispatchRouter(): bool 58 | { 59 | return (new HttpPublisher())->publish( 60 | content: $this->router->match( 61 | serverRequest: ServerRequest::fromGlobals( 62 | server: $_SERVER, 63 | query: $_GET, 64 | body: $_POST, 65 | cookies: $_COOKIE, 66 | files: $_FILES 67 | ) 68 | ), 69 | emitter: new SapiEmitter() 70 | ); 71 | } 72 | 73 | /** 74 | * @throws Exception 75 | */ 76 | public function boot(): bool 77 | { 78 | if (version_compare( 79 | version1: $current = PHP_VERSION, 80 | version2: (string) $required = Application::MIN_PHP_VERSION, 81 | operator: '<' 82 | ) 83 | ) { 84 | die( 85 | sprintf( 86 | 'You are running PHP %s, but CodefyPHP requires at least PHP %s to run.', 87 | $current, 88 | $required 89 | ) 90 | ); 91 | } 92 | 93 | __observer()->action->doAction('kernel.preboot'); 94 | 95 | if (! $this->codefy->hasBeenBootstrapped()) { 96 | $this->codefy->bootstrapWith(bootstrappers: $this->bootstrappers()); 97 | } 98 | 99 | return $this->dispatchRouter(); 100 | } 101 | 102 | /** 103 | * Get the bootstrappers. 104 | * 105 | * @return string[] 106 | */ 107 | protected function bootstrappers(): array 108 | { 109 | return $this->bootstrappers; 110 | } 111 | 112 | /** 113 | * @throws \Qubus\Exception\Exception 114 | */ 115 | protected function registerErrorHandler(): ErrorHandler 116 | { 117 | if ($this->codefy()->hasDebugModeEnabled()) { 118 | return new DebugErrorHandler(); 119 | } 120 | 121 | return new ProductionErrorHandler(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 [Joshua Parker](mailto:joshua@joshuaparker.dev) 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Migration/Adapter/DbalMigrationAdapter.php: -------------------------------------------------------------------------------- 1 | connection; 26 | } 27 | 28 | /** 29 | * Get all migrated version numbers 30 | * 31 | * @return array 32 | * @throws Exception 33 | */ 34 | public function fetchAll(): array 35 | { 36 | $tableName = $this->connection->quoteIdentifier($this->tableName); 37 | $sql = DB::query(query: "SELECT version FROM $tableName ORDER BY version ASC", type: DB::SELECT)->asAssoc(); 38 | $all = $sql->execute($this->connection); 39 | 40 | return array_map(fn ($v) => $v['version'], $all); 41 | } 42 | 43 | /** 44 | * Up 45 | * 46 | * @param Migration $migration 47 | * @return MigrationAdapter 48 | */ 49 | public function up(Migration $migration): MigrationAdapter 50 | { 51 | $this->connection->insert($this->tableName) 52 | ->values( 53 | [ 54 | 'version' => $migration->getVersion(), 55 | 'recorded_on' => (new QubusDateTimeImmutable(time: 'now'))->format(format: 'Y-m-d h:i:s') 56 | ] 57 | )->execute(); 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * Down 64 | * 65 | * @param Migration $migration 66 | * @return MigrationAdapter 67 | */ 68 | public function down(Migration $migration): MigrationAdapter 69 | { 70 | $this->connection->delete($this->tableName) 71 | ->where('version', $migration->getVersion()) 72 | ->execute(); 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Is the schema ready? 79 | * 80 | * @return bool 81 | * @throws Exception 82 | */ 83 | public function hasSchema(): bool 84 | { 85 | $tables = $this->connection->listTables(); 86 | 87 | if (in_array(needle: $this->tableName, haystack: $tables)) { 88 | return true; 89 | } 90 | 91 | return false; 92 | } 93 | 94 | /** 95 | * Create Schema 96 | * 97 | * @return MigrationAdapter 98 | */ 99 | public function createSchema(): MigrationAdapter 100 | { 101 | $this->connection->schema()->create($this->tableName, function (CreateTable $table) { 102 | $table->integer(name: 'id')->size(value: 'big')->autoincrement(); 103 | $table->string(name: 'version', length: 191)->notNull(); 104 | $table->dateTime(name: 'recorded_on'); 105 | }); 106 | 107 | return $this; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Migration/Adapter/FileMigrationAdapter.php: -------------------------------------------------------------------------------- 1 | filename = $filename; 22 | } 23 | 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function fetchAll(): array 28 | { 29 | $versions = file(filename: $this->filename, flags: FILE_IGNORE_NEW_LINES); 30 | sort($versions); 31 | return $versions; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | * @throws TypeException 37 | */ 38 | public function up(Migration $migration): MigrationAdapter 39 | { 40 | $versions = $this->fetchAll(); 41 | if (in_array(needle: $migration->getVersion(), haystack: $versions)) { 42 | return $this; 43 | } 44 | 45 | $versions[] = $migration->getVersion(); 46 | $this->write(versions: $versions); 47 | return $this; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | * @throws TypeException 53 | */ 54 | public function down(Migration $migration): MigrationAdapter 55 | { 56 | $versions = $this->fetchAll(); 57 | if (!in_array(needle: $migration->getVersion(), haystack: $versions)) { 58 | return $this; 59 | } 60 | 61 | unset($versions[array_search(needle: $migration->getVersion(), haystack: $versions)]); 62 | $this->write(versions: $versions); 63 | return $this; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function hasSchema(): bool 70 | { 71 | return file_exists(filename: $this->filename); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | * @throws TypeException 77 | */ 78 | public function createSchema(): MigrationAdapter 79 | { 80 | if (!is_writable(filename: dirname(path: $this->filename))) { 81 | throw new TypeException(message: sprintf('The file "%s" is not writeable', $this->filename)); 82 | } 83 | 84 | if (false === touch(filename: $this->filename)) { 85 | throw new TypeException(message: sprintf('The file "%s" could not be written to', $this->filename)); 86 | } 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * Write to file 93 | * 94 | * @param array $versions 95 | * @throws TypeException 96 | */ 97 | protected function write(array $versions): void 98 | { 99 | if (false === file_put_contents(filename: $this->filename, data: implode(separator: "\n", array: $versions))) { 100 | throw new TypeException(message: sprintf('The file "%s" could not be written to', $this->filename)); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Migration/Adapter/MigrationAdapter.php: -------------------------------------------------------------------------------- 1 | version = $version; 39 | } 40 | 41 | /** 42 | * Init. 43 | * 44 | * @return void 45 | */ 46 | public function init(): void 47 | { 48 | return; 49 | } 50 | 51 | /** 52 | * Do the migration. 53 | * 54 | * @return void 55 | */ 56 | public function up(): void 57 | { 58 | return; 59 | } 60 | 61 | /** 62 | * Undo the migration. 63 | * 64 | * @return void 65 | */ 66 | public function down(): void 67 | { 68 | return; 69 | } 70 | 71 | /** 72 | * Get adapter. 73 | * 74 | * @return MigrationAdapter 75 | */ 76 | public function getAdapter(): MigrationAdapter 77 | { 78 | return $this->get('phpmig.adapter'); 79 | } 80 | 81 | /** 82 | * Get Version. 83 | * 84 | * @return int|string|null 85 | */ 86 | public function getVersion(): int|string|null 87 | { 88 | return $this->version; 89 | } 90 | 91 | /** 92 | * Set version. 93 | * 94 | * @param int|string $version 95 | * @return Migration 96 | */ 97 | public function setVersion(int|string $version): static 98 | { 99 | $this->version = $version; 100 | return $this; 101 | } 102 | 103 | /** 104 | * Get name. 105 | * 106 | * @return string 107 | */ 108 | public function getName(): string 109 | { 110 | return get_class(object: $this); 111 | } 112 | 113 | /** 114 | * Get ObjectMap. 115 | * 116 | * @return ArrayAccess 117 | */ 118 | public function getObjectMap(): ArrayAccess 119 | { 120 | return $this->objectmap; 121 | } 122 | 123 | /** 124 | * Set ObjectMap. 125 | * 126 | * @param ArrayAccess $objectmap 127 | * @return Migration 128 | */ 129 | public function setObjectMap(ArrayAccess $objectmap): static 130 | { 131 | $this->objectmap = $objectmap; 132 | return $this; 133 | } 134 | 135 | 136 | /** 137 | * Get Output. 138 | * 139 | * @return OutputInterface|null 140 | */ 141 | public function getOutput(): ?OutputInterface 142 | { 143 | return $this->output; 144 | } 145 | 146 | /** 147 | * Set Output. 148 | * 149 | * @param OutputInterface $output 150 | * @return Migration 151 | */ 152 | public function setOutput(OutputInterface $output): static 153 | { 154 | $this->output = $output; 155 | return $this; 156 | } 157 | 158 | /** 159 | * Get Input. 160 | * 161 | * @return InputInterface|null 162 | */ 163 | public function getInput(): ?InputInterface 164 | { 165 | return $this->input; 166 | } 167 | 168 | /** 169 | * Set Input. 170 | * 171 | * @param InputInterface $input 172 | * @return Migration 173 | */ 174 | public function setInput(InputInterface $input): static 175 | { 176 | $this->input = $input; 177 | return $this; 178 | } 179 | 180 | /** 181 | * Ask for input. 182 | * 183 | * @param Question $question 184 | * @return mixed 185 | */ 186 | public function ask(Question $question): string 187 | { 188 | return $this->getDialogHelper()->ask(input: $this->getInput(), output: $this->getOutput(), question: $question); 189 | } 190 | 191 | /** 192 | * Get something from the objectmap 193 | * 194 | * @param string $key 195 | * @return mixed 196 | */ 197 | public function get(string $key): mixed 198 | { 199 | $c = $this->getObjectMap(); 200 | return $c[$key]; 201 | } 202 | 203 | /** 204 | * Get Dialog Helper. 205 | * 206 | * @return QuestionHelper|null 207 | */ 208 | public function getDialogHelper(): ?QuestionHelper 209 | { 210 | if ($this->dialogHelper) { 211 | return $this->dialogHelper; 212 | } 213 | 214 | return $this->dialogHelper = new QuestionHelper(); 215 | } 216 | 217 | /** 218 | * Set Dialog Helper. 219 | * 220 | * @param QuestionHelper $dialogHelper 221 | * @return Migration 222 | */ 223 | public function setDialogHelper(QuestionHelper $dialogHelper): static 224 | { 225 | $this->dialogHelper = $dialogHelper; 226 | return $this; 227 | } 228 | 229 | public function schema(): Schema 230 | { 231 | return $this->getAdapter()->connection()->schema(); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Migration/Migrator.php: -------------------------------------------------------------------------------- 1 | objectmap = $objectmap; 29 | $this->adapter = $adapter; 30 | $this->output = $output; 31 | } 32 | 33 | /** 34 | * Run the up method on a migration 35 | * 36 | * @param Migration $migration 37 | * @return void 38 | */ 39 | public function up(Migration $migration): void 40 | { 41 | $this->run(migration: $migration, direction: 'up'); 42 | } 43 | 44 | /** 45 | * Run the down method on a migration 46 | * 47 | * @param Migration $migration 48 | * @return void 49 | */ 50 | public function down(Migration $migration): void 51 | { 52 | $this->run(migration: $migration, direction: 'down'); 53 | } 54 | 55 | /** 56 | * Run a migration in a particular direction 57 | * 58 | * @param Migration $migration 59 | * @param string $direction 60 | * @return void 61 | */ 62 | protected function run(Migration $migration, string $direction = 'up'): void 63 | { 64 | $direction = ($direction == 'down' ? 'down' : 'up'); 65 | $this->getOutput()?->writeln( 66 | messages: sprintf( 67 | ' == ' . 68 | $migration->getVersion() . ' ' . 69 | $migration->getName() . ' ' . 70 | '' . 71 | ($direction == 'up' ? 'migrating' : 'reverting') . 72 | '' 73 | ) 74 | ); 75 | $start = microtime(as_float: true); 76 | $migration->setObjectMap($this->getObjectMap()); 77 | $migration->init(); 78 | $migration->{$direction}(); 79 | $this->getAdapter()->{$direction}($migration); 80 | $end = microtime(as_float: true); 81 | $this->getOutput()?->writeln( 82 | messages: sprintf( 83 | ' == ' . 84 | $migration->getVersion() . ' ' . 85 | $migration->getName() . ' ' . 86 | '' . 87 | ($direction == 'up' ? 'migrated ' : 'reverted ') . 88 | sprintf("%.4fs", $end - $start) . 89 | '' 90 | ) 91 | ); 92 | } 93 | 94 | /** 95 | * Get ObjectMap. 96 | * 97 | * @return ArrayAccess 98 | */ 99 | public function getObjectMap(): ArrayAccess 100 | { 101 | return $this->objectmap; 102 | } 103 | 104 | /** 105 | * Set ObjectMap. 106 | * 107 | * @param ArrayAccess $objectmap 108 | * @return Migrator 109 | */ 110 | public function setObjectMap(ArrayAccess $objectmap): static 111 | { 112 | $this->objectmap = $objectmap; 113 | return $this; 114 | } 115 | 116 | /** 117 | * Get Adapter 118 | * 119 | * @return MigrationAdapter|null 120 | */ 121 | public function getAdapter(): ?MigrationAdapter 122 | { 123 | return $this->adapter; 124 | } 125 | 126 | /** 127 | * Set Adapter 128 | * 129 | * @param MigrationAdapter $adapter 130 | * @return Migrator 131 | */ 132 | public function setAdapter(MigrationAdapter $adapter): static 133 | { 134 | $this->adapter = $adapter; 135 | return $this; 136 | } 137 | 138 | /** 139 | * Get Output 140 | * 141 | * @return OutputInterface|null 142 | */ 143 | public function getOutput(): ?OutputInterface 144 | { 145 | return $this->output; 146 | } 147 | 148 | /** 149 | * Set Output 150 | * 151 | * @param OutputInterface $output 152 | * @return Migrator 153 | */ 154 | public function setOutput(OutputInterface $output): static 155 | { 156 | $this->output = $output; 157 | return $this; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Providers/ConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | codefy->defineParam( 18 | paramName: 'config', 19 | value: new Configuration( 20 | [ 21 | 'path' => $this->codefy->configPath(), 22 | 'dotenv' => $this->codefy->basePath(), 23 | 'environment' => env(key: 'APP_ENV', default: 'local'), 24 | ] 25 | ) 26 | ); 27 | 28 | $this->codefy->share(nameOrInstance: Configuration::class); 29 | $this->codefy->alias(original: 'codefy.config', alias: Collection::class); 30 | $this->codefy->share(nameOrInstance: 'codefy.config'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Providers/FlysystemServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerFilesystem(); 16 | } 17 | 18 | private function registerFilesystem(): void 19 | { 20 | $this->registerAdapter(); 21 | 22 | $this->codefy->delegate( 23 | name: 'filesystem.default', 24 | callableOrMethodStr: fn () => new FileSystem($this->codefy->make(name: 'flysystem.adapter')) 25 | ); 26 | } 27 | 28 | private function registerAdapter(): void 29 | { 30 | $this->codefy->delegate( 31 | name: 'flysystem.adapter', 32 | callableOrMethodStr: fn () => new LocalFlysystemAdapter($this->codefy->make(name: 'codefy.config')) 33 | ); 34 | 35 | $this->codefy->share(nameOrInstance: 'flysystem.adapter'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Providers/PdoServiceProvider.php: -------------------------------------------------------------------------------- 1 | codefy->define(name: PDO::class, args: [ 26 | ':dsn' => $dsn, 27 | ':username' => config(key: "database.connections.{$default}.username"), 28 | ':passwd' => config(key: "database.connections.{$default}.password"), 29 | ]); 30 | 31 | $this->codefy->share(nameOrInstance: PDO::class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | CodefyPHP Logo 3 |

4 | 5 |

6 | Latest Stable Version 7 | PHP 8.2 8 | License 9 | Total Downloads 10 | CodefyPHP Support Forum 11 |

12 | 13 | --- 14 | 15 | CodefyPHP is __not__ a framework such as the likes of Symfony, Laravel, Codeigniter or CakePHP. Codefy is a simple, 16 | light framework providing interfaces and implementations for architecting a Domain Driven project with 17 | CQRS, Event Sourcing and implementations of [PSR-3](https://www.php-fig.org/psr/psr-3), 18 | [PSR-6](https://www.php-fig.org/psr/psr-6), [PSR-7](https://www.php-fig.org/psr/psr-7), 19 | [PSR-11](https://www.php-fig.org/psr/psr-11), [PSR-12](https://www.php-fig.org/psr/psr-12/), 20 | [PSR-15](https://www.php-fig.org/psr/psr-15), [PSR-16](https://www.php-fig.org/psr/psr-16) 21 | and [PSR-17](https://www.php-fig.org/psr/psr-17). 22 | 23 | The philosophy of Codefy is that code should be systematized, maintainable, and follow OOP (Object-Oriented Programming). 24 | CodefyPHP tries not to be too opinionated, yet encourages best practices and coding standards by following [Qubus Coding 25 | Standards](https://github.com/QubusPHP/qubus-coding-standard). Use Codefy as you see fit. You can tap into all, some or 26 | none of the features and instead use the interfaces to build your own implementations for a domain driven project. 27 | 28 |

29 | UserId ValueObject 30 |

31 | 32 | ## 📍 Requirement 33 | - PHP 8.2+ 34 | - Additional constraints based on which components are used. 35 | 36 | ## 🏆 Highlighted Features 37 | - A powerful [routing engine](https://docs.qubusphp.com/routing/) 38 | - Robust [dependency injector](https://docs.qubusphp.com/dependency-injector/) for bootstrapping 39 | - Adapters for cookies, sessions and cache storage 40 | - Provides a simple hook and event system without affecting core code 41 | - Encourages object-oriented programming 42 | - Multiple PSR implementations 43 | - Dual query builders with migrations 44 | - Scheduler for scheduling tasks/jobs 45 | - Security and sanitizing helpers 46 | - Dual templating engines 47 | - NIST Level 2 Standard Role-Based Access Control 48 | 49 | ## 📦 Installation 50 | 51 | You can use the composer command below to install the library, or by creating a new Codefy project using the 52 | [skeleton](https://github.com/CodefyPHP/skeleton) package. 53 | 54 | ```bash 55 | composer require codefyphp/codefy 56 | ``` 57 | 58 | ## 🕑 Releases 59 | 60 | | Version | Minimum PHP Version | Release Date | Bug Fixes Until | Security Fixes Until | 61 | |---------|---------------------|----------------|-----------------|----------------------| 62 | | 1 | 8.2 | September 2023 | July 2024 | March 2025 | 63 | | 2 - LTS | 8.2 | September 2024 | September 2027 | September 2028 | 64 | | 3 | 8.3 | October 2024 | August 2025 | April 2026 | 65 | | 4 | 8.4 | February 2025 | December 2025 | August 2026 | 66 | | 5 - LTS | 8.4 | April 2025 | April 2028 | April 2029 | 67 | 68 | ## 📘 Documentation 69 | 70 | Documentation is still a work in progress. Between the [Qubus Components](https://docs.qubusphp.com/) documentation 71 | and [CodefyPHP's](https://codefyphp.com/documentation/) documentation, that should help you get started. If you have questions or 72 | need help, feel free to ask for help in the [forums](https://codefyphp.com/community/). 73 | 74 | ## 🙌 Sponsors 75 | 76 | If you use CodefyPHP or you are interested in supporting the continued development of my opensource projects, 77 | please consider sponsoring me via [Github](https://github.com/sponsors/nomadicjosh). 78 | 79 | ## 🖋 Contributing 80 | 81 | CodefyPHP could always be better! If you are interested in contributing enhancements or bug fixes, here are a few 82 | rules to follow in order to ease code reviews, and discussions before I accept and merge your work. 83 | - You MUST follow the [QubusPHP Coding Standards](https://github.com/QubusPHP/qubus-coding-standard). 84 | - You MUST write (or update) unit tests. 85 | - You SHOULD write documentation. 86 | - Please, write [commit messages that make sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), 87 | and rebase your branch before submitting your Pull Request. 88 | - Please [squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) too. 89 | This is used to "clean" your Pull Request before merging it (I don't want commits such as `fix tests`, `fix 2`, `fix 3`, 90 | etc.). 91 | 92 | ## 🔐 Security Vulnerabilities 93 | 94 | If you discover a vulnerability in the code, please email [joshua@joshuaparker.dev](mailto:joshua@joshuaparker.dev). 95 | 96 | ## 📄 License 97 | 98 | CodefyPHP is opensource software licensed under the [MIT License](https://opensource.org/license/MIT/). -------------------------------------------------------------------------------- /Scheduler/Event/TaskCompleted.php: -------------------------------------------------------------------------------- 1 | task = $task; 19 | } 20 | 21 | public function getTask(): Task 22 | { 23 | return $this->task; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Scheduler/Event/TaskFailed.php: -------------------------------------------------------------------------------- 1 | task = $task; 19 | } 20 | 21 | public function getTask(): Task 22 | { 23 | return $this->task; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Scheduler/Event/TaskSkipped.php: -------------------------------------------------------------------------------- 1 | task = $task; 19 | } 20 | 21 | public function getTask(): Task 22 | { 23 | return $this->task; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Scheduler/Expressions/At.php: -------------------------------------------------------------------------------- 1 | format('i'), 29 | $datetime->format('H'), 30 | $datetime->format('d'), 31 | $datetime->format('m') 32 | ) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Scheduler/Expressions/DayOfWeek/Friday.php: -------------------------------------------------------------------------------- 1 | processor; 23 | } 24 | 25 | public function getException(): Exception 26 | { 27 | return $this->exception; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Scheduler/Mutex/CacheLocker.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 20 | } 21 | 22 | /** 23 | * @throws InvalidArgumentException 24 | */ 25 | public function tryLock(Processor $processor): bool 26 | { 27 | $item = $this->cache->getItem($processor->mutexName()); 28 | 29 | if ($item->isHit()) { 30 | //cache is hit, cannot lock! 31 | return false; 32 | } 33 | 34 | $item->set($processor->maxRuntime() + time()); 35 | 36 | $item->expiresAfter($processor->maxRuntime()); 37 | 38 | // and saves it 39 | return $this->cache->save($item); 40 | } 41 | 42 | /** 43 | * @throws InvalidArgumentException 44 | */ 45 | public function hasLock(Processor $processor): bool 46 | { 47 | return $this->cache->hasItem($processor->mutexName()); 48 | } 49 | 50 | /** 51 | * @throws InvalidArgumentException 52 | */ 53 | public function unlock(Processor $processor): bool 54 | { 55 | return $this->cache->deleteItem($processor->mutexName()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Scheduler/Mutex/Locker.php: -------------------------------------------------------------------------------- 1 | preventOverlapping && 17 | ! $this->mutex->tryLock($this) 18 | ) { 19 | return false; 20 | } 21 | 22 | $this->callBeforeCallbacks(); 23 | 24 | $response = $this->exec($this->command); 25 | 26 | $this->callAfterCallbacks(); 27 | 28 | return $response; 29 | } 30 | 31 | /** 32 | * Executes command. 33 | */ 34 | private function exec(callable $fn): mixed 35 | { 36 | $data = $this->call($fn, $this->args, true); 37 | 38 | return is_string($data) ? $data : ''; 39 | } 40 | 41 | public function __toString(): string 42 | { 43 | return ! empty($this->description) 44 | ? $this->description 45 | : 'callback'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Scheduler/Processor/Dispatcher.php: -------------------------------------------------------------------------------- 1 | command; 17 | } 18 | 19 | public function run(): bool 20 | { 21 | if ( 22 | $this->preventOverlapping && 23 | ! $this->mutex->tryLock($this) 24 | ) { 25 | return false; 26 | } 27 | 28 | $this->callBeforeCallbacks(); 29 | 30 | $this->dispatcher->dispatch($this->command); 31 | 32 | $this->callAfterCallbacks(); 33 | 34 | return true; 35 | } 36 | 37 | public function setDispatcher(EventDispatcher $dispatcher): static 38 | { 39 | $this->dispatcher = $dispatcher; 40 | return $this; 41 | } 42 | 43 | public function __toString(): string 44 | { 45 | return $this->getCommand(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Scheduler/Processor/Processor.php: -------------------------------------------------------------------------------- 1 | command; 19 | } 20 | 21 | /** 22 | * Executes the shell command. 23 | */ 24 | public function run(): bool 25 | { 26 | if ( 27 | $this->preventOverlapping && 28 | ! $this->mutex->tryLock($this) 29 | ) { 30 | return false; 31 | } 32 | 33 | $this->callBeforeCallbacks(); 34 | 35 | $task = $this->canRunCommandInBackground() ? $this->runCommandInBackground() : $this->runCommandInForeground(); 36 | 37 | exec($task); 38 | 39 | $this->callAfterCallbacks(); 40 | 41 | return true; 42 | } 43 | 44 | public function __toString(): string 45 | { 46 | return $this->getCommand(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Scheduler/Stack.php: -------------------------------------------------------------------------------- 1 | dispatcher = $dispatcher; 38 | $this->mutex = $mutex; 39 | $this->timezone = $timezone; 40 | } 41 | 42 | /** 43 | * Add tasks to the list. 44 | * 45 | * @param Task[] $tasks 46 | */ 47 | public function addTasks(array $tasks): void 48 | { 49 | foreach ($tasks as $task) { 50 | $this->addTask($task); 51 | } 52 | } 53 | 54 | /** 55 | * Add a task to the list. 56 | */ 57 | public function addTask(Task $task): Stack 58 | { 59 | $this->tasks[] = $task; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Returns an array of tasks. 66 | */ 67 | public function tasks(): array 68 | { 69 | return $this->tasks; 70 | } 71 | 72 | /** 73 | * Gets tasks that are due and can run. 74 | * 75 | * @return array 76 | */ 77 | public function tasksDue(): array 78 | { 79 | $that = clone $this; 80 | 81 | return array_filter($this->tasks, function (Task $task) use ($that) { 82 | return $task->isDue($that->timezone); 83 | }); 84 | } 85 | 86 | public function withOptions(array $options = []): self 87 | { 88 | $resolver = new OptionsResolver(); 89 | $this->configureOptions($resolver); 90 | 91 | $this->options = $resolver->resolve($options); 92 | 93 | return $this; 94 | } 95 | 96 | protected function configureOptions(OptionsResolver $resolver): void 97 | { 98 | $resolver->setDefaults([ 99 | 'dispatcher' => $this->dispatcher, 100 | 'mutex' => $this->mutex, 101 | 'timezone' => $this->timezone, 102 | 'maxRuntime' => 120, 103 | 'encryption' => null, 104 | ]); 105 | } 106 | 107 | public function run(): void 108 | { 109 | $schedule = new Schedule($this->options['timezone'], $this->options['mutex']); 110 | 111 | foreach ($this->tasks() as $task) { 112 | if (is_false__($task->getOption('enabled'))) { 113 | continue; 114 | } 115 | 116 | $this->dispatcher->dispatch(TaskStarted::EVENT_NAME); 117 | 118 | try { 119 | // Run code before executing task. 120 | $task->setUp(); 121 | // Execute task. 122 | $task->execute($schedule); 123 | // Run code after executing task. 124 | $task->tearDown(); 125 | } catch (Exception $ex) { 126 | $this->dispatcher->dispatch(TaskFailed::EVENT_NAME); 127 | $this->sendEmail($ex); 128 | } 129 | 130 | $this->dispatcher->dispatch(TaskCompleted::EVENT_NAME); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Scheduler/Task.php: -------------------------------------------------------------------------------- 1 | EveryMinute::make(), 48 | '@weekdays' => WeekDays::make(), 49 | '@weekends' => WeekEnds::make(), 50 | '@quarterly' => Quarterly::make(), 51 | '@sunday', '@sun' => Sunday::make(), 52 | '@monday', '@mon' => Monday::make(), 53 | '@tuesday', '@tu', '@tue', '@tues' => Tuesday::make(), 54 | '@wednesday', '@wed' => Wednesday::make(), 55 | '@thursday', '@th', '@thu', '@thur', '@thurs' => Thursday::make(), 56 | '@friday', '@fri' => Friday::make(), 57 | '@saturday', '@sat' => Saturday::make(), 58 | '@january', '@jan' => January::make(), 59 | '@february', '@feb' => February::make(), 60 | '@march', '@mar' => March::make(), 61 | '@april', '@apr' => April::make(), 62 | '@may' => May::make(), 63 | '@june', '@jun' => June::make(), 64 | '@july', '@jul' => July::make(), 65 | '@august', '@aug' => August::make(), 66 | '@september', '@sept', '@sep' => September::make(), 67 | '@october', '@oct' => October::make(), 68 | '@november', '@nov' => November::make(), 69 | '@december', '@dec' => December::make(), 70 | default => At::make($literal ?? '* * * * *') 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Scheduler/Traits/MailerAware.php: -------------------------------------------------------------------------------- 1 | options['recipients'])) { 25 | return false; 26 | } 27 | 28 | if (is_null__($this->options['smtpSender'])) { 29 | return false; 30 | } 31 | 32 | $mailer->send(function ($message) use ($ex) { 33 | $message->to(explode(',', $this->options['recipients'])); 34 | $message->from($this->options['smtpSender'], $this->options['smtpSenderName']); 35 | $message->subject(sprintf('[%s] A Task Needs Attention!', php_uname('n'))); 36 | $message->body($ex->getMessage()); 37 | $message->charset('utf-8'); 38 | $message->html(false); 39 | }); 40 | 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Scheduler/Traits/ScheduleValidateAware.php: -------------------------------------------------------------------------------- 1 | self::range($minute, 0, 59), 28 | 'hour' => self::range($hour, 0, 23), 29 | 'day' => self::range($day, 1, 31), 30 | 'month' => self::range($month, 1, 12), 31 | 'weekday' => self::range($weekday, 0, 6), 32 | ]; 33 | } 34 | 35 | /** 36 | * @throws TypeException 37 | */ 38 | protected static function range(int|string|array|null $value = null, int $min = 0, int $max = 0): int|string|null 39 | { 40 | if ($value === '*') { 41 | return '*'; 42 | } 43 | 44 | if (is_array($value)) { 45 | foreach ($value as $v) { 46 | if ( 47 | ! is_numeric($v) || 48 | ! ($v >= $min && $v <= $max) 49 | ) { 50 | throw new TypeException( 51 | sprintf( 52 | "Invalid value. It should be '*' or between %s and %s.", 53 | $min, 54 | $max 55 | ) 56 | ); 57 | } 58 | } 59 | 60 | $value = implode(',', $value); 61 | return $value; 62 | } 63 | 64 | if ( 65 | null !== $value && (! is_numeric($value) || 66 | ! ($value >= $min && $value <= $max)) 67 | ) { 68 | throw new TypeException( 69 | sprintf( 70 | "Invalid value. It should be '*' or between %s and %s.", 71 | $min, 72 | $max 73 | ) 74 | ); 75 | } 76 | 77 | return $value; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Scheduler/ValueObject/TaskId.php: -------------------------------------------------------------------------------- 1 | [ 56 | 'public' => self::getConfigForDriverName(name: $name)['permission']['file']['public'], 57 | 'private' => self::getConfigForDriverName(name: $name)['permission']['file']['private'], 58 | ], 59 | 'dir' => [ 60 | 'public' => self::getConfigForDriverName(name: $name)['permission']['dir']['public'], 61 | 'private' => self::getConfigForDriverName(name: $name)['permission']['dir']['private'], 62 | ], 63 | ]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Support/Password.php: -------------------------------------------------------------------------------- 1 | filter->applyFilter('password.hash.algo', $algo); 35 | } 36 | 37 | /** 38 | * An associative array containing options. 39 | * 40 | * @return array Array of options. 41 | */ 42 | private static function options(): array 43 | { 44 | /** 45 | * Filters the password_hash() options parameter. 46 | * 47 | * @param array $options Options to pass to password_hash() function. 48 | */ 49 | return __observer()->filter->applyFilter( 50 | 'password.hash.options', 51 | (array) ['memory_cost' => 1 << 12, 'time_cost' => 2, 'threads' => 2] 52 | ); 53 | } 54 | 55 | /** 56 | * Hashes a plain text password. 57 | * 58 | * @param string $password Plain text password 59 | * @return string Hashed password. 60 | */ 61 | public static function hash(string $password): string 62 | { 63 | return password_hash(password: $password, algo: self::algorithm(), options: self::options()); 64 | } 65 | 66 | /** 67 | * Checks if the given hash matches the given options. 68 | * 69 | * @param string $password 70 | * @param string $hash 71 | * @return bool 72 | */ 73 | public static function verify(string $password, string $hash): bool 74 | { 75 | return password_verify($password, $hash); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Support/Paths.php: -------------------------------------------------------------------------------- 1 | paths = array_map(callback: function ($path) { 52 | return rtrim(string: $path, characters: '\/'); 53 | }, array: $paths); 54 | 55 | // Assume a standard Composer directory structure unless specified 56 | $this->paths['vendor'] = $this->vendor ?? $this->base . '/vendor'; 57 | } 58 | 59 | public function __get(mixed $name): ?string 60 | { 61 | return $this->paths[$name] ?? null; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /View/FenomView.php: -------------------------------------------------------------------------------- 1 | fenom = (new Fenom( 21 | provider: new Provider(template_dir: config(key: 'view.path')) 22 | ))->setCompileDir( 23 | dir: config(key: 'view.cache') 24 | )->setOptions(options: config(key: 'view.options')); 25 | } 26 | 27 | /** 28 | * @throws CompileException 29 | */ 30 | public function render(array|string $template, array $data = []): string|array 31 | { 32 | return $this->fenom->display(template: $template, vars: $data); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /View/FoilView.php: -------------------------------------------------------------------------------- 1 | engine = engine(config(key: 'view.options')); 20 | } 21 | 22 | public function render(array|string $template, array $data = []): string|array 23 | { 24 | return $this->engine->render(template: $template, data: $data); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codefyphp/codefy", 3 | "type": "library", 4 | "description": "PHP framework for Domain Driven Development, CQRS, and Event Sourcing.", 5 | "keywords": ["codefy","codefyphp","codefy-php","routing","application","container","framework","php-framework"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Joshua Parker", 10 | "email": "joshua@joshuaparker.dev" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=8.2", 15 | "ext-pdo": "*", 16 | "codefyphp/domain-driven-core": "^1", 17 | "dragonmantank/cron-expression": "^3", 18 | "qubus/cache": "^3", 19 | "qubus/error": "^2", 20 | "qubus/event-dispatcher": "^3", 21 | "qubus/exception": "^3", 22 | "qubus/expressive": "^1", 23 | "qubus/filesystem": "^3", 24 | "qubus/injector": "^3", 25 | "qubus/mail": "^4", 26 | "qubus/router": "^3", 27 | "qubus/security": "^3", 28 | "qubus/support": "^3", 29 | "qubus/view": "^2", 30 | "symfony/console": "^6", 31 | "symfony/options-resolver": "^6" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Codefy\\Framework\\": "" 36 | }, 37 | "files": [ 38 | "Helpers/core.php", 39 | "Helpers/path.php" 40 | ] 41 | }, 42 | "require-dev": { 43 | "fenom/fenom": "^3.0", 44 | "fenom/providers-collection": "^1.0", 45 | "foil/foil": "^0.6.7", 46 | "mockery/mockery": "^1.3.1", 47 | "pestphp/pest": "^1.22", 48 | "pestphp/pest-plugin-mock": "^1.0", 49 | "qubus/qubus-coding-standard": "^1.1" 50 | }, 51 | "scripts": { 52 | "test": "XDEBUG_MODE=coverage vendor/bin/pest --coverage --min=50 --colors=always", 53 | "cs-check": "phpcs", 54 | "cs-fix": "phpcbf" 55 | }, 56 | "config": { 57 | "optimize-autoloader": true, 58 | "sort-packages": true, 59 | "allow-plugins": { 60 | "dealerdirect/phpcodesniffer-composer-installer": true, 61 | "pestphp/pest-plugin": true 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Auth 16 | Bootstrap 17 | Console 18 | Contracts 19 | Factory 20 | Helpers 21 | Http 22 | Migration 23 | Providers 24 | Scheduler 25 | Support 26 | View 27 | Application.php 28 | Codefy.php 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | ./vendor 15 | ./tests 16 | 17 | 18 | ./ 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/Auth/PermissionTest.php: -------------------------------------------------------------------------------- 1 | getName()); 17 | }); 18 | 19 | it('should get permission description.', function () use ($resource) { 20 | $permission = new \Codefy\Framework\Auth\Rbac\Entity\RbacPermission( 21 | permissionName: 'admin:edit', 22 | description: 'Edit permission.', 23 | rbacStorageCollection: $resource 24 | ); 25 | 26 | Assert::assertEquals('Edit permission.', $permission->getDescription()); 27 | }); 28 | 29 | it('should get children.', function () use ($resource) { 30 | $permission2 = new \Codefy\Framework\Auth\Rbac\Entity\RbacPermission( 31 | permissionName: 'perm:perm2', 32 | description: 'desc2', 33 | rbacStorageCollection: $resource 34 | ); 35 | 36 | $permission3 = new \Codefy\Framework\Auth\Rbac\Entity\RbacPermission( 37 | permissionName: 'perm:perm3', 38 | description: 'desc3', 39 | rbacStorageCollection: $resource 40 | ); 41 | 42 | $resource = Mockery::mock(\Codefy\Framework\Auth\Rbac\Resource\StorageResource::class); 43 | $resource->shouldReceive('getPermission')->andReturn($permission2, $permission3); 44 | 45 | $permission1 = new \Codefy\Framework\Auth\Rbac\Entity\RbacPermission( 46 | permissionName: 'perm:perm1', 47 | description: 'desc1', 48 | rbacStorageCollection: $resource 49 | ); 50 | 51 | $permission1->addChild($permission2); 52 | $permission1->addChild($permission3); 53 | 54 | Assert::assertEquals( 55 | [ 56 | 'perm:perm2' => $permission2, 57 | 'perm:perm3' => $permission3 58 | ], 59 | $permission1->getChildren() 60 | ); 61 | 62 | $permission1->removeChild(permissionName: 'perm:perm2'); 63 | 64 | Assert::assertEquals(['perm:perm3' => $permission3], $permission1->getChildren()); 65 | }); 66 | 67 | it('should set and get rule.', function () use ($resource) { 68 | $permission = new \Codefy\Framework\Auth\Rbac\Entity\RbacPermission( 69 | permissionName: 'admin:edit', 70 | description: 'Edit permission.', 71 | rbacStorageCollection: $resource 72 | ); 73 | 74 | $permission->setRuleClass(ruleClass: 'AuthorRule'); 75 | 76 | Assert::assertEquals('AuthorRule', $permission->getRuleClass()); 77 | }); 78 | -------------------------------------------------------------------------------- /tests/Auth/RbacTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('load'); 14 | 15 | new Rbac($resource); 16 | 17 | Assert::assertTrue(true); 18 | }); 19 | 20 | it('should create role and return Role instance.', function () use ($resource) { 21 | $resource->shouldReceive('load'); 22 | $resource->shouldReceive('addRole') 23 | ->with('role1', 'desc1') 24 | ->andReturn(Mockery::mock(Role::class)); 25 | 26 | $rbac = new Rbac($resource); 27 | 28 | Assert::assertInstanceOf(Role::class, $rbac->addRole(name: 'role1', description: 'desc1')); 29 | }); 30 | 31 | it('should create Permission and return Permission instance.', function () use ($resource) { 32 | $resource->shouldReceive('load'); 33 | $resource->shouldReceive('addPermission') 34 | ->with('dashboard:view', 'Access dashboard.') 35 | ->andReturn(Mockery::mock(Permission::class)); 36 | 37 | $rbac = new Rbac($resource); 38 | 39 | Assert::assertInstanceOf( 40 | Permission::class, 41 | $rbac->addPermission(name: 'dashboard:view', description: 'Access dashboard.') 42 | ); 43 | }); 44 | 45 | it('should get roles.', function () use ($resource) { 46 | $resource->shouldReceive('load'); 47 | $resource->shouldReceive('getRoles') 48 | ->andReturn([]); 49 | 50 | $rbac = new Rbac($resource); 51 | 52 | Assert::assertEquals([], $rbac->getRoles()); 53 | }); 54 | 55 | it('should get role.', function () use ($resource) { 56 | $resource->shouldReceive('load'); 57 | $resource->shouldReceive('getRole') 58 | ->with('role1') 59 | ->andReturn(Mockery::mock(Role::class)); 60 | 61 | $rbac = new Rbac($resource); 62 | 63 | Assert::assertInstanceOf(Role::class, $rbac->getRole('role1')); 64 | }); 65 | 66 | it('should get permissions.', function () use ($resource) { 67 | $resource->shouldReceive('load'); 68 | $resource->shouldReceive('getPermissions') 69 | ->andReturn([]); 70 | 71 | $rbac = new Rbac($resource); 72 | 73 | Assert::assertEquals([], $rbac->getPermissions()); 74 | }); 75 | 76 | it('should get permission.', function () use ($resource) { 77 | $resource->shouldReceive('load'); 78 | $resource->shouldReceive('getPermission') 79 | ->with('perm1') 80 | ->andReturn(Mockery::mock(Permission::class)); 81 | 82 | $rbac = new Rbac($resource); 83 | 84 | Assert::assertInstanceOf(Permission::class, $rbac->getPermission(name: 'perm1')); 85 | }); 86 | -------------------------------------------------------------------------------- /tests/Auth/RoleTest.php: -------------------------------------------------------------------------------- 1 | getName()); 17 | }); 18 | 19 | it('should get the role description', function () use ($resource) { 20 | $role = new \Codefy\Framework\Auth\Rbac\Entity\RbacRole( 21 | roleName: 'admin', 22 | description: 'Super administrator.', 23 | rbacStorageCollection: $resource 24 | ); 25 | 26 | Assert::assertEquals('Super administrator.', $role->getDescription()); 27 | }); 28 | 29 | it('should get children.', function () use ($resource) { 30 | $role2 = new \Codefy\Framework\Auth\Rbac\Entity\RbacRole( 31 | roleName: 'role2', 32 | description: 'desc2', 33 | rbacStorageCollection: $resource 34 | ); 35 | 36 | $role3 = new \Codefy\Framework\Auth\Rbac\Entity\RbacRole( 37 | roleName: 'role3', 38 | description: 'desc3', 39 | rbacStorageCollection: $resource 40 | ); 41 | 42 | $resource = Mockery::mock(\Codefy\Framework\Auth\Rbac\Resource\StorageResource::class); 43 | $resource->shouldReceive('getRole')->andReturn($role2, $role3); 44 | 45 | $role1 = new \Codefy\Framework\Auth\Rbac\Entity\RbacRole( 46 | roleName: 'role1', 47 | description: 'desc1', 48 | rbacStorageCollection: $resource 49 | ); 50 | 51 | $role1->addChild($role2); 52 | $role1->addChild($role3); 53 | 54 | Assert::assertEquals( 55 | [ 56 | 'role2' => $role2, 57 | 'role3' => $role3 58 | ], 59 | $role1->getChildren() 60 | ); 61 | 62 | $role1->removeChild(roleName: 'role2'); 63 | 64 | Assert::assertEquals(['role3' => $role3], $role1->getChildren()); 65 | }); 66 | 67 | it('should get permissions.', function () use ($resource) { 68 | $permission2 = new \Codefy\Framework\Auth\Rbac\Entity\RbacPermission( 69 | permissionName: 'perm:perm2', 70 | description: 'desc2', 71 | rbacStorageCollection: $resource 72 | ); 73 | 74 | $permission3 = new \Codefy\Framework\Auth\Rbac\Entity\RbacPermission( 75 | permissionName: 'perm:perm3', 76 | description: 'desc3', 77 | rbacStorageCollection: $resource 78 | ); 79 | 80 | $resource = Mockery::mock(\Codefy\Framework\Auth\Rbac\Resource\StorageResource::class); 81 | $resource->shouldReceive('getPermission')->andReturn($permission2, $permission3); 82 | 83 | $role1 = new \Codefy\Framework\Auth\Rbac\Entity\RbacRole( 84 | roleName: 'admin', 85 | description: 'Super administrator.', 86 | rbacStorageCollection: $resource 87 | ); 88 | 89 | $role1->addPermission($permission2); 90 | $role1->addPermission($permission3); 91 | 92 | Assert::assertEquals( 93 | [ 94 | 'perm:perm2' => $permission2, 95 | 'perm:perm3' => $permission3 96 | ], 97 | $role1->getPermissions() 98 | ); 99 | 100 | $role1->removePermission(permissionName: 'perm:perm2'); 101 | 102 | Assert::assertEquals(['perm:perm3' => $permission3], $role1->getPermissions()); 103 | }); 104 | -------------------------------------------------------------------------------- /tests/Expressions/DailyTest.php: -------------------------------------------------------------------------------- 1 | isDue(DateTime::createFromFormat('H:i', '00:00'))); 11 | 12 | Assert::assertTrue(At::make('0 0 * * *')->isDue(DateTime::createFromFormat('H:i', '00:00'))); 13 | }); 14 | 15 | it('should run with custom input.', function () { 16 | Assert::assertTrue(Daily::make(19, 53)->isDue(DateTime::createFromFormat('H:i', '19:53'))); 17 | Assert::assertFalse(Daily::make(19, 53)->isDue(DateTime::createFromFormat('H:i', '18:53'))); 18 | 19 | Assert::assertTrue(At::make('53 19 * * *')->isDue(DateTime::createFromFormat('H:i', '19:53'))); 20 | Assert::assertFalse(At::make('53 19 * * *')->isDue(DateTime::createFromFormat('H:i', '18:53'))); 21 | 22 | // Run daily at 12:00 AM and 12:00 PM 23 | Assert::assertTrue(Daily::make([0,12])->isDue(DateTime::createFromFormat('H:i', '00:00'))); 24 | Assert::assertFalse(Daily::make([0,12])->isDue(DateTime::createFromFormat('H:i', '00:01'))); 25 | 26 | Assert::assertTrue(Daily::make([0,12])->isDue(DateTime::createFromFormat('H:i', '12:00'))); 27 | Assert::assertFalse(Daily::make([0,12])->isDue(DateTime::createFromFormat('H:i', '12:01'))); 28 | 29 | // Run daily at 12:30 AM and 12:30 PM 30 | Assert::assertTrue(Daily::make([0,12], 30)->isDue(DateTime::createFromFormat('H:i', '00:30'))); 31 | Assert::assertFalse(Daily::make([0,12], 30)->isDue(DateTime::createFromFormat('H:i', '00:31'))); 32 | 33 | Assert::assertTrue(Daily::make([0,12], 30)->isDue(DateTime::createFromFormat('H:i', '12:30'))); 34 | Assert::assertFalse(Daily::make([0,12], 30)->isDue(DateTime::createFromFormat('H:i', '12:31'))); 35 | 36 | // Run daily at 12:00 & 12:30 AM and 12:00 & 12:30 PM 37 | Assert::assertTrue(Daily::make([0,12], [0, 30])->isDue(DateTime::createFromFormat('H:i', '00:00'))); 38 | Assert::assertFalse(Daily::make([0,12], [0, 30])->isDue(DateTime::createFromFormat('H:i', '00:01'))); 39 | 40 | Assert::assertTrue(Daily::make([0,12], [0, 30])->isDue(DateTime::createFromFormat('H:i', '00:30'))); 41 | Assert::assertFalse(Daily::make([0,12], [0, 30])->isDue(DateTime::createFromFormat('H:i', '00:31'))); 42 | 43 | Assert::assertTrue(Daily::make([0,12], [0,30])->isDue(DateTime::createFromFormat('H:i', '12:00'))); 44 | Assert::assertFalse(Daily::make([0,12], [0,30])->isDue(DateTime::createFromFormat('H:i', '12:01'))); 45 | 46 | Assert::assertTrue(Daily::make([0,12], [0,30])->isDue(DateTime::createFromFormat('H:i', '12:30'))); 47 | Assert::assertFalse(Daily::make([0,12], [0,30])->isDue(DateTime::createFromFormat('H:i', '12:31'))); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/Expressions/DateTest.php: -------------------------------------------------------------------------------- 1 | isDue(new DateTime($date))); 12 | Assert::assertTrue(Date::make(new DateTime($date))->isDue(new DateTime($date))); 13 | Assert::assertFalse(Date::make($date)->isDue(new DateTime('2021-12-02'))); 14 | }); 15 | 16 | it('should run at a specific date and time.', function () { 17 | $date = '2021-12-01 12:25'; 18 | 19 | Assert::assertTrue(Date::make($date)->isDue(new DateTime($date))); 20 | Assert::assertTrue(Date::make(new DateTime($date))->isDue(new DateTime($date))); 21 | Assert::assertFalse(Date::make($date)->isDue(new DateTime('2021-12-02 12:30'))); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/Expressions/DayOfWeekTest.php: -------------------------------------------------------------------------------- 1 | isDue(new DateTime('Sunday'))); 17 | Assert::assertTrue(Monday::make()->isDue(new DateTime('Monday'))); 18 | Assert::assertTrue(Tuesday::make()->isDue(new DateTime('Tuesday'))); 19 | Assert::assertTrue(Wednesday::make()->isDue(new DateTime('Wed'))); 20 | Assert::assertTrue(Thursday::make()->isDue(new DateTime('Thu'))); 21 | Assert::assertTrue(Friday::make()->isDue(new DateTime('Fri'))); 22 | Assert::assertTrue(Saturday::make()->isDue(new DateTime('Sat'))); 23 | }); 24 | 25 | it('should run on specific days of the week with custom input.', function () { 26 | // Each day at 5:30 AM 27 | Assert::assertTrue(Sunday::make(5, 30)->isDue(new DateTime('Sunday 05:30'))); 28 | Assert::assertFalse(Sunday::make(5, 35)->isDue(new DateTime('Sunday 05:30'))); 29 | 30 | Assert::assertTrue(Monday::make(5, 30)->isDue(new DateTime('Monday 05:30'))); 31 | Assert::assertFalse(Monday::make(5, 30)->isDue(new DateTime('Monday 05:35'))); 32 | 33 | Assert::assertTrue(Tuesday::make(5, 30)->isDue(new DateTime('Tuesday 05:30'))); 34 | Assert::assertFalse(Tuesday::make(5, 35)->isDue(new DateTime('Tuesday 05:30'))); 35 | 36 | Assert::assertTrue(Wednesday::make(5, 30)->isDue(new DateTime('Wed 05:30'))); 37 | Assert::assertFalse(Wednesday::make(5, 30)->isDue(new DateTime('Wed 05:35'))); 38 | 39 | Assert::assertTrue(Thursday::make(5, 30)->isDue(new DateTime('Thu 05:30'))); 40 | Assert::assertFalse(Thursday::make(5, 35)->isDue(new DateTime('Thu 05:30'))); 41 | 42 | Assert::assertTrue(Friday::make(5, 30)->isDue(new DateTime('Fri 05:30'))); 43 | Assert::assertFalse(Friday::make(5, 30)->isDue(new DateTime('Fri 05:35'))); 44 | 45 | Assert::assertTrue(Saturday::make(5, 30)->isDue(new DateTime('Sat 05:30'))); 46 | Assert::assertFalse(Saturday::make(5, 35)->isDue(new DateTime('Sat 05:30'))); 47 | }); 48 | 49 | it('should throw exception for invalid hour input.', function () { 50 | Saturday::make('invalid', 30); 51 | })->throws(TypeException::class); 52 | 53 | it('should throw exception for invalid minute input.', function () { 54 | Saturday::make(5, 'invalid'); 55 | })->throws(TypeException::class); 56 | -------------------------------------------------------------------------------- /tests/Expressions/EveryMinuteTest.php: -------------------------------------------------------------------------------- 1 | isDue(DateTime::createFromFormat('H:i', '00:00'))); 11 | 12 | Assert::assertTrue(At::make('* * * * *')->isDue(DateTime::createFromFormat('H:i', '00:00'))); 13 | }); 14 | 15 | it('should run every 5 minutes.', function () { 16 | Assert::assertTrue(EveryMinute::make(5)->isDue(DateTime::createFromFormat('H:i', '00:05'))); 17 | 18 | Assert::assertTrue(At::make('*/5 * * * *')->isDue(DateTime::createFromFormat('H:i', '00:05'))); 19 | }); 20 | 21 | it('should run every nth minute.', function () { 22 | Assert::assertTrue(EveryMinute::make([5, 20, 45])->isDue(DateTime::createFromFormat('H:i', '00:05'))); 23 | Assert::assertFalse(EveryMinute::make([5, 20, 45])->isDue(DateTime::createFromFormat('H:i', '00:06'))); 24 | 25 | Assert::assertTrue(EveryMinute::make([5, 20, 45])->isDue(DateTime::createFromFormat('H:i', '18:45'))); 26 | Assert::assertFalse(EveryMinute::make([5, 20, 45])->isDue(DateTime::createFromFormat('H:i', '18:46'))); 27 | 28 | Assert::assertTrue(At::make('5,20,45 * * * *')->isDue(DateTime::createFromFormat('H:i', '00:05'))); 29 | Assert::assertFalse(At::make('5,20,45 * * * *')->isDue(DateTime::createFromFormat('H:i', '00:06'))); 30 | 31 | Assert::assertTrue(At::make('5,20,45 * * * *')->isDue(DateTime::createFromFormat('H:i', '18:45'))); 32 | Assert::assertFalse(At::make('5,20,45 * * * *')->isDue(DateTime::createFromFormat('H:i', '18:46'))); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/Expressions/HourlyTest.php: -------------------------------------------------------------------------------- 1 | isDue(DateTime::createFromFormat('H:i', '11:00'))); 11 | Assert::assertFalse(Hourly::make()->isDue(DateTime::createFromFormat('H:i', '11:01'))); 12 | 13 | Assert::assertTrue(At::make('0 * * * *')->isDue(DateTime::createFromFormat('H:i', '11:00'))); 14 | Assert::assertFalse(At::make('0 * * * *')->isDue(DateTime::createFromFormat('H:i', '11:01'))); 15 | }); 16 | 17 | it('should run hourly with custom input.', function () { 18 | Assert::assertTrue(Hourly::make(19)->isDue(DateTime::createFromFormat('H:i', '11:19'))); 19 | Assert::assertFalse(Hourly::make(19)->isDue(DateTime::createFromFormat('H:i', '11:20'))); 20 | 21 | Assert::assertTrue(At::make('19 * * * *')->isDue(DateTime::createFromFormat('H:i', '11:19'))); 22 | Assert::assertFalse(At::make('19 * * * *')->isDue(DateTime::createFromFormat('H:i', '11:20'))); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/Expressions/MonthOfYearTest.php: -------------------------------------------------------------------------------- 1 | isDue(new DateTime('01 January'))); 22 | Assert::assertFalse(January::make()->isDue(new DateTime('02 January'))); 23 | 24 | Assert::assertTrue(February::make()->isDue(new DateTime('01 Feb'))); 25 | Assert::assertFalse(February::make()->isDue(new DateTime('02 Feb'))); 26 | 27 | Assert::assertTrue(March::make()->isDue(new DateTime('01 March'))); 28 | Assert::assertFalse(March::make()->isDue(new DateTime('02 March'))); 29 | 30 | Assert::assertTrue(April::make()->isDue(new DateTime('01 April'))); 31 | Assert::assertFalse(April::make()->isDue(new DateTime('02 April'))); 32 | 33 | Assert::assertTrue(May::make()->isDue(new DateTime('01 May'))); 34 | Assert::assertFalse(May::make()->isDue(new DateTime('02 May'))); 35 | 36 | Assert::assertTrue(June::make()->isDue(new DateTime('01 June'))); 37 | Assert::assertFalse(June::make()->isDue(new DateTime('02 June'))); 38 | 39 | Assert::assertTrue(July::make()->isDue(new DateTime('01 July'))); 40 | Assert::assertFalse(July::make()->isDue(new DateTime('02 July'))); 41 | 42 | Assert::assertTrue(August::make()->isDue(new DateTime('01 Aug'))); 43 | Assert::assertFalse(August::make()->isDue(new DateTime('02 Aug'))); 44 | 45 | Assert::assertTrue(September::make()->isDue(new DateTime('01 Sept'))); 46 | Assert::assertFalse(September::make()->isDue(new DateTime('02 Sept'))); 47 | 48 | Assert::assertTrue(October::make()->isDue(new DateTime('01 Oct'))); 49 | Assert::assertFalse(October::make()->isDue(new DateTime('02 Oct'))); 50 | 51 | Assert::assertTrue(November::make()->isDue(new DateTime('01 Nov'))); 52 | Assert::assertFalse(November::make()->isDue(new DateTime('02 Nov'))); 53 | 54 | Assert::assertTrue(December::make()->isDue(new DateTime('01 Dec'))); 55 | Assert::assertFalse(December::make()->isDue(new DateTime('02 Dec'))); 56 | }); 57 | 58 | it('should run with custom month and input.', function () { 59 | // The 15th of each month at 10:00 AM. 60 | Assert::assertTrue(January::make(15, 10, 0)->isDue(new DateTime('January 15 10:00'))); 61 | Assert::assertFalse(January::make(16, 10, 0)->isDue(new DateTime('January 15 10:00'))); 62 | 63 | Assert::assertTrue(February::make(15, 10, 0)->isDue(new DateTime('Feb 15 10:00'))); 64 | Assert::assertFalse(February::make(15, 10, 0)->isDue(new DateTime('Feb 16 10:00'))); 65 | 66 | Assert::assertTrue(March::make(15, 10, 0)->isDue(new DateTime('March 15 10:00'))); 67 | Assert::assertFalse(March::make(15, 11, 0)->isDue(new DateTime('March 15 10:00'))); 68 | 69 | Assert::assertTrue(April::make(15, 10, 0)->isDue(new DateTime('April 15 10:00'))); 70 | Assert::assertFalse(April::make(15, 10, 0)->isDue(new DateTime('April 15 11:00'))); 71 | 72 | Assert::assertTrue(May::make(15, 10, 0)->isDue(new DateTime('May 15 10:00'))); 73 | Assert::assertFalse(May::make(15, 10, 30)->isDue(new DateTime('May 15 10:00'))); 74 | 75 | Assert::assertTrue(June::make(15, 10, 0)->isDue(new DateTime('June 15 10:00'))); 76 | Assert::assertFalse(June::make(15, 10, 0)->isDue(new DateTime('June 15 10:30'))); 77 | 78 | Assert::assertTrue(July::make(15, 10, 0)->isDue(new DateTime('July 15 10:00'))); 79 | Assert::assertFalse(July::make(15, 12, 0)->isDue(new DateTime('July 15 10:00'))); 80 | 81 | Assert::assertTrue(August::make(15, 10, 0)->isDue(new DateTime('Aug 15 10:00'))); 82 | Assert::assertFalse(August::make(15, 10, 0)->isDue(new DateTime('Aug 15 12:00'))); 83 | 84 | Assert::assertTrue(September::make(15, 10, 0)->isDue(new DateTime('Sept 15 10:00'))); 85 | Assert::assertFalse(September::make(15, 10, 45)->isDue(new DateTime('Sept 15 10:00'))); 86 | 87 | Assert::assertTrue(October::make(15, 10, 0)->isDue(new DateTime('Oct 15 10:00'))); 88 | Assert::assertFalse(October::make(15, 10, 0)->isDue(new DateTime('Oct 15 10:45'))); 89 | 90 | Assert::assertTrue(November::make(15, 10, 0)->isDue(new DateTime('Nov 15 10:00'))); 91 | Assert::assertFalse(November::make(18, 10, 0)->isDue(new DateTime('Nov 15 10:00'))); 92 | 93 | Assert::assertTrue(December::make(15, 10, 0)->isDue(new DateTime('Dec 15 10:00'))); 94 | Assert::assertFalse(December::make(15, 10, 0)->isDue(new DateTime('Dec 18 10:00'))); 95 | }); 96 | -------------------------------------------------------------------------------- /tests/Expressions/WeekDaysTest.php: -------------------------------------------------------------------------------- 1 | isDue(new DateTime('Monday'))); 11 | Assert::assertFalse(WeekDays::make()->isDue(new DateTime('Saturday'))); 12 | 13 | Assert::assertTrue(At::make('0 0 * * 1-5')->isDue(new DateTime('Wednesday'))); 14 | Assert::assertFalse(At::make('0 0 * * 1-5')->isDue(new DateTime('Sunday'))); 15 | }); 16 | 17 | it('should run on weekdays with custom input.', function () { 18 | Assert::assertTrue(WeekDays::make(12, 30)->isDue(new DateTime('Monday 12:30'))); 19 | Assert::assertFalse(WeekDays::make(12, 30)->isDue(new DateTime('Monday 12:31'))); 20 | 21 | Assert::assertTrue(At::make('30 12 * * 1-5')->isDue(new DateTime('Wednesday 12:30'))); 22 | Assert::assertFalse(At::make('30 12 * * 1-5')->isDue(new DateTime('Wednesday 12:31'))); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/Expressions/WeekEndsTest.php: -------------------------------------------------------------------------------- 1 | isDue(new DateTime('Saturday'))); 11 | Assert::assertFalse(WeekEnds::make()->isDue(new DateTime('Monday'))); 12 | 13 | Assert::assertTrue(At::make('0 0 * * 0,6')->isDue(new DateTime('Sunday'))); 14 | Assert::assertFalse(At::make('0 0 * * 0,6')->isDue(new DateTime('Wednesday'))); 15 | }); 16 | 17 | it('should run on weekends with custom input.', function () { 18 | Assert::assertTrue(WeekEnds::make(12, 30)->isDue(new DateTime('Saturday 12:30'))); 19 | Assert::assertFalse(WeekEnds::make(12, 30)->isDue(new DateTime('Saturday 12:31'))); 20 | 21 | Assert::assertTrue(At::make('30 12 * * 0,6')->isDue(new DateTime('Sunday 12:30'))); 22 | Assert::assertFalse(At::make('30 12 * * 0,6')->isDue(new DateTime('Sunday 12:31'))); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/Expressions/WeeklyTest.php: -------------------------------------------------------------------------------- 1 | isDue(new DateTime('Sunday'))); 11 | Assert::assertFalse(Weekly::make()->isDue(new DateTime('Saturday'))); 12 | 13 | // Sunday's at 12:00 AM 14 | Assert::assertTrue(Weekly::make(0)->isDue(new DateTime('Sunday'))); 15 | // Monday's at 12:00 AM 16 | Assert::assertTrue(Weekly::make(1)->isDue(new DateTime('Monday'))); 17 | // Tuesday's at 12:00 AM 18 | Assert::assertTrue(Weekly::make(2)->isDue(new DateTime('Tuesday'))); 19 | // Wednesday's at 12:00 AM 20 | Assert::assertTrue(Weekly::make(3)->isDue(new DateTime('Wednesday'))); 21 | // Thursday's at 12:00 AM 22 | Assert::assertTrue(Weekly::make(4)->isDue(new DateTime('Thursday'))); 23 | // Friday's at 12:00 AM 24 | Assert::assertTrue(Weekly::make(5)->isDue(new DateTime('Friday'))); 25 | // Saturday's at 12:00 AM 26 | Assert::assertTrue(Weekly::make(6)->isDue(new DateTime('Saturday'))); 27 | }); 28 | 29 | it('should run weekly with time.', function () { 30 | // Sunday's at 5:30 AM 31 | Assert::assertTrue(Weekly::make(0, 5, 30)->isDue(new DateTime('Sunday 5:30'))); 32 | Assert::assertFalse(Weekly::make(0, 5, 35)->isDue(new DateTime('Sunday 5:30'))); 33 | 34 | // Monday's at 5:30 AM 35 | Assert::assertTrue(Weekly::make(1, 5, 30)->isDue(new DateTime('Monday 5:30'))); 36 | Assert::assertFalse(Weekly::make(1, 5, 30)->isDue(new DateTime('Monday 5:35'))); 37 | 38 | // Tuesday's at 5:30 AM 39 | Assert::assertTrue(Weekly::make(2, 5, 30)->isDue(new DateTime('Tuesday 5:30'))); 40 | Assert::assertFalse(Weekly::make(2, 5, 35)->isDue(new DateTime('Tuesday 5:30'))); 41 | 42 | // Wednesday's at 5:30 AM 43 | Assert::assertTrue(Weekly::make(3, 5, 30)->isDue(new DateTime('Wednesday 5:30'))); 44 | Assert::assertFalse(Weekly::make(3, 5, 30)->isDue(new DateTime('Wednesday 5:35'))); 45 | 46 | // Thursday's at 5:30 AM 47 | Assert::assertTrue(Weekly::make(4, 5, 30)->isDue(new DateTime('Thursday 5:30'))); 48 | Assert::assertFalse(Weekly::make(4, 5, 35)->isDue(new DateTime('Thursday 5:30'))); 49 | 50 | // Friday's at 5:30 AM 51 | Assert::assertTrue(Weekly::make(5, 5, 30)->isDue(new DateTime('Friday 5:30'))); 52 | Assert::assertFalse(Weekly::make(5, 5, 30)->isDue(new DateTime('Friday 5:35'))); 53 | 54 | // Saturday's at 5:30 AM 55 | Assert::assertTrue(Weekly::make(6, 5, 30)->isDue(new DateTime('Saturday 5:30'))); 56 | Assert::assertFalse(Weekly::make(6, 5, 35)->isDue(new DateTime('Saturday 5:30'))); 57 | }); 58 | 59 | it('should run weekly only on weekends.', function () { 60 | Assert::assertTrue(Weekly::make([0, 6])->isDue(new DateTime('Saturday 00:00'))); 61 | Assert::assertTrue(Weekly::make([0, 6])->isDue(new DateTime('Sunday 00:00'))); 62 | 63 | Assert::assertFalse(Weekly::make([0, 6])->isDue(new DateTime('Monday 00:00'))); 64 | Assert::assertFalse(Weekly::make([0, 6])->isDue(new DateTime('Tuesday 00:00'))); 65 | Assert::assertFalse(Weekly::make([0, 6])->isDue(new DateTime('Wednesday 00:00'))); 66 | }); 67 | 68 | it('should through an exception on invalid day input.', function () { 69 | Weekly::make(10, 5, 30); 70 | })->throws(TypeException::class); 71 | 72 | it('should through an exception on invalid hour input.', function () { 73 | Weekly::make(5, 'invalid', 30); 74 | })->throws(TypeException::class); 75 | 76 | it('should through an exception on invalid minute input.', function () { 77 | Weekly::make(5, 5, 60); 78 | })->throws(TypeException::class); 79 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Expectations 19 | |-------------------------------------------------------------------------- 20 | | 21 | | When you're writing tests, you often need to check that values meet certain conditions. The 22 | | "expect()" function gives you access to a set of "expectations" methods that you can use 23 | | to assert different things. Of course, you may extend the Expectation API at any time. 24 | | 25 | */ 26 | 27 | expect()->extend('toBeOne', function () { 28 | return $this->toBe(1); 29 | }); 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Functions 34 | |-------------------------------------------------------------------------- 35 | | 36 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 37 | | project that you don't want to repeat in every file. Here you can also expose helpers as 38 | | global functions to help you to reduce the number of lines of code in your test files. 39 | | 40 | */ 41 | 42 | function something() 43 | { 44 | // .. 45 | } 46 | --------------------------------------------------------------------------------