├── .env.example ├── .gitignore ├── App ├── Controllers │ ├── AuthController.php │ ├── ProductController.php │ └── UserController.php ├── Core │ ├── Blueprint.php │ ├── Commands │ │ ├── Concerns │ │ │ └── MakeCommand.php │ │ ├── CreateAuthCommand.php │ │ ├── CreateControllerCommand.php │ │ ├── CreateDotEnvCommand.php │ │ ├── CreateMiddlewareCommand.php │ │ ├── CreateMigrationCommand.php │ │ ├── CreateModelCommand.php │ │ ├── CreateRouteCommand.php │ │ ├── CreateSeederCommand.php │ │ ├── CreateUpdateCommand.php │ │ ├── RunGlobalSeederCommand.php │ │ ├── RunMigrateCommand.php │ │ ├── RunMigrateDatabaseCommand.php │ │ ├── RunMigrateRefreshCommand.php │ │ ├── RunSeederCommand.php │ │ ├── ServeCommand.php │ │ └── Stubs │ │ │ ├── Auth │ │ │ ├── auth-controller.stub │ │ │ ├── auth-env.stub │ │ │ └── auth-package.stub │ │ │ ├── controller.stub │ │ │ ├── env.stub │ │ │ ├── middleware.stub │ │ │ ├── migration.stub │ │ │ ├── model.stub │ │ │ ├── routes │ │ │ ├── route-group-method.stub │ │ │ ├── route.stub │ │ │ └── use-route.stub │ │ │ ├── seeder.stub │ │ │ └── update.stub │ ├── Controller.php │ ├── Database.php │ ├── DotEnvKey.php │ ├── HasTokens.php │ ├── Input.php │ ├── Kernel.php │ ├── Middleware.php │ ├── Migration.php │ ├── Model.php │ ├── QueryBuilder.php │ ├── Request.php │ ├── Responses.php │ ├── Router.php │ ├── Schema.php │ ├── Seeder.php │ ├── Upload.php │ └── Validator.php ├── Database │ ├── Migrations │ │ ├── 2023_01_31_104005_create_table_users.php │ │ └── 2023_01_31_160221_create_table_roles.php │ └── Seeders │ │ ├── GlobalSeeder.php │ │ ├── RoleSeeder.php │ │ └── UserSeeder.php ├── Helpers │ └── TimeHelper.php ├── Middleware │ ├── AdminMiddleware.php │ └── AuthMiddleware.php ├── Models │ └── User.php ├── Packages │ └── Auth.php └── Routes │ └── Api.php ├── LICENSE.md ├── README.md ├── composer.json ├── mardira └── public ├── index.php └── logo.png /.env.example: -------------------------------------------------------------------------------- 1 | ACCESS_TOKEN_SECRET_KEY= 2 | REFRESH_TOKEN_SECRET_KEY= 3 | 4 | # Database Connection 5 | DB_HOST= 6 | DB_USER= 7 | DB_PASS= 8 | DB_NAME= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | vendor 3 | .env 4 | composer.lock 5 | .gitignore 6 | -------------------------------------------------------------------------------- /App/Controllers/AuthController.php: -------------------------------------------------------------------------------- 1 | input->file('image'); 14 | 15 | $validator = Validator::validate([ 16 | 'image' => [ 17 | 'required' => true, 18 | 'file' => true, 19 | ], 20 | ], [ 21 | 'image' => $image 22 | ]); 23 | 24 | if ($validator->fails()) { 25 | $this->response(400, [ 26 | 'status' => 'error', 27 | 'message' => 'Validation failed', 28 | 'data' => $validator->errors(), 29 | ]); 30 | } 31 | 32 | $upload = new Upload($image); 33 | $upload->setPath($_SERVER['DOCUMENT_ROOT'] . "/uploads/"); 34 | $upload->setAllowedExtensions(['jpg','png','jpeg','JPG']); 35 | $upload->setAllowedMimeTypes(['image/jpeg', 'image/png']); 36 | 37 | $upload->setRandomName(true); 38 | $upload->validate(); 39 | $upload->upload(); 40 | if ($upload->isUploaded()) { 41 | $upload->move(); 42 | $this->response(200, [ 43 | 'status' => 'success', 44 | 'message' => 'File uploaded successfully', 45 | 'data' => [ 46 | 'file' => $upload->getName(), 47 | 'path' => $upload->getPath(), 48 | ], 49 | ]); 50 | } 51 | $this->response(400, [ 52 | 'status' => 'error', 53 | 'message' => 'File not uploaded', 54 | 'data' => [ 55 | 'file' => $upload->getName(), 56 | 'path' => $upload->getPath(), 57 | ], 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /App/Controllers/UserController.php: -------------------------------------------------------------------------------- 1 | response(200, $users); 16 | } 17 | 18 | // example with query builder 19 | public function show($id) 20 | { 21 | $user = DB::table('users')->where('id', $id)->first(); 22 | $this->response(200, $user); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /App/Core/Blueprint.php: -------------------------------------------------------------------------------- 1 | table = $table; 39 | $this->connection = Database::getConnection(); 40 | } 41 | 42 | public function string(string $column, int $length = 255) 43 | { 44 | $this->columns[] = $column . ' VARCHAR(' . $length . ')'; 45 | return $this; 46 | } 47 | 48 | public function integer(string $column, int $length = 11) 49 | { 50 | $this->columns[] = $column . ' INT (' . $length . ')'; 51 | return $this; 52 | } 53 | 54 | 55 | public function mediumInteger(string $column, int $length = 8) 56 | { 57 | $this->columns[] = $column . ' MEDIUMINT(' . $length . ')'; 58 | return $this; 59 | } 60 | 61 | public function smallInteger(string $column, int $length = 6) 62 | { 63 | $this->columns[] = $column . ' SMALLINT(' . $length . ')'; 64 | return $this; 65 | } 66 | 67 | public function text(string $column) 68 | { 69 | $this->columns[] = $column . ' TEXT'; 70 | return $this; 71 | } 72 | 73 | public function longText(string $column) 74 | { 75 | $this->columns[] = $column . ' LONGTEXT'; 76 | return $this; 77 | } 78 | 79 | public function mediumText(string $column) 80 | { 81 | $this->columns[] = $column . ' MEDIUMTEXT'; 82 | return $this; 83 | } 84 | 85 | public function tinyText(string $column) 86 | { 87 | $this->columns[] = $column . ' TINYTEXT'; 88 | return $this; 89 | } 90 | 91 | public function char(string $column, int $length = 255) 92 | { 93 | $this->columns[] = $column . ' CHAR(' . $length . ')'; 94 | return $this; 95 | } 96 | 97 | public function float(string $column, int $total = 8, int $places = 2) 98 | { 99 | $this->columns[] = $column . ' FLOAT(' . $total . ',' . $places . ')'; 100 | return $this; 101 | } 102 | 103 | public function double(string $column, int $total = 8, int $places = 2) 104 | { 105 | $this->columns[] = $column . ' DOUBLE(' . $total . ',' . $places . ')'; 106 | return $this; 107 | } 108 | 109 | public function decimal(string $column, int $total = 8, int $places = 2) 110 | { 111 | $this->columns[] = $column . ' DECIMAL(' . $total . ',' . $places . ')'; 112 | return $this; 113 | } 114 | 115 | 116 | public function boolean(string $column) 117 | { 118 | $this->columns[] = $column . ' TINYINT(1)'; 119 | return $this; 120 | } 121 | 122 | public function enum(string $column, array $values) 123 | { 124 | $this->columns[] = $column . ' ENUM(' . implode(',', $values) . ')'; 125 | return $this; 126 | } 127 | 128 | public function date(string $column) 129 | { 130 | $this->columns[] = $column . ' DATE'; 131 | return $this; 132 | } 133 | 134 | public function dateTime(string $column) 135 | { 136 | $this->columns[] = $column . ' DATETIME'; 137 | return $this; 138 | } 139 | 140 | public function time(string $column) 141 | { 142 | $this->columns[] = $column . ' TIME'; 143 | return $this; 144 | } 145 | 146 | 147 | public function timestamp(string $column) 148 | { 149 | $this->columns[] = $column . ' TIMESTAMP'; 150 | return $this; 151 | } 152 | 153 | public function binary(string $column) 154 | { 155 | $this->columns[] = $column . ' BINARY'; 156 | return $this; 157 | } 158 | 159 | public function blob(string $column) 160 | { 161 | $this->columns[] = $column . ' BLOB'; 162 | return $this; 163 | } 164 | 165 | public function longBlob(string $column) 166 | { 167 | $this->columns[] = $column . ' LONGBLOB'; 168 | return $this; 169 | } 170 | 171 | public function mediumBlob(string $column) 172 | { 173 | $this->columns[] = $column . ' MEDIUMBLOB'; 174 | return $this; 175 | } 176 | 177 | public function tinyBlob(string $column) 178 | { 179 | $this->columns[] = $column . ' TINYBLOB'; 180 | return $this; 181 | } 182 | 183 | public function json(string $column) 184 | { 185 | $this->columns[] = $column . ' JSON'; 186 | return $this; 187 | } 188 | 189 | public function jsonb(string $column) 190 | { 191 | $this->columns[] = $column . ' JSONB'; 192 | return $this; 193 | } 194 | 195 | public function year(string $column) 196 | { 197 | $this->columns[] = $column . ' YEAR'; 198 | return $this; 199 | } 200 | 201 | public function timestamps() 202 | { 203 | $this->columns[] = 'created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP'; 204 | $this->columns[] = 'updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'; 205 | return $this; 206 | } 207 | 208 | public function softDeletes() 209 | { 210 | $this->columns[] = 'deleted_at TIMESTAMP NULL'; 211 | return $this; 212 | } 213 | 214 | public function primary(string $column): Blueprint 215 | { 216 | $this->primary = $column; 217 | return $this; 218 | } 219 | 220 | public function increment(string $column): Blueprint 221 | { 222 | $this->columns[] = $column . ' INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY'; 223 | return $this; 224 | } 225 | 226 | public function smallIncrement(string $column): Blueprint 227 | { 228 | $this->columns[] = $column . ' SMALLINT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY'; 229 | return $this; 230 | } 231 | 232 | public function tinyIncrement(string $column): Blueprint 233 | { 234 | $this->columns[] = $column . ' TINYINT(4) UNSIGNED AUTO_INCREMENT PRIMARY KEY'; 235 | return $this; 236 | } 237 | 238 | public function foreign(string $column): Blueprint 239 | { 240 | $this->foreign = $column; 241 | return $this; 242 | } 243 | 244 | public function references(string $column): Blueprint 245 | { 246 | $this->references = $column; 247 | return $this; 248 | } 249 | 250 | public function on(string $table): Blueprint 251 | { 252 | $this->on = $table; 253 | return $this; 254 | } 255 | 256 | public function onDelete(string $action): Blueprint 257 | { 258 | $this->onDelete = $action; 259 | return $this; 260 | } 261 | 262 | public function onUpdate(string $action): Blueprint 263 | { 264 | $this->onUpdate = $action; 265 | return $this; 266 | } 267 | 268 | public function unique(string $column): Blueprint 269 | { 270 | $this->unique = $column; 271 | return $this; 272 | } 273 | 274 | public function index($column): Blueprint 275 | { 276 | // if multiple index requested 277 | if (is_array($column)) { 278 | $this->index = implode(',', $column); 279 | return $this; 280 | } 281 | 282 | $this->index = $column; 283 | 284 | return $this; 285 | } 286 | 287 | public function default(string $value): Blueprint 288 | { 289 | $this->default = $value; 290 | return $this; 291 | } 292 | 293 | public function nullable(): Blueprint 294 | { 295 | $this->nullable = true; 296 | return $this; 297 | } 298 | 299 | public function autoIncrement(): Blueprint 300 | { 301 | $this->autoIncrement = true; 302 | return $this; 303 | } 304 | 305 | public function unsigned(): Blueprint 306 | { 307 | $this->unsigned = true; 308 | return $this; 309 | } 310 | 311 | public function after(string $column): Blueprint 312 | { 313 | $this->after = $column; 314 | return $this; 315 | } 316 | 317 | public function comment(string $comment): Blueprint 318 | { 319 | $this->comment = $comment; 320 | return $this; 321 | } 322 | 323 | public function charset(string $charset): Blueprint 324 | { 325 | $this->charset = $charset; 326 | return $this; 327 | } 328 | 329 | public function collation(string $collation): Blueprint 330 | { 331 | $this->collation = $collation; 332 | return $this; 333 | } 334 | 335 | public function engine(string $engine): Blueprint 336 | { 337 | $this->engine = $engine; 338 | return $this; 339 | } 340 | 341 | public function getConnection(): PDO 342 | { 343 | return $this->connection; 344 | } 345 | 346 | public function setConnection(PDO $connection): void 347 | { 348 | $this->connection = $connection; 349 | } 350 | 351 | public function getTable(): string 352 | { 353 | return $this->table; 354 | } 355 | 356 | public function getColumns(): array 357 | { 358 | return $this->columns; 359 | } 360 | 361 | public function getPrimary() 362 | { 363 | return $this->primary; 364 | } 365 | 366 | public function getForeign() 367 | { 368 | return $this->foreign; 369 | } 370 | 371 | public function getReferences() 372 | { 373 | return $this->references; 374 | } 375 | 376 | public function getOn() 377 | { 378 | return $this->on; 379 | } 380 | 381 | public function getOnDelete() 382 | { 383 | return $this->onDelete; 384 | } 385 | 386 | public function getOnUpdate() 387 | { 388 | return $this->onUpdate; 389 | } 390 | 391 | public function getUnique() 392 | { 393 | return $this->unique; 394 | } 395 | 396 | public function getIndex() 397 | { 398 | return $this->index; 399 | } 400 | 401 | public function getDefault() 402 | { 403 | return $this->default; 404 | } 405 | 406 | public function getNullable() 407 | { 408 | return $this->nullable; 409 | } 410 | 411 | public function getAutoIncrement() 412 | { 413 | return $this->autoIncrement; 414 | } 415 | 416 | public function getUnsigned() 417 | { 418 | return $this->unsigned; 419 | } 420 | 421 | public function getAfter() 422 | { 423 | return $this->after; 424 | } 425 | 426 | public function getComment() 427 | { 428 | return $this->comment; 429 | } 430 | 431 | public function getCharset() 432 | { 433 | return $this->charset; 434 | } 435 | 436 | public function build() 437 | { 438 | $sql = 'CREATE TABLE ' . $this->table . ' ('; 439 | $sql .= implode(', ', $this->columns); 440 | if ($this->primary) { 441 | $sql .= ', PRIMARY KEY (' . $this->primary . ')'; 442 | } 443 | if ($this->foreign) { 444 | $sql .= ', FOREIGN KEY (' . $this->foreign . ') REFERENCES ' . $this->on . '(' . $this->references . ')'; 445 | } 446 | if ($this->onDelete) { 447 | $sql .= ' ON DELETE ' . $this->onDelete; 448 | } 449 | if ($this->onUpdate) { 450 | $sql .= ' ON UPDATE ' . $this->onUpdate; 451 | } 452 | if ($this->unique) { 453 | $sql .= ', UNIQUE (' . $this->unique . ')'; 454 | } 455 | 456 | if ($this->index) { 457 | 458 | $sql .= ', INDEX (' . $this->index . ')'; 459 | } 460 | $sql .= ')'; 461 | 462 | if ($this->charset) { 463 | $sql .= ' CHARSET=' . $this->charset; 464 | } 465 | if ($this->collation) { 466 | $sql .= ' COLLATE=' . $this->collation; 467 | } 468 | if ($this->engine) { 469 | $sql .= ' ENGINE=' . $this->engine; 470 | } 471 | if ($this->comment) { 472 | $sql .= ' COMMENT=' . $this->comment; 473 | } 474 | 475 | $sql .= ';'; 476 | 477 | return $sql; 478 | } 479 | 480 | public function execute() 481 | { 482 | $sql = $this->build(); 483 | // set connection from Database class 484 | $this->connection->exec($sql); 485 | } 486 | 487 | public function create() 488 | { 489 | $this->execute(); 490 | } 491 | 492 | public function table(string $table): Blueprint 493 | { 494 | $this->table = $table; 495 | return $this; 496 | } 497 | 498 | public function drop() 499 | { 500 | $sql = 'DROP TABLE ' . $this->table . ';'; 501 | $this->connection->exec($sql); 502 | } 503 | 504 | public function truncate() 505 | { 506 | $sql = 'TRUNCATE TABLE ' . $this->table . ';'; 507 | $this->connection->exec($sql); 508 | } 509 | 510 | public function dropIfExists() 511 | { 512 | $sql = 'DROP TABLE IF EXISTS ' . $this->table . ';'; 513 | $this->connection->exec($sql); 514 | } 515 | 516 | public function truncateIfExists() 517 | { 518 | $sql = 'TRUNCATE TABLE IF EXISTS ' . $this->table . ';'; 519 | $this->connection->exec($sql); 520 | } 521 | 522 | public function dropColumn(string $column) 523 | { 524 | $sql = 'ALTER TABLE ' . $this->table . ' DROP COLUMN ' . $column . ';'; 525 | $this->connection->exec($sql); 526 | } 527 | 528 | public function dropPrimary() 529 | { 530 | $sql = 'ALTER TABLE ' . $this->table . ' DROP PRIMARY KEY;'; 531 | $this->connection->exec($sql); 532 | } 533 | 534 | public function dropForeign() 535 | { 536 | $sql = 'ALTER TABLE ' . $this->table . ' DROP FOREIGN KEY ' . $this->foreign . ';'; 537 | $this->connection->exec($sql); 538 | } 539 | 540 | public function addColumn(string $column) 541 | { 542 | $sql = 'ALTER TABLE ' . $this->table . ' ADD COLUMN ' . $column . ';'; 543 | $this->connection->exec($sql); 544 | } 545 | 546 | public function rename(string $table): Blueprint 547 | { 548 | $this->rename = $table; 549 | return $this; 550 | } 551 | 552 | public function renameColumn(string $column, string $newColumn): Blueprint 553 | { 554 | $this->renameColumn = $column; 555 | $this->newColumn = $newColumn; 556 | return $this; 557 | } 558 | 559 | 560 | public function __toString() 561 | { 562 | return $this->build(); 563 | } 564 | 565 | public function __destruct() 566 | { 567 | $this->connection = null; 568 | } 569 | 570 | public function __clone() 571 | { 572 | $this->connection = null; 573 | } 574 | } 575 | -------------------------------------------------------------------------------- /App/Core/Commands/Concerns/MakeCommand.php: -------------------------------------------------------------------------------- 1 | getStub()); 10 | 11 | $replacements = $this->getReplacements($name, $model); 12 | 13 | $stub = str_replace( 14 | array_keys($replacements), 15 | array_values($replacements), 16 | $stub 17 | ); 18 | 19 | $fileName = $this->getFileName($name); 20 | 21 | $filePath = $this->getFilePath($fileName); 22 | 23 | file_put_contents($filePath, $stub); 24 | } 25 | 26 | protected function getFilePath($fileName) 27 | { 28 | return $this->getNamespacePath() . '/' . $fileName; 29 | } 30 | 31 | protected function getNamespacePath() 32 | { 33 | $namespace = $this->getNamespace(); 34 | 35 | $namespace = str_replace('\\', '/', $namespace); 36 | 37 | return $namespace; 38 | } 39 | 40 | protected function getReplacements($name, $model) 41 | { 42 | return []; 43 | } 44 | 45 | protected function getNamespace() 46 | { 47 | return ''; 48 | } 49 | 50 | protected function getFileName($name) 51 | { 52 | return $name . '.php'; 53 | } 54 | 55 | protected function getClassName($name) 56 | { 57 | return $name; 58 | } 59 | 60 | protected function getStub() 61 | { 62 | return ''; 63 | } 64 | 65 | public function createFile($stub, $namespace, $fileName, $replacements) 66 | { 67 | $stub = file_get_contents($stub); 68 | 69 | $stub = str_replace( 70 | array_keys($replacements), 71 | array_values($replacements), 72 | $stub 73 | ); 74 | 75 | $filePath = $this->getFilePath($namespace, $fileName); 76 | 77 | file_put_contents($filePath, $stub); 78 | } 79 | } -------------------------------------------------------------------------------- /App/Core/Commands/CreateAuthCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 24 | ->setDescription($this->commandDescription) 25 | ->addOption( 26 | $this->commandOptionName, 27 | null, 28 | InputOption::VALUE_NONE, 29 | $this->commandOptionDescription 30 | ); 31 | } 32 | 33 | protected function execute(InputInterface $input, OutputInterface $output) 34 | { 35 | // if refresh Auth 36 | 37 | if ($input->getOption($this->commandOptionName)) { 38 | $this->makeAuthPackages(); 39 | $this->makeAuthController(); 40 | $output->writeln("Auth refreshed successfully."); 41 | return; 42 | } 43 | 44 | // if packages created 45 | if ($this->authPackagesAlreadyExist()) { 46 | $output->writeln("Auth already exists!"); 47 | return; 48 | } 49 | // if controller created 50 | 51 | if ($this->authControllerAlreadyExist()) { 52 | $output->writeln("Auth already exists!"); 53 | return; 54 | } 55 | 56 | $this->makeAuthPackages(); 57 | $this->makeAuthController(); 58 | $this->makeAuthEnv(); 59 | $output->writeln("Auth created successfully."); 60 | } 61 | 62 | protected function getStubPackage() 63 | { 64 | return file_get_contents(__DIR__ . '/Stubs/Auth/auth-package.stub'); 65 | } 66 | 67 | protected function getFileNamePackges() 68 | { 69 | return 'Auth.php'; 70 | } 71 | 72 | protected function getFilePathAuthPackages() 73 | { 74 | return __DIR__ . '/../../Packages/Auth.php'; 75 | } 76 | 77 | protected function authPackagesAlreadyExist() 78 | { 79 | return file_exists($this->getFilePathAuthPackages()); 80 | } 81 | 82 | protected function getStubController() 83 | { 84 | return file_get_contents(__DIR__ . '/Stubs/Auth/auth-controller.stub'); 85 | } 86 | 87 | protected function getFileNameController() 88 | { 89 | return 'AuthController.php'; 90 | } 91 | 92 | protected function getFilePathAuthController() 93 | { 94 | return __DIR__ . '/../../Controllers/AuthController.php'; 95 | } 96 | 97 | protected function authControllerAlreadyExist() 98 | { 99 | return file_exists($this->getFilePathAuthController()); 100 | } 101 | 102 | protected function getStubEnv() 103 | { 104 | return file_get_contents(__DIR__ . '/Stubs/Auth/auth-env.stub'); 105 | } 106 | 107 | protected function getFileNameEnv() 108 | { 109 | return '.env'; 110 | } 111 | 112 | protected function getFilePathAuthEnv() 113 | { 114 | return __DIR__ . '/../../../.env'; 115 | } 116 | 117 | protected function makeAuthPackages() 118 | { 119 | $stub = $this->getStubPackage(); 120 | $stub = str_replace( 121 | ['{{namespace}}'], 122 | ['App\Packages'], 123 | $stub 124 | ); 125 | file_put_contents($this->getFilePathAuthPackages(), $stub); 126 | } 127 | 128 | protected function makeAuthController() 129 | { 130 | $stub = $this->getStubController(); 131 | $stub = str_replace( 132 | ['{{namespace}}'], 133 | ['App\Http\Controllers'], 134 | $stub 135 | ); 136 | file_put_contents($this->getFilePathAuthController(), $stub); 137 | } 138 | 139 | protected function makeAuthEnv() 140 | { 141 | $stub = $this->getStubEnv(); 142 | file_put_contents($this->getFilePathAuthEnv(), $stub, FILE_APPEND); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /App/Core/Commands/CreateControllerCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 31 | ->setDescription($this->commandDescription) 32 | ->addArgument( 33 | $this->commandArgumentName, 34 | InputArgument::OPTIONAL, 35 | $this->commandArgumentDescription 36 | ) 37 | ->addOption( 38 | $this->commandOptionName, 39 | null, 40 | InputOption::VALUE_OPTIONAL, 41 | $this->commandOptionDescription 42 | )->addOption( 43 | $this->commandRouteName, 44 | null, 45 | InputOption::VALUE_NONE, 46 | $this->commandRouteDescription 47 | ); 48 | } 49 | 50 | protected function execute(InputInterface $input, OutputInterface $output) 51 | { 52 | $name = $input->getArgument('name'); 53 | $model = $input->getOption('model'); 54 | 55 | if (!$name) { 56 | $infoText = "What is the name of the controller?"; 57 | $blueText = "\033[34m" . $infoText . "\033[0m"; 58 | $name = $this->ask($blueText); 59 | if (!$name) { 60 | $infoText = "Controller name is required!"; 61 | $redText = "\033[31m" . $infoText . "\033[0m"; 62 | $output->writeln("{$redText}"); 63 | return; 64 | } 65 | } 66 | 67 | // check if controller already exists 68 | if ($this->alreadyExists($name)) { 69 | $infoText = "Controller {$name} already exists!"; 70 | $yellowText = "\033[33m" . $infoText . "\033[0m"; 71 | $output->writeln("{$yellowText}"); 72 | return; 73 | } 74 | 75 | $infoText = "Controller {$name} created successfully."; 76 | $greenText = "\033[32m" . $infoText . "\033[0m"; 77 | $this->make($name, $model); 78 | $output->writeln("{$greenText}"); 79 | // if use model option run command CreateModelCommand 80 | if ($model) { 81 | $this->runCreateModelCommand($model); 82 | } 83 | 84 | $inputs = $input->getOptions(); 85 | 86 | if ($inputs['route']) { 87 | $this->runCreateRouteCommand($name); 88 | } 89 | } 90 | 91 | public function ask($question) 92 | { 93 | $handle = fopen("php://stdin", "r"); 94 | echo $question . " "; 95 | $line = fgets($handle); 96 | return trim($line); 97 | } 98 | 99 | public function runCreateRouteCommand($name) 100 | { 101 | $command = $this->getApplication()->find('make:route'); 102 | $arguments = [ 103 | 'command' => 'make:route', 104 | '--controller' => $name, 105 | ]; 106 | $input = new \Symfony\Component\Console\Input\ArrayInput($arguments); 107 | $output = new \Symfony\Component\Console\Output\ConsoleOutput(); 108 | $command->run($input, $output); 109 | } 110 | 111 | // method to run CreateModelCommand 112 | public function runCreateModelCommand($model) 113 | { 114 | $command = $this->getApplication()->find('make:model'); 115 | $arguments = [ 116 | 'command' => 'make:model', 117 | 'name' => $model, 118 | ]; 119 | $input = new \Symfony\Component\Console\Input\ArrayInput($arguments); 120 | $output = new \Symfony\Component\Console\Output\ConsoleOutput(); 121 | $command->run($input, $output); 122 | } 123 | 124 | protected function alreadyExists($name) 125 | { 126 | return file_exists($this->getFilePath($this->getFileName($name))); 127 | } 128 | 129 | protected function getStub() 130 | { 131 | if (!file_exists($this->getStubPath())) { 132 | throw new \Exception('Stub not found'); 133 | } 134 | 135 | return $this->getStubPath(); 136 | } 137 | 138 | protected function getStubPath() 139 | { 140 | return __DIR__ . '/Stubs/controller.stub'; 141 | } 142 | 143 | protected function getNamespace() 144 | { 145 | return 'App\Controllers'; 146 | } 147 | 148 | protected function getFileName($name) 149 | { 150 | return $name . '.php'; 151 | } 152 | 153 | protected function getReplacements($name, $model) 154 | { 155 | $replacements = [ 156 | 'DummyNamespace' => $this->getNamespace(), 157 | 'CoreController' => 'App\Core\Controller', 158 | 'DummyClass' => $name, 159 | 'DummyParentClass' => 'Controller', 160 | 'DummyModel' => $model, 161 | ]; 162 | return $replacements; 163 | } 164 | 165 | protected function createSubFolder($name) 166 | { 167 | $folderName = explode('/', $name); 168 | $folderName = $folderName; 169 | $folderPath = ''; 170 | foreach ($folderName as $key => $value) { 171 | if ($key == count($folderName) - 1) { 172 | break; 173 | } 174 | 175 | $folderPath .= $value . '/'; 176 | 177 | if (!file_exists($this->getFilePath($folderPath))) { 178 | mkdir($this->getFilePath($folderPath)); 179 | } 180 | } 181 | } 182 | 183 | protected function make($name, $model) 184 | { 185 | $stub = file_get_contents($this->getStub()); 186 | 187 | $replacements = $this->getReplacements($name, $model); 188 | 189 | $stub = str_replace( 190 | array_keys($replacements), 191 | array_values($replacements), 192 | $stub 193 | ); 194 | 195 | $fileName = $this->getFileName($name); 196 | 197 | $filePath = $this->getFilePath($fileName); 198 | 199 | // create sub folder if it doesn't exist 200 | if (strpos($name, '/') !== false) { 201 | $this->createSubFolder($name); 202 | $folderName = explode('/', $name); 203 | // replace namespace 204 | 205 | $namespace = $this->getNamespace(); 206 | foreach ($folderName as $key => $value) { 207 | if ($key == count($folderName) - 1) { 208 | break; 209 | } 210 | 211 | $namespace .= '\\' . $value; 212 | } 213 | 214 | $stub = str_replace( 215 | $replacements['DummyNamespace'], 216 | $namespace, 217 | $stub 218 | ); 219 | 220 | // replace class name 221 | $className = explode('/', $name); 222 | $stub = str_replace( 223 | $replacements['DummyClass'], 224 | end($className), 225 | $stub 226 | ); 227 | } 228 | 229 | file_put_contents($filePath, $stub); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /App/Core/Commands/CreateDotEnvCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 19 | ->setDescription($this->commandDescription); 20 | } 21 | 22 | protected function execute(InputInterface $input, OutputInterface $output) 23 | { 24 | 25 | // check if .env exist 26 | if ($this->alreadyExists()) { 27 | $output->writeln(".env already exists!"); 28 | return; 29 | } 30 | 31 | $this->make(); 32 | $output->writeln(".env created successfully."); 33 | } 34 | 35 | protected function alreadyExists() 36 | { 37 | return file_exists($this->getFilePath($this->getFileName('.env'))); 38 | } 39 | 40 | protected function getStub() 41 | { 42 | if (!file_exists($this->getStubPath())) { 43 | throw new \Exception('Stub not found'); 44 | } 45 | 46 | return $this->getStubPath(); 47 | } 48 | 49 | protected function getStubPath() 50 | { 51 | return __DIR__ . '/Stubs/env.stub'; 52 | } 53 | 54 | protected function getFileName($name) 55 | { 56 | return $name; 57 | } 58 | 59 | protected function getReplacements() 60 | { 61 | // string random 64 characters different from each other and generate 62 | $accessToken = bin2hex(random_bytes(32)); 63 | $refreshToken = bin2hex(random_bytes(32)); 64 | $replacements = [ 65 | 'DummyAccessToken' => $accessToken, 66 | 'DummyRefreshToken' => $refreshToken, 67 | 'DummyLocalhost' => 'localhost', 68 | 'DummyUsername' => 'root', 69 | 'DummyPassword' => '', 70 | 'DummyDbName' => 'mardira', 71 | ]; 72 | return $replacements; 73 | } 74 | 75 | protected function getFilePath($name) 76 | { 77 | return __DIR__ . '/../../../' . $name; 78 | } 79 | 80 | protected function make() 81 | { 82 | $stub = $this->getStub(); 83 | $replacements = $this->getReplacements(); 84 | $filePath = $this->getFilePath($this->getFileName('.env')); 85 | 86 | $file = file_get_contents($stub); 87 | 88 | foreach ($replacements as $key => $value) { 89 | $file = str_replace("{{ $key }}", $value, $file); 90 | } 91 | 92 | file_put_contents($filePath, $file); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /App/Core/Commands/CreateMiddlewareCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 26 | ->setDescription($this->commandDescription) 27 | ->addArgument( 28 | $this->commandArgumentName, 29 | InputOption::VALUE_REQUIRED, 30 | $this->commandArgumentDescription 31 | ); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $name = $input->getArgument('name'); 37 | 38 | // check if middleware already exists 39 | if ($this->alreadyExists($name)) { 40 | $output->writeln("Middleware already exists!"); 41 | return; 42 | } 43 | 44 | $this->make($name); 45 | $output->writeln("Middleware created successfully."); 46 | } 47 | 48 | protected function alreadyExists($name) 49 | { 50 | return file_exists($this->getFilePath($this->getFileName($name))); 51 | } 52 | 53 | protected function getFileName($name) 54 | { 55 | return $name . '.php'; 56 | } 57 | 58 | protected function getNamespace() 59 | { 60 | return 'App\Middleware'; 61 | } 62 | 63 | protected function getStub() 64 | { 65 | return __DIR__ . '/Stubs/middleware.stub'; 66 | } 67 | 68 | protected function getReplacements($name) 69 | { 70 | $replacements = [ 71 | 'DummyNamespace' => $this->getNamespace(), 72 | 'CoreMiddleware' => 'App\Core\Middleware', 73 | 'DummyClass' => $name, 74 | 'DummyParentClass' => 'Middleware', 75 | ]; 76 | 77 | return $replacements; 78 | } 79 | 80 | protected function make($name) 81 | { 82 | $stub = file_get_contents($this->getStub()); 83 | $replacements = $this->getReplacements($name); 84 | 85 | $stub = str_replace( 86 | array_keys($replacements), 87 | array_values($replacements), 88 | $stub 89 | ); 90 | 91 | $fileName = $this->getFileName($name); 92 | $filePath = $this->getFilePath($fileName); 93 | 94 | 95 | file_put_contents($filePath, $stub); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /App/Core/Commands/CreateMigrationCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 28 | ->setDescription($this->commandDescription) 29 | ->addArgument( 30 | $this->commandArgumentName, 31 | InputArgument::REQUIRED, 32 | $this->commandArgumentDescription 33 | )->addOption( 34 | $this->commandOptionName, 35 | null, 36 | InputOption::VALUE_OPTIONAL, 37 | $this->commandOptionDescription 38 | ); 39 | } 40 | 41 | protected function execute(InputInterface $input, OutputInterface $output): void 42 | { 43 | $name = $input->getArgument('name'); 44 | // if table null 45 | $table = $input->getOption('table') ?? $name; 46 | 47 | $this->make($name, $table); 48 | 49 | $output->writeln("Migration created successfully."); 50 | } 51 | 52 | protected function getStub(): string 53 | { 54 | if (!file_exists($this->getStubPath())) { 55 | throw new \Exception('Stub not found'); 56 | } 57 | 58 | return $this->getStubPath(); 59 | } 60 | 61 | protected function getStubPath(): string 62 | { 63 | return __DIR__ . '/Stubs/migration.stub'; 64 | } 65 | 66 | protected function getDestinationPath(): string 67 | { 68 | return __DIR__ . 'App/Database/Migrations'; 69 | } 70 | 71 | protected function getDestinationFileName(string $name): string 72 | { 73 | return date('Y_m_d_His') . '_' . $name . '.php'; 74 | } 75 | 76 | protected function getReplacements(string $name, string $table): array 77 | { 78 | return [ 79 | 'DummyClass' => $name, 80 | 'DummyNamespace' => $this->getNamespace(), 81 | 'DummyParentClass' => 'Migration', 82 | 'DummyMigrationNamespace' => $this->getMigrationNamespace(), 83 | 'DummyParentClass' => $this->getMigrationClassName(), 84 | 'DummySchemaNameSpace' => $this->getSchemaNamespace(), 85 | 'DummySchemaClassName' => $this->getSchemaClassName(), 86 | 'DummyBlueprintNameSpace' => $this->getBlueprintNamespace(), 87 | 'DummyBlueprintClassName' => $this->getBlueprintClassName(), 88 | 'DummyMigrationCoreNamespace' => $this->getMigrationCoreNamespace(), 89 | 'DummyTable' => $this->getTable($table) 90 | ]; 91 | } 92 | 93 | protected function getTable(string $table) 94 | { 95 | $table = strtolower($table); 96 | $table = explode('_', $table); 97 | $table = end($table); 98 | return $table; 99 | } 100 | 101 | protected function getMigrationNamespace(): string 102 | { 103 | return 'App\Database\Migration'; 104 | } 105 | 106 | protected function getMigrationClassName(): string 107 | { 108 | return 'Migration'; 109 | } 110 | 111 | protected function getMigrationCoreNamespace(): string 112 | { 113 | return 'App\Core\Migration'; 114 | } 115 | 116 | protected function getSchemaNamespace(): string 117 | { 118 | return 'App\Core\Schema'; 119 | } 120 | 121 | protected function getSchemaClassName(): string 122 | { 123 | return 'Schema'; 124 | } 125 | 126 | protected function getBlueprintNamespace(): string 127 | { 128 | return 'App\Core\Blueprint'; 129 | } 130 | 131 | protected function getBlueprintClassName(): string 132 | { 133 | return 'Blueprint'; 134 | } 135 | 136 | protected function getNamespace(): string 137 | { 138 | return 'App\Database\Migrations'; 139 | } 140 | 141 | protected function getClassName(string $name): string 142 | { 143 | return $name; 144 | } 145 | 146 | protected function getFileName(string $name): string 147 | { 148 | return $name; 149 | } 150 | 151 | protected function make(string $name, string $table): void 152 | { 153 | $stub = file_get_contents($this->getStub()); 154 | 155 | $replacements = $this->getReplacements($name, $table); 156 | 157 | $stub = str_replace( 158 | array_keys($replacements), 159 | array_values($replacements), 160 | $stub 161 | ); 162 | 163 | $fileName = $this->getDestinationFileName($name); 164 | 165 | $filePath = $this->getFilePath($fileName); 166 | 167 | file_put_contents($filePath, $stub); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /App/Core/Commands/CreateModelCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 29 | ->setDescription($this->commandDescription) 30 | ->addArgument( 31 | $this->commandArgumentName, 32 | InputArgument::REQUIRED, 33 | $this->commandArgumentDescription 34 | )->addOption( 35 | $this->commandOptionName, 36 | null, 37 | InputOption::VALUE_OPTIONAL, 38 | $this->commandOptionDescription 39 | ); 40 | } 41 | 42 | protected function execute(InputInterface $input, OutputInterface $output): void 43 | { 44 | 45 | $name = $input->getArgument('name'); 46 | $table = $input->getOption('table'); 47 | 48 | // if model exist 49 | 50 | if (file_exists($this->getFilePath($this->getFileName($name)))) { 51 | $infoText = "Model {$name} already exists!"; 52 | $yellowText = "\033[33m" . $infoText . "\033[0m"; 53 | $output->writeln("{$yellowText}"); 54 | return; 55 | } 56 | 57 | $this->make($name, $table); 58 | 59 | // text terminal green color 60 | 61 | $infoText = "Model created successfully."; 62 | $greenText = "\033[32m" . $infoText . "\033[0m"; 63 | 64 | $output->writeln("{$greenText}"); 65 | } 66 | 67 | protected function getStub(): string 68 | { 69 | if (!file_exists($this->getStubPath())) { 70 | throw new \Exception('Stub not found'); 71 | } 72 | 73 | return $this->getStubPath(); 74 | } 75 | 76 | protected function getStubPath(): string 77 | { 78 | return __DIR__ . '/Stubs/model.stub'; 79 | } 80 | 81 | protected function getNamespace(): string 82 | { 83 | return 'App\Models'; 84 | } 85 | 86 | protected function pluralize(string $name): string 87 | { 88 | $inflector = InflectorFactory::create()->build(); 89 | return $inflector->pluralize($name); 90 | } 91 | 92 | protected function getFileName(string $name): string 93 | { 94 | return $name . '.php'; 95 | } 96 | 97 | protected function getReplacements($name, $table): array 98 | { 99 | $replacements = [ 100 | 'DummyNamespace' => $this->getNamespace(), 101 | 'CoreModel' => 'App\Core\Model', 102 | 'DummyClass' => $name, 103 | 'DummyParentClass' => 'Model', 104 | 'DummyTable' => $table ?? strtolower($this->pluralize($name)), 105 | 'DummyPrimaryKey' => 'id', 106 | ]; 107 | return $replacements; 108 | } 109 | 110 | protected function make($name, $model): void 111 | { 112 | $stub = file_get_contents($this->getStub()); 113 | 114 | $replacements = $this->getReplacements($name, $model); 115 | 116 | $stub = str_replace( 117 | array_keys($replacements), 118 | array_values($replacements), 119 | $stub 120 | ); 121 | 122 | $fileName = $this->getFileName($name); 123 | 124 | $filePath = $this->getFilePath($fileName); 125 | 126 | file_put_contents($filePath, $stub); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /App/Core/Commands/CreateRouteCommand.php: -------------------------------------------------------------------------------- 1 | [ 34 | 'name' => 'controller', 35 | 'shortName' => 'c', 36 | 'input' => InputOption::VALUE_OPTIONAL, 37 | 'description' => 'Generate a resource controller for the given model', 38 | ], 39 | 'parameter' => [ 40 | 'name' => 'parameter', 41 | 'shortName' => 'p', 42 | 'input' => InputOption::VALUE_OPTIONAL, 43 | 'description' => 'Parameter of the route', 44 | ], 45 | 'get' => [ 46 | 'name' => 'get', 47 | 'shortName' => null, 48 | 'input' => InputOption::VALUE_NONE, 49 | 'description' => 'Create a new get route', 50 | ], 51 | 'post' => [ 52 | 'name' => 'post', 53 | 'shortName' => null, 54 | 'input' => InputOption::VALUE_NONE, 55 | 'description' => 'Create a new post route', 56 | ], 57 | 'put' => [ 58 | 'name' => 'put', 59 | 'shortName' => null, 60 | 'input' => InputOption::VALUE_NONE, 61 | 'description' => 'Create a new put route', 62 | ], 63 | 'patch' => [ 64 | 'name' => 'patch', 65 | 'shortName' => null, 66 | 'input' => InputOption::VALUE_NONE, 67 | 'description' => 'Create a new patch route', 68 | ], 69 | 'delete' => [ 70 | 'name' => 'delete', 71 | 'shortName' => null, 72 | 'input' => InputOption::VALUE_NONE, 73 | 'description' => 'Create a new delete route', 74 | ], 75 | 'options' => [ 76 | 'name' => 'options', 77 | 'shortName' => null, 78 | 'input' => InputOption::VALUE_NONE, 79 | 'description' => 'Create a new options route', 80 | ], 81 | ]; 82 | 83 | protected function configure() 84 | { 85 | $this->setName($this->commandName) 86 | ->setDescription($this->commandDescription) 87 | ->addArgument( 88 | $this->commandArgumentName, 89 | InputArgument::OPTIONAL, 90 | $this->commandArgumentDescription 91 | ); 92 | foreach ($this->commandOptions as $option) { 93 | $this->addOption( 94 | $option['name'], 95 | $option['shortName'], 96 | $option['input'], 97 | $option['description'] 98 | ); 99 | } 100 | } 101 | 102 | protected function execute(InputInterface $input, OutputInterface $output) 103 | { 104 | $name = $input->getArgument('name'); 105 | $controller = $input->getOption('controller'); 106 | // remove any = from controller name 107 | $controller = str_replace('=', '', $controller); 108 | $parameter = $input->getOption('parameter'); 109 | 110 | // remove any = from parameter name 111 | $parameter = str_replace('=', '', $parameter); 112 | // if method is not set, ask user to set it 113 | $methodName = strpos($name, ':') !== false ? '' : $name; 114 | if (!$name || strpos($name, ':') !== false) { 115 | $infoText = "Method is not set. Please type method name: "; 116 | $yellowText = "\033[33m" . $infoText . "\033[0m"; 117 | $methodName = $this->ask($yellowText); 118 | if ($methodName == '') { 119 | $infoText = "Method is not set, route not created."; 120 | $blueText = "\033[34m" . $infoText . "\033[0m"; 121 | $output->writeln($blueText); 122 | return; 123 | } 124 | } 125 | 126 | $inputs = $input->getOptions(); 127 | 128 | $httpVerbs = ''; 129 | 130 | foreach ($inputs as $key => $value) { 131 | if (in_array($key, $this->methodList)) { 132 | if ($value) { 133 | $httpVerbs = $key; 134 | } 135 | } 136 | } 137 | 138 | $httpVerbs = $httpVerbs ? $httpVerbs : 'get'; 139 | 140 | 141 | // if controller is not set, ask user to set it 142 | if (!$controller) { 143 | $infoText = "Controller is not set. Please type controller name: "; 144 | $yellowText = "\033[33m" . $infoText . "\033[0m"; 145 | $controller = $this->ask($yellowText); 146 | if ($controller == '') { 147 | $infoText = "Controller is not set, route not created."; 148 | $blueText = "\033[34m" . $infoText . "\033[0m"; 149 | $output->writeln($blueText); 150 | return; 151 | } 152 | } 153 | 154 | // if controller does not exist from file, ask if user wants to create it with option y/n 155 | if (!(file_exists('App/Controllers/' . $controller . '.php'))) { 156 | $infoText = "Controller does not exist. Do you want to create it? (y/n)"; 157 | $yellowText = "\033[33m" . $infoText . "\033[0m"; 158 | $ask = $this->ask($yellowText); 159 | if ($ask == 'y' || $ask == 'Y') { 160 | $command = $this->getApplication()->find('make:controller'); 161 | $arguments = [ 162 | 'command' => 'make:controller', 163 | 'name' => $controller, 164 | ]; 165 | $input = new \Symfony\Component\Console\Input\ArrayInput($arguments); 166 | $output = new \Symfony\Component\Console\Output\ConsoleOutput(); 167 | $command->run($input, $output); 168 | } else { 169 | $infoText = "Controller not created."; 170 | $blueText = "\033[34m" . $infoText . "\033[0m"; 171 | $output->writeln($blueText); 172 | return; 173 | } 174 | } 175 | 176 | $this->generateRoute($name, $methodName, $controller, $parameter, $httpVerbs); 177 | 178 | // 179 | $splitRoute = explode('/', $name); 180 | // filter splitRoute only has parameter 181 | if (strpos($name, ':') !== false) { 182 | $splitRoute = array_filter($splitRoute, function ($item) { 183 | return strpos($item, ':') !== false; 184 | }); 185 | // remove : and implode with comma 186 | $parameter = implode(',', str_replace(':', '', $splitRoute)); 187 | } 188 | 189 | // if method does not exist from controller automatically create it 190 | $findMethod = $this->findMethod($controller, $methodName, $parameter); 191 | if (!$findMethod) { 192 | $infoText = "Method {$methodName} from controller {$controller} does not exist!"; 193 | $yellowText = "\033[33m" . $infoText . "\033[0m"; 194 | // if its has parameter, create method with parameter method 195 | if ($parameter) { 196 | $infoText = "Creating method {$methodName} with parameter {$parameter} from controller {$controller}..."; 197 | $blueText = "\033[34m" . $infoText . "\033[0m"; 198 | } else { 199 | $infoText = "Creating method {$methodName} from controller {$controller}..."; 200 | $blueText = "\033[34m" . $infoText . "\033[0m"; 201 | } 202 | $infoText = "Method created successfully."; 203 | $greenText = "\033[32m" . $infoText . "\033[0m"; 204 | $output->writeln($yellowText); 205 | $output->writeln($blueText); 206 | $output->writeln($greenText); 207 | 208 | $this->createMethod($controller, $methodName, $parameter); 209 | } 210 | 211 | $infoText = "Route created successfully."; 212 | $greenText = "\033[32m" . $infoText . "\033[0m"; 213 | $output->writeln($greenText); 214 | } 215 | 216 | protected function createMethod($controller, $name, $parameter = null) 217 | { 218 | $controllerPath = $this->getControllerPath($controller); 219 | $controller = file_get_contents($controllerPath); 220 | $class = strrpos($controller, '}'); 221 | $controller = substr($controller, 0, $class); 222 | // if parameter is not null, create method with parameter 223 | if ($parameter) { 224 | // if paramter is separated by comma, explode it 225 | if (strpos($parameter, ',') !== false) { 226 | $parameter = explode(',', $parameter); 227 | $parameter = array_map(function ($item) { 228 | return '$' . $item; 229 | }, $parameter); 230 | $parameter = implode(', ', $parameter); 231 | } else { 232 | $parameter = '$' . $parameter; 233 | } 234 | $controller .= "\tpublic function {$name}({$parameter})\n\t{\n\t\t\n\t}\n\n}"; 235 | } else { 236 | $controller .= "\tpublic function {$name}()\n\t{\n\t\t\n\t}\n\n}"; 237 | } 238 | file_put_contents($controllerPath, $controller); 239 | } 240 | 241 | protected function findMethod($controller, $name, $parameter = null) 242 | { 243 | $controller = $this->getControllerPath($controller); 244 | $controller = file_get_contents($controller); 245 | $controller = explode('public function', $controller); 246 | 247 | foreach ($controller as $key => $value) { 248 | // if parameter is not null, check if method has parameter 249 | if (strpos($value, $name) !== false) { 250 | return true; 251 | } 252 | } 253 | return false; 254 | } 255 | 256 | protected function ask($question) 257 | { 258 | $handle = fopen("php://stdin", "r"); 259 | echo $question . " "; 260 | $line = fgets($handle); 261 | return trim($line); 262 | } 263 | 264 | 265 | protected function getUseRouteStub() 266 | { 267 | if (!file_exists($this->getUseRouteStubPath())) { 268 | throw new \Exception('Stub not found'); 269 | } 270 | 271 | return $this->getUseRouteStubPath(); 272 | } 273 | 274 | protected function getUseRouteStubPath() 275 | { 276 | return __DIR__ . '/Stubs/routes/use-route.stub'; 277 | } 278 | 279 | protected function getRouteStub() 280 | { 281 | if (!file_exists($this->getRouteStubPath())) { 282 | throw new \Exception('Stub not found'); 283 | } 284 | 285 | return $this->getRouteStubPath(); 286 | } 287 | 288 | protected function getGroupStubMethodPath() 289 | { 290 | return __DIR__ . '/Stubs/routes/route-group-method.stub'; 291 | } 292 | 293 | protected function getGroupStubMethod() 294 | { 295 | if (!file_exists($this->getGroupStubMethodPath())) { 296 | throw new \Exception('Stub not found'); 297 | } 298 | 299 | return $this->getGroupStubMethodPath(); 300 | } 301 | 302 | protected function getRouteStubPath() 303 | { 304 | return __DIR__ . '/Stubs/routes/route.stub'; 305 | } 306 | 307 | protected function getRoutePath() 308 | { 309 | return 'App/Routes/Api.php'; 310 | } 311 | 312 | protected function getReplacements($name, $methodName, $controller, $parameter = null, $httpVerbs) 313 | { 314 | // if method is index, create route without method 315 | $actionName = $name === NULL ? $methodName : $name; 316 | $action = $actionName == 'index' ? '' : '/' . $this->getAction($actionName); 317 | // change :parameter to {parameter} 318 | $splitRoute = explode('/', $action); 319 | // remove only :parameter and add {} 320 | $splitRoute = array_map(function ($item) { 321 | return strpos($item, ':') !== false ? '{' . str_replace(':', '', $item) . '}' : $item; 322 | }, $splitRoute); 323 | $action = implode('/', $splitRoute); 324 | 325 | $prefix = '/'; 326 | if ($parameter) { 327 | // if parameter is separated by comma, explode it 328 | if (strpos($parameter, ',') !== false) { 329 | $parameter = explode(',', $parameter); 330 | $parameter = array_map(function ($item) { 331 | return '{' . $item . '}'; 332 | }, $parameter); 333 | $parameter = implode('/', $parameter); 334 | } else { 335 | $parameter = '{' . $parameter . '}'; 336 | } 337 | if (strpos($name, ':') !== false) { 338 | $prefix = ''; 339 | } 340 | 341 | $dummyRoute = $prefix . $this->splitNameController($controller) . $action . '/' . $parameter; 342 | } else { 343 | $dummyRoute = $prefix . $this->splitNameController($controller) . $action; 344 | } 345 | 346 | return [ 347 | 'DummyRoute' => $dummyRoute, 348 | 'DummyAction' => $methodName, 349 | 'DummyController' => $this->getControllerName($controller), 350 | 'DummyNameController' => $this->splitSlashController($controller), 351 | 'DummyHttpVerbs' => $httpVerbs, 352 | ]; 353 | } 354 | 355 | protected function splitSlashController($controller) 356 | { 357 | $controller = $this->getControllerName($controller); 358 | // remove controller from last string 359 | $controller = str_replace('\\', '/', $controller); 360 | // split if there is / 361 | if (strpos($controller, '/') !== false) { 362 | $controller = explode('/', $controller); 363 | $controller = end($controller); 364 | } 365 | 366 | return $controller; 367 | } 368 | 369 | protected function splitNameController($controller) 370 | { 371 | $controller = $this->getControllerName($controller); 372 | // remove controller from last string 373 | $controller = substr($controller, 0, -10); 374 | // convert to lowercase 375 | $controller = strtolower($controller); 376 | // replace \ with / 377 | $controller = str_replace('\\', '/', $controller); 378 | // convert to plural 379 | $inflector = InflectorFactory::create()->build(); 380 | $controller = $inflector->pluralize($controller); 381 | return $controller; 382 | } 383 | 384 | protected function getControllerPath($controller) 385 | { 386 | return 'App/Controllers/' . $controller . '.php'; 387 | } 388 | 389 | protected function getAction($name) 390 | { 391 | return strtolower($name); 392 | } 393 | 394 | protected function getControllerName($name) 395 | { 396 | $name = str_replace('/', '\\', $name); 397 | return ucfirst($name); 398 | } 399 | 400 | protected function generateRoute($name, $methodName, $controller, $parameter = null, $httpVerbs) 401 | { 402 | $replacements = $this->getReplacements($name, $methodName, $controller, $parameter, $httpVerbs); 403 | $routeStub = $this->getRouteStub(); 404 | $routePath = $this->getRoutePath(); 405 | 406 | $useRouteStub = $this->getUseRouteStub(); 407 | $useRouteContent = file_get_contents($useRouteStub); 408 | 409 | $useRouteContent = str_replace( 410 | array_keys($replacements), 411 | array_values($replacements), 412 | $useRouteContent 413 | ); 414 | 415 | $routeStubContent = file_get_contents($routeStub); 416 | $routeStubContent = str_replace( 417 | array_keys($replacements), 418 | array_values($replacements), 419 | $routeStubContent 420 | ); 421 | 422 | 423 | // get file content routes and check last use controller 424 | $routeContent = file_get_contents($routePath); 425 | $useControllers = strrpos($routeContent, 'use App\Controllers\\'); 426 | 427 | // get last use controller 428 | $lastUseController = substr($routeContent, $useControllers); 429 | $lastUseController = substr($lastUseController, 0, strpos($lastUseController, ';') + 1); 430 | 431 | // check if use controller already exists 432 | if (strpos($routeContent, $useRouteContent) === false) { 433 | $routeContent = str_replace($lastUseController, $lastUseController . PHP_EOL . $useRouteContent, $routeContent); 434 | } 435 | file_put_contents($routePath, $routeContent); 436 | 437 | // get last Router::controller 438 | $routeContent = file_get_contents($routePath); 439 | $lastRoute = strrpos($routeContent, 'Router::controller'); 440 | if (!$lastRoute) { 441 | $lastRoute = substr($lastUseController, 0, strpos($lastUseController, ';') + 1); 442 | } 443 | $controllerName = $this->splitSlashController($controller); 444 | 445 | $checkRoute = strrpos($routeContent, "Router::controller({$controllerName}::class)"); 446 | if (!$checkRoute) { 447 | $useControllers = strrpos($routeContent, 'use App\Controllers\\'); 448 | 449 | // get last use controller 450 | $lastUseController = substr($routeContent, $useControllers); 451 | $lastUseController = substr($lastUseController, 0, strpos($lastUseController, ';') + 1); 452 | 453 | $routeContent = str_replace($lastUseController, $lastUseController . PHP_EOL . PHP_EOL . $routeStubContent, $routeContent); 454 | file_put_contents($routePath, $routeContent); 455 | } else { 456 | $groupStubMethod = $this->getGroupStubMethod(); 457 | $groupStubMethodContent = file_get_contents($groupStubMethod); 458 | $groupStubMethodContent = str_replace( 459 | array_keys($replacements), 460 | array_values($replacements), 461 | $groupStubMethodContent 462 | ); 463 | $checkRoute = substr($routeContent, $checkRoute); 464 | $checkRoute = substr($checkRoute, 0, strpos($checkRoute, '});') + 3); 465 | 466 | // check action in Router::controller 467 | $checkMethod = strrpos($checkRoute, $this->getAction($methodName)); 468 | if (!$checkMethod) { 469 | $lastMethod = strrchr($checkRoute, 'Router::'); 470 | // check if result string end with ->group(function () { 471 | $checkGroupMethod = substr($checkRoute, -48, strpos($lastMethod, "group(function () {")); 472 | // if strlength is below to 43 then add string { 473 | if (strlen($checkGroupMethod) < 43) { 474 | $checkGroupMethod = substr($checkRoute, -45, strpos($lastMethod, "group(function () {")); 475 | } 476 | 477 | // check if method in group controller already exists 478 | if ($checkGroupMethod == "") { 479 | $lastMethod = substr($lastMethod, 0, strpos($lastMethod, ');') + 2); 480 | if (strpos($lastMethod, str_replace(' ', "\t", $groupStubMethodContent)) === false) { 481 | $routeContent = str_replace($lastMethod, $lastMethod . PHP_EOL . "\t" . $groupStubMethodContent, $routeContent); 482 | file_put_contents($routePath, $routeContent); 483 | } 484 | } else { 485 | // find stringlast 486 | 487 | $lastMethod = substr($checkGroupMethod, 0, strpos($checkGroupMethod, '{') + 1); 488 | 489 | 490 | $routeContent = str_replace($lastMethod, $lastMethod . PHP_EOL . "\t" . $groupStubMethodContent, $routeContent); 491 | 492 | file_put_contents($routePath, $routeContent); 493 | } 494 | } 495 | } 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /App/Core/Commands/CreateSeederCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 24 | ->setDescription($this->commandDescription) 25 | ->addArgument( 26 | $this->commandArgumentName, 27 | InputArgument::REQUIRED, 28 | $this->commandArgumentDescription 29 | ); 30 | } 31 | 32 | protected function execute(InputInterface $input, OutputInterface $output): void 33 | { 34 | $name = $input->getArgument('name'); 35 | $this->make($name); 36 | 37 | $output->writeln("Seeder created successfully."); 38 | } 39 | 40 | protected function getStub(): string 41 | { 42 | if (!file_exists($this->getStubPath())) { 43 | throw new \Exception('Stub not found'); 44 | } 45 | 46 | return $this->getStubPath(); 47 | } 48 | 49 | protected function getStubPath(): string 50 | { 51 | return __DIR__ . '/Stubs/seeder.stub'; 52 | } 53 | 54 | protected function getDestinationPath(): string 55 | { 56 | return __DIR__ . 'App/Database/Migrations'; 57 | } 58 | 59 | protected function getDestinationFileName(string $name): string 60 | { 61 | return $this->getClassName($name) . '.php'; 62 | } 63 | 64 | protected function getClassName(string $name): string 65 | { 66 | return ucfirst($name); 67 | } 68 | 69 | 70 | protected function getNamespace(): string 71 | { 72 | return 'App\Database\Seeders'; 73 | } 74 | 75 | protected function getReplacements($name): array 76 | { 77 | return [ 78 | 'DummyClassName' => $this->getClassName($name), 79 | 'DummyParentClass' => $this->getParentClassName(), 80 | 'DummyNameSpace' => $this->getNamespace(), 81 | 'DummyCoreSeederNamespace' => $this->getCoreSeederNameSpace(), 82 | ]; 83 | } 84 | 85 | protected function getParentClassName(): string 86 | { 87 | return 'Seeder'; 88 | } 89 | 90 | protected function getCoreSeederNameSpace(): string 91 | { 92 | return 'App\Core\Seeder'; 93 | } 94 | 95 | protected function getFileName(string $name): string 96 | { 97 | return $name; 98 | } 99 | 100 | protected function make(string $name): void 101 | { 102 | $stub = file_get_contents($this->getStub()); 103 | 104 | $replacements = $this->getReplacements($name); 105 | 106 | $stub = str_replace( 107 | array_keys($replacements), 108 | array_values($replacements), 109 | $stub 110 | ); 111 | 112 | $fileName = $this->getDestinationFileName($name); 113 | $filePath = $this->getFilePath($fileName); 114 | 115 | file_put_contents($filePath, $stub); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /App/Core/Commands/CreateUpdateCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 24 | ->setDescription($this->commandDescription) 25 | ->addOption( 26 | $this->commandOptionName, 27 | null, 28 | InputOption::VALUE_OPTIONAL, 29 | $this->commandOptionDescription 30 | ); 31 | } 32 | 33 | protected function execute(InputInterface $input, OutputInterface $output): void 34 | { 35 | $version = $input->getOption('version'); 36 | $stub = $this->getStub(); 37 | $stub = str_replace('{{version}}', $version, $stub); 38 | $file = file_get_contents('https://packagist.org/packages/mardira/mardira-framework'); 39 | $version = $this->getVersion($file); 40 | $this->removeLockFile(); 41 | $this->removeJsonFile(); 42 | $this->initComposer(); 43 | $this->requireCore($version); 44 | 45 | // get files from vendor folder app core 46 | foreach (glob("vendor/mardira/mardira-framework/App/Core/*") as $file) { 47 | 48 | // get command folder with if statement 49 | if (basename($file) == 'Commands') { 50 | // get files from command folder 51 | foreach (glob("vendor/mardira/mardira-framework/App/Core/Commands/*") as $file) { 52 | // if file changed from vendor folder and from App/Core folder show message 53 | if (file_exists($file) && file_exists("App/Core/Commands/" . basename($file))) { 54 | // check difference between files from vendor folder and App/Core folder with --dif 55 | $diff = shell_exec("diff {$file} App/Core/Commands/" . basename($file)); 56 | if ($diff) { 57 | //split file name from path mardira-framework 58 | $file = explode('mardira-framework', $file)[1]; 59 | // text terminal color yellow and name file 60 | $textYellow = "\033[33m" . $file . "\033[0m"; 61 | 62 | $output->writeln("File {$textYellow} has been changed."); 63 | } 64 | } 65 | 66 | // if file added from vendor folder and not exist in App/Core folder show message 67 | else if (file_exists($file) && !file_exists("App/Core/Commands/" . basename($file))) { 68 | //split file name from path mardira-framework 69 | $file = explode('mardira-framework', $file)[1]; 70 | // text terminal color green and name file 71 | $textGreen = "\033[32m" . $file . "\033[0m"; 72 | 73 | $output->writeln("File {$textGreen} has been added."); 74 | } 75 | 76 | // if file deleted from vendor folder and exist in App/Core folder show message 77 | else if (!file_exists($file) && file_exists("App/Core/Commands/" . basename($file))) { 78 | $file = implode('/', ['/App/Core/Commands', basename($file)]); 79 | // text terminal color red and name file 80 | $textRed = "\033[31m" . $file . "\033[0m"; 81 | 82 | $output->writeln("File {$textRed} has been deleted."); 83 | } 84 | } 85 | } 86 | 87 | // if file changed from vendor folder and from App/Core folder show message 88 | if (file_exists($file) && file_exists("App/Core/" . basename($file))) { 89 | // check difference between files from vendor folder and App/Core folder with --dif 90 | $diff = shell_exec("diff {$file} App/Core/" . basename($file)); 91 | if ($diff) { 92 | //split file name from path mardira-framework 93 | $file = explode('mardira-framework', $file)[1]; 94 | // text terminal color yellow and name file 95 | $textYellow = "\033[33m" . $file . "\033[0m"; 96 | 97 | $output->writeln("File {$textYellow} has been changed."); 98 | } 99 | } 100 | 101 | // if file added from vendor folder and not exist in App/Core folder show message 102 | else if (file_exists($file) && !file_exists("App/Core/" . basename($file))) { 103 | //split file name from path mardira-framework 104 | $file = explode('mardira-framework', $file)[1]; 105 | // text terminal color green and name file 106 | $textGreen = "\033[32m" . $file . "\033[0m"; 107 | 108 | $output->writeln("File {$textGreen} has been added."); 109 | } 110 | 111 | // if file deleted from vendor folder and exist in App/Core folder show message 112 | else if (!file_exists($file) && file_exists("App/Core/" . basename($file))) { 113 | 114 | //implode basename($file) with path /App/Core/ 115 | $file = implode('/', ['/App/Core', basename($file)]); 116 | $textRed = "\033[31m" . $file . "\033[0m"; 117 | 118 | $output->writeln("File {$textRed} has been deleted."); 119 | } 120 | } 121 | $this->removeCoreFolder(); 122 | $this->moveCoreFolder(); 123 | $this->moveJsonFile(); 124 | $this->removeVendorFolder(); 125 | $this->composerUpdate(); 126 | 127 | $output->writeln("Application updated successfully."); 128 | } 129 | 130 | protected function getVersion($file): string 131 | { 132 | $version = ''; 133 | $pattern = '/(.*)<\/span>/'; 134 | preg_match($pattern, $file, $matches); 135 | if (isset($matches[1])) { 136 | $version = $matches[1]; 137 | } 138 | return $version; 139 | } 140 | 141 | 142 | protected function removeLockFile(): void 143 | { 144 | $command = "rm -rf composer.lock"; 145 | exec($command); 146 | } 147 | 148 | protected function removeJsonFile(): void 149 | { 150 | $command = "rm -rf composer.json"; 151 | exec($command); 152 | } 153 | 154 | protected function initComposer(): void 155 | { 156 | $command = "composer init --no-interaction"; 157 | exec($command); 158 | } 159 | 160 | protected function requireCore($version): void 161 | { 162 | $version = explode('v', $version)[1]; 163 | $command = "composer require mardira/mardira-framework:{$version}"; 164 | exec($command); 165 | } 166 | 167 | protected function removeCoreFolder(): void 168 | { 169 | $command = "rm -rf App/Core"; 170 | exec($command); 171 | } 172 | 173 | protected function moveCoreFolder(): void 174 | { 175 | $command = "mv vendor/mardira/mardira-framework/App/Core App"; 176 | exec($command); 177 | } 178 | 179 | protected function moveJsonFile(): void 180 | { 181 | $command = "mv vendor/mardira/mardira-framework/composer.json ."; 182 | exec($command); 183 | } 184 | 185 | protected function removeVendorFolder(): void 186 | { 187 | $command = "rm -rf vendor"; 188 | exec($command); 189 | } 190 | 191 | protected function composerUpdate(): void 192 | { 193 | $command = "composer update"; 194 | exec($command); 195 | } 196 | 197 | protected function getStub(): string 198 | { 199 | return file_get_contents(__DIR__ . '/stubs/update.stub'); 200 | } 201 | 202 | protected function getFileName(): string 203 | { 204 | return 'update'; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /App/Core/Commands/RunGlobalSeederCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 21 | ->setDescription($this->commandDescription); 22 | } 23 | 24 | // execute seeding database 25 | protected function execute(InputInterface $input, OutputInterface $output): void 26 | { 27 | // get global seeder 28 | $globalSeeder = $this->getGlobalSeeder(); 29 | // run seeding 30 | $globalSeeder->run(); 31 | 32 | // count seeder called 33 | 34 | // looping seeder called 35 | foreach ($globalSeeder->getSeederCalled() as $seeder) { 36 | // write info file migration generate succesfully 37 | $time = round(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2); 38 | 39 | // count dot for info migration 40 | $dot = 80 - strlen($seeder); 41 | 42 | // text terminal color green and DONE 43 | $green = "\033[32m" . 'DONE' . "\033[0m"; 44 | 45 | // text terminal color blue and name seeder 46 | $seeder = "\033[34m" . $seeder . "\033[0m"; 47 | 48 | // text terminal colour yellow and RUNNING 49 | $yellow = "\033[33m" . 'RUNNING' . "\033[0m"; 50 | $output->writeln("{$seeder} " . str_repeat('.', $dot) . " {$yellow}"); 51 | $output->writeln("{$seeder} " . str_repeat('.', $dot) . " {$time} ms {$green}"); 52 | } 53 | 54 | // write info file migration generate succesfully 55 | $output->writeln("Seeding run successfully."); 56 | } 57 | 58 | 59 | 60 | // get only global seeder 61 | protected function getGlobalSeeder() 62 | { 63 | // get Only GlobalSeeder 64 | $globalSeeder = $this->getSeederPath() . '/GlobalSeeder.php'; 65 | // get object namespace 66 | $globalSeeder = new \App\Database\Seeders\GlobalSeeder(); 67 | return $globalSeeder; 68 | } 69 | 70 | // get file name seeder 71 | protected function getFileSeederName($seeder): string 72 | { 73 | $seederName = explode('\\', get_class($seeder)); 74 | return end($seederName); 75 | } 76 | 77 | // get path seeder 78 | protected function getSeederPath(): string 79 | { 80 | return __DIR__ . '/../../Database/Seeders'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /App/Core/Commands/RunMigrateCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 22 | ->setDescription($this->commandDescription); 23 | } 24 | 25 | protected function execute(InputInterface $input, OutputInterface $output): void 26 | { 27 | // if database not exist show message and command click to create database 28 | $database = $this->getDatabaseName(); 29 | if (!$this->isDatabaseExists()) { 30 | $output->writeln("Database {$database} not exists."); 31 | $output->writeln("Creating database {$database}..."); 32 | $this->runCommand('migrate:database'); 33 | } 34 | 35 | $migrations = $this->getMigrations(); 36 | foreach ($migrations as $migration) { 37 | $this->runMigration($migration); 38 | // get file name migration 39 | $migrationName = $this->getFileMigrationName($migration); 40 | 41 | // get info time interval each migration run ms 42 | $time = round(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2); 43 | 44 | // count dot for info migration 45 | $dot = 80 - strlen($migrationName); 46 | 47 | // text terminal color green and DONE 48 | $green = "\033[32m" . 'DONE' . "\033[0m"; 49 | 50 | // text terminal color blue and name file migration 51 | $textBlueMigrationFile = "\033[34m" . $migrationName . "\033[0m"; 52 | 53 | $output->writeln("{$textBlueMigrationFile} " . str_repeat('.', $dot) . " {$time} ms {$green}"); 54 | } 55 | 56 | // write info file migration generate succesfully 57 | $output->writeln("Migrations run successfully."); 58 | } 59 | 60 | protected function runCommand($command): void 61 | { 62 | 63 | $command = $this->getApplication()->find($command); 64 | 65 | $arguments = [ 66 | 'command' => $command, 67 | ]; 68 | 69 | $input = new \Symfony\Component\Console\Input\ArrayInput($arguments); 70 | $output = new \Symfony\Component\Console\Output\ConsoleOutput(); 71 | $command->run($input, $output); 72 | } 73 | 74 | protected function getMigrations(): array 75 | { 76 | $migrations = []; 77 | 78 | foreach (glob($this->getMigrationsPath() . '/*.php') as $file) { 79 | $migrations[] = require_once $file; 80 | } 81 | 82 | return $migrations; 83 | } 84 | 85 | 86 | protected function getFileMigrationName($migrationName): string 87 | { 88 | $migrationName = (new \ReflectionClass($migrationName))->getFileName(); 89 | $migrationName = preg_split('/[\/\\\\]/', $migrationName); 90 | $migrationName = end($migrationName); 91 | return $migrationName; 92 | } 93 | 94 | protected function getMigrationsPath(): string 95 | { 96 | return __DIR__ . '/../../Database/Migrations'; 97 | } 98 | 99 | protected function runMigration($migration): void 100 | { 101 | $migration->up(); 102 | } 103 | 104 | protected function isDatabaseExists(): bool 105 | { 106 | $databaseName = $this->getDatabaseName(); 107 | 108 | $statement = $this->getConnection()->prepare("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = :databaseName"); 109 | $statement->bindParam(':databaseName', $databaseName); 110 | $statement->execute(); 111 | 112 | return $statement->rowCount() > 0; 113 | } 114 | 115 | protected function getDatabaseName(): string 116 | { 117 | return DotEnvKey::get('DB_NAME'); 118 | } 119 | 120 | // get connection pdo 121 | protected function getConnection() 122 | { 123 | // pdo run get connect server only unsername and password 124 | $pdo = new \PDO( 125 | 'mysql:host=' . DotEnvKey::get('DB_HOST'), 126 | DotEnvKey::get('DB_USER'), 127 | DotEnvKey::get('DB_PASS') 128 | ); 129 | 130 | $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 131 | 132 | return $pdo; 133 | } 134 | 135 | protected function getStub(): string 136 | { 137 | if (!file_exists($this->getStubPath())) { 138 | throw new \Exception('Stub not found'); 139 | } 140 | 141 | return $this->getStubPath(); 142 | } 143 | 144 | protected function getStubPath(): string 145 | { 146 | return __DIR__ . '/Stubs/migration.stub'; 147 | } 148 | 149 | protected function getMigrationName(string $name): string 150 | { 151 | return date('Y_m_d_His') . '_' . $name . '.php'; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /App/Core/Commands/RunMigrateDatabaseCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 22 | ->setDescription($this->commandDescription); 23 | } 24 | 25 | protected function execute(InputInterface $input, OutputInterface $output): void 26 | { 27 | $connection = $this->getConnection(); 28 | 29 | $sql = "CREATE DATABASE IF NOT EXISTS " . DotEnvKey::get('DB_NAME'); 30 | 31 | $connection->exec($sql); 32 | 33 | $output->writeln("Database created successfully."); 34 | } 35 | 36 | // get connection pdo 37 | protected function getConnection() 38 | { 39 | // pdo run get connect server only unsername and password 40 | $pdo = new \PDO( 41 | 'mysql:host=' . DotEnvKey::get('DB_HOST'), 42 | DotEnvKey::get('DB_USER'), 43 | DotEnvKey::get('DB_PASS') 44 | ); 45 | 46 | $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 47 | 48 | return $pdo; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /App/Core/Commands/RunMigrateRefreshCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 26 | ->setDescription($this->commandDescription) 27 | ->addOption( 28 | $this->commandOptionName, 29 | null, 30 | InputOption::VALUE_NONE, 31 | $this->commandOptionDescription 32 | ); 33 | } 34 | 35 | protected function execute(InputInterface $input, OutputInterface $output): void 36 | { 37 | $database = $this->getDatabaseName(); 38 | if (!$this->isDatabaseExists()) { 39 | $output->writeln("Database {$database} not exists."); 40 | $output->writeln("Creating database {$database}..."); 41 | $this->runCommand('migrate:database'); 42 | } 43 | 44 | $migrations = $this->getMigrations(); 45 | 46 | foreach ($migrations as $migration) { 47 | $this->runMigration($migration); 48 | // get file name migration 49 | $migrationName = $this->getFileMigrationName($migration); 50 | 51 | // get info time interval each migration run ms 52 | $time = round(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2); 53 | 54 | // count dot for info migration 55 | $dot = 80 - strlen($migrationName); 56 | 57 | // text terminal color green and DONE 58 | $green = "\033[32m" . 'DONE' . "\033[0m"; 59 | 60 | // text terminal color blue and name file migration 61 | $textBlueMigrationFile = "\033[34m" . $migrationName . "\033[0m"; 62 | 63 | $output->writeln("{$textBlueMigrationFile} " . str_repeat('.', $dot) . " {$time} ms {$green}"); 64 | } 65 | 66 | // write info file migration generate succesfully 67 | $output->writeln("Migrations run successfully."); 68 | 69 | $inputs = $input->getOptions(); 70 | 71 | if ($inputs['seed']) { 72 | $runSeeder = new RunSeederCommand(); 73 | $runSeeder->runGlobalSeeder($output); 74 | } 75 | } 76 | 77 | protected function runCommand($command): void 78 | { 79 | 80 | $command = $this->getApplication()->find($command); 81 | 82 | $arguments = [ 83 | 'command' => $command, 84 | ]; 85 | 86 | $input = new \Symfony\Component\Console\Input\ArrayInput($arguments); 87 | $output = new \Symfony\Component\Console\Output\ConsoleOutput(); 88 | $command->run($input, $output); 89 | } 90 | 91 | protected function isDatabaseExists(): bool 92 | { 93 | $databaseName = $this->getDatabaseName(); 94 | 95 | $statement = $this->getConnection()->prepare("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = :databaseName"); 96 | $statement->bindParam(':databaseName', $databaseName); 97 | $statement->execute(); 98 | 99 | return $statement->rowCount() > 0; 100 | } 101 | 102 | protected function getDatabaseName(): string 103 | { 104 | return DotEnvKey::get('DB_NAME'); 105 | } 106 | 107 | // get connection pdo 108 | protected function getConnection() 109 | { 110 | // pdo run get connect server only unsername and password 111 | $pdo = new \PDO( 112 | 'mysql:host=' . DotEnvKey::get('DB_HOST'), 113 | DotEnvKey::get('DB_USER'), 114 | DotEnvKey::get('DB_PASS') 115 | ); 116 | 117 | $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 118 | 119 | return $pdo; 120 | } 121 | 122 | protected function getMigrations(): array 123 | { 124 | $migrations = []; 125 | 126 | foreach (glob($this->getMigrationsPath() . '/*.php') as $file) { 127 | $migrations[] = require_once $file; 128 | } 129 | 130 | return $migrations; 131 | } 132 | 133 | protected function getFileMigrationName($migrationName): string 134 | { 135 | $migrationName = (new \ReflectionClass($migrationName))->getFileName(); 136 | $migrationName = preg_split('/[\/\\\\]/', $migrationName); 137 | $migrationName = end($migrationName); 138 | return $migrationName; 139 | } 140 | 141 | protected function getMigrationsPath(): string 142 | { 143 | return __DIR__ . '/../../Database/Migrations'; 144 | } 145 | 146 | protected function runMigration($migration): void 147 | { 148 | $migration->down(); 149 | $migration->up(); 150 | } 151 | 152 | protected function getStub(): string 153 | { 154 | if (!file_exists($this->getStubPath())) { 155 | throw new \Exception('Stub not found'); 156 | } 157 | 158 | return file_get_contents($this->getStubPath()); 159 | } 160 | 161 | protected function getStubPath(): string 162 | { 163 | return __DIR__ . '/stubs/migration.stub'; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /App/Core/Commands/RunSeederCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 25 | ->setDescription($this->commandDescription) 26 | ->addOption( 27 | $this->commandOptionName, 28 | null, 29 | InputOption::VALUE_OPTIONAL, 30 | $this->commandOptionDescription 31 | ); 32 | } 33 | 34 | // execute seeding database 35 | protected function execute(InputInterface $input, OutputInterface $output): void 36 | { 37 | // get class name seeder 38 | $class = $input->getOption('class'); 39 | 40 | // check class name seeder is null 41 | if (is_null($class)) { 42 | // run global seeder 43 | $this->runGlobalSeeder($output); 44 | } else { 45 | // run seeder 46 | $this->runSeeder($class, $output); 47 | } 48 | } 49 | 50 | 51 | // run GlobalSeeder 52 | public function runGlobalSeeder(OutputInterface $output): void 53 | { 54 | // get global seeder 55 | $globalSeeder = $this->getGlobalSeeder(); 56 | // run global seeder 57 | $globalSeeder->run(); 58 | // count seeder called 59 | $count = count($globalSeeder->getSeederCalled()); 60 | 61 | // looping seeder called 62 | foreach ($globalSeeder->getSeederCalled() as $seeder) { 63 | // write info file migration generate succesfully 64 | $time = round(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2); 65 | 66 | // count dot for info migration 67 | $dot = 80 - strlen($seeder); 68 | 69 | // text terminal color green and DONE 70 | $green = "\033[32m" . 'DONE' . "\033[0m"; 71 | 72 | // text terminal color blue and name seeder 73 | $seeder = "\033[34m" . $seeder . "\033[0m"; 74 | 75 | // text terminal colour yellow and RUNNING 76 | $yellow = "\033[33m" . 'RUNNING' . "\033[0m"; 77 | $output->writeln("{$seeder} " . str_repeat('.', $dot) . " {$yellow}"); 78 | $output->writeln("{$seeder} " . str_repeat('.', $dot) . " {$time} ms {$green}"); 79 | } 80 | 81 | // write info seeder generate succesfully 82 | $output->writeln("Seeded: {$count} seeders"); 83 | } 84 | // run seeder 85 | 86 | protected function runSeeder(string $class, OutputInterface $output): void 87 | { 88 | // get seeder path 89 | $seederPath = $this->getSeederPath(); 90 | // get seeder file 91 | $seederFile = $seederPath . '/' . $class . '.php'; 92 | // get namespace seeder 93 | $class = $this->getNamespaceSeeder($class); 94 | 95 | // check seeder file is exists 96 | if (!file_exists($seederFile)) { 97 | // write info file migration generate succesfully 98 | $output->writeln("Seeder {$class} not found."); 99 | exit; 100 | } 101 | $time = round(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2); 102 | 103 | // count dot for info migration 104 | $dot = 80 - strlen($class); 105 | 106 | // text terminal color green and DONE 107 | $green = "\033[32m" . 'DONE' . "\033[0m"; 108 | 109 | // text terminal color blue and name seeder 110 | 111 | $class = "\033[34m" . $class . "\033[0m"; 112 | 113 | // text terminal colour yellow and RUNNING 114 | 115 | $yellow = "\033[33m" . 'RUNNING' . "\033[0m"; 116 | 117 | $output->writeln("{$class} " . str_repeat('.', $dot) . " {$yellow}"); 118 | 119 | $output->writeln("{$class} " . str_repeat('.', $dot) . " {$time} ms {$green}"); 120 | 121 | // write info seeder generate succesfully 122 | $output->writeln("Seeded: 1 seeder"); 123 | } 124 | 125 | // get only global seeder 126 | protected function getGlobalSeeder() 127 | { 128 | // get Only GlobalSeeder 129 | $globalSeeder = $this->getSeederPath() . '/GlobalSeeder.php'; 130 | // get object namespace 131 | $globalSeeder = new \App\Database\Seeders\GlobalSeeder(); 132 | return $globalSeeder; 133 | } 134 | 135 | // get file name seeder 136 | protected function getFileSeederName($seeder): string 137 | { 138 | $seederName = explode('\\', get_class($seeder)); 139 | return end($seederName); 140 | } 141 | 142 | // get namespace seeder 143 | protected function getNamespaceSeeder($class): string 144 | { 145 | return 'App\\Database\\Seeders\\' . $class; 146 | } 147 | 148 | // get path seeder 149 | protected function getSeederPath(): string 150 | { 151 | return __DIR__ . '/../../Database/Seeders'; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /App/Core/Commands/ServeCommand.php: -------------------------------------------------------------------------------- 1 | setName($this->commandName) 28 | ->setDescription($this->commandDescription) 29 | ->addArgument( 30 | $this->commandArgumentName, 31 | InputArgument::OPTIONAL, 32 | $this->commandArgumentDescription 33 | )->addOption( 34 | $this->commandOptionName, 35 | null, 36 | InputOption::VALUE_OPTIONAL, 37 | $this->commandOptionDescription 38 | ); 39 | } 40 | 41 | protected function execute(InputInterface $input, OutputInterface $output): void 42 | { 43 | $host = $input->getArgument('host'); 44 | $port = $input->getOption('port'); 45 | // set default host if empty 46 | if (empty($host)) { 47 | $host = '127.0.0.1'; 48 | } 49 | 50 | // set default port if empty 51 | if (empty($port)) { 52 | $port = 8000; 53 | } 54 | $this->serve($host, $port); 55 | 56 | $output->writeln("Server started successfully."); 57 | } 58 | 59 | protected function getStub(): string 60 | { 61 | if (!file_exists($this->getStubPath())) { 62 | throw new \Exception('Stub not found'); 63 | } 64 | 65 | return $this->getStubPath(); 66 | } 67 | 68 | protected function getStubPath(): string 69 | { 70 | return __DIR__ . '/Stubs/serve.stub'; 71 | } 72 | 73 | protected function getTemplate(): string 74 | { 75 | if (!file_exists($this->getTemplatePath())) { 76 | throw new \Exception('Template not found'); 77 | } 78 | 79 | return $this->getTemplatePath(); 80 | } 81 | 82 | protected function getTemplatePath(): string 83 | { 84 | return __DIR__ . '/../../Templates/serve.template'; 85 | } 86 | 87 | protected function getConfiguration(): string 88 | { 89 | if (!file_exists($this->getConfigurationPath())) { 90 | throw new \Exception('Configuration not found'); 91 | } 92 | 93 | return $this->getConfigurationPath(); 94 | } 95 | 96 | protected function getConfigurationPath(): string 97 | { 98 | return __DIR__ . '/../../Config/serve.yaml'; 99 | } 100 | 101 | protected function getConfigurationKey(): string 102 | { 103 | return 'serve'; 104 | } 105 | 106 | public function serve(string $host, string $port): void 107 | { 108 | $this->validateHost($host); 109 | $this->validatePort($port); 110 | // start host 111 | $this->startHost($host, $port); 112 | } 113 | 114 | protected function startHost(string $host, string $port): void 115 | { 116 | $this->info("Starting Mardira development server: http://{$host}:{$port}"); 117 | $this->info("Quit the server with CTRL+C."); 118 | // start server 119 | $this->startServer($host, $port); 120 | } 121 | 122 | protected function startServer(string $host, string $port): void 123 | { 124 | $this->info("Starting server..."); 125 | // run execute command with PHP built-in server 126 | $this->executeCommand("php -S {$host}:{$port} -t public"); 127 | } 128 | 129 | protected function executeCommand(string $command): void 130 | { 131 | passthru($command); 132 | } 133 | 134 | protected function info(string $message): void 135 | { 136 | echo $message . PHP_EOL; 137 | } 138 | 139 | protected function validateHost(string $host): void 140 | { 141 | $this->validate($host, 'host'); 142 | } 143 | 144 | protected function validatePort(string $port): void 145 | { 146 | $this->validate($port, 'port'); 147 | } 148 | 149 | protected function validate(string $value, string $type): void 150 | { 151 | $this->validateRequired($value, $type); 152 | $this->validateType($value, $type); 153 | } 154 | 155 | protected function validateRequired(string $value, string $type): void 156 | { 157 | if (empty($value)) { 158 | throw new \Exception("The {$type} is required."); 159 | } 160 | } 161 | 162 | protected function validateType(string $value, string $type): void 163 | { 164 | if ($type === 'host') { 165 | $this->validateHostType($value); 166 | } 167 | 168 | if ($type === 'port') { 169 | $this->validatePortType($value); 170 | } 171 | } 172 | 173 | protected function validateHostType(string $host): void 174 | { 175 | if (!filter_var($host, FILTER_VALIDATE_IP)) { 176 | throw new \Exception("The host must be a valid IP address."); 177 | } 178 | } 179 | 180 | protected function validatePortType(string $port): void 181 | { 182 | if (!is_numeric($port)) { 183 | throw new \Exception("The port must be a number."); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /App/Core/Commands/Stubs/Auth/auth-controller.stub: -------------------------------------------------------------------------------- 1 | $this->input->post('email'), 15 | 'password' => $this->input->post('password') 16 | ] 17 | ); 18 | 19 | $this->response(401, [ 20 | 'message' => 'Invalid credentials' 21 | ]); 22 | } 23 | 24 | public function register() 25 | { 26 | $email = $this->input->post('email'); 27 | $password = $this->input->post('password'); 28 | 29 | Auth::register([ 30 | 'email' => $email, 31 | 'password' => $password 32 | ]); 33 | 34 | $this->response(401, [ 35 | 'message' => 'User already exists' 36 | ]); 37 | } 38 | 39 | public function logout() : void 40 | { 41 | Auth::logout(); 42 | 43 | $this->response(401, [ 44 | 'message' => 'Invalid credentials' 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /App/Core/Commands/Stubs/Auth/auth-env.stub: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Token Expired Configuration in minutes 4 | EXPIRED_TOKEN=60 -------------------------------------------------------------------------------- /App/Core/Commands/Stubs/Auth/auth-package.stub: -------------------------------------------------------------------------------- 1 | verifyToken($token, DotEnvKey::get('ACCESS_TOKEN_SECRET_KEY')); 23 | if (isset($user->data)) { 24 | return $user->data; 25 | } 26 | return false; 27 | } 28 | 29 | public static function check(): bool 30 | { 31 | $token = Request::bearerToken(); 32 | $user = self::verify($token); 33 | if ($user) { 34 | $user = DB::table('users')->where('id', $user->id)->first(); 35 | if ($user) { 36 | return true; 37 | } 38 | } 39 | return false; 40 | } 41 | 42 | public static function user() 43 | { 44 | $token = Request::bearerToken(); 45 | $user = self::verify($token); 46 | if ($user) { 47 | $user = DB::table('users')->where('id', $user->id)->first(); 48 | if ($user) { 49 | return $user; 50 | } 51 | } 52 | return false; 53 | } 54 | 55 | public static function attempt(array $credentials = [], $validator = null) 56 | { 57 | // if validation fails 58 | if ($validator) { 59 | if ($validator->fails()) { 60 | self::response(401, [ 61 | 'message' => 'Invalid credentials', 62 | 'errors' => $validator->errors() 63 | ]); 64 | } 65 | } 66 | 67 | // validate form via validator 68 | if (self::validate($credentials)) { 69 | $user = DB::table('users'); 70 | $field = isset($credentials['email']) ? 'email' : 'username'; 71 | $user = $user->where($field, isset($credentials['email']) ? $credentials['email'] : $credentials['username']); 72 | $user = $user->first(); 73 | 74 | if ($user) { 75 | if (password_verify($credentials['password'], $user->password)) { 76 | $token = (new self)->generateToken([ 77 | 'data' => [ 78 | 'id' => $user->id, 79 | 'username' => $user->username, 80 | 'email' => $user->email, 81 | 'created_at' => $user->created_at, 82 | 'updated_at' => $user->updated_at, 83 | 'expires_at' => TimeHelper::setMinutes(30) 84 | ] 85 | ], DotEnvKey::get('ACCESS_TOKEN_SECRET_KEY')); 86 | self::response(200, [ 87 | 'message' => 'Login successful', 88 | 'token' => $token, 89 | 'expires_at' => date('Y-m-d H:i:s', TimeHelper::setMinutes(30)) 90 | ]); 91 | return true; 92 | } 93 | } 94 | } 95 | return false; 96 | } 97 | 98 | public static function register(array $credentials = []) 99 | { 100 | // validate form via validator 101 | if (self::validate($credentials)) { 102 | $user = DB::table('users'); 103 | $field = isset($credentials['email']) ? 'email' : 'username'; 104 | $user = $user->where($field, isset($credentials['email']) ? $credentials['email'] : $credentials['username']); 105 | $user = $user->first(); 106 | 107 | if (!$user) { 108 | $credentials['password'] = password_hash($credentials['password'], PASSWORD_DEFAULT); 109 | $insert = DB::table('users')->insert($credentials); 110 | $user = DB::table('users')->where('id', $insert)->first(); 111 | if ($user) { 112 | $token = (new self)->generateToken([ 113 | 'data' => [ 114 | 'id' => $user->id, 115 | 'username' => $user->username, 116 | 'email' => $user->email, 117 | 'created_at' => $user->created_at, 118 | 'updated_at' => $user->updated_at, 119 | 'expires_at' => TimeHelper::setMinutes(30) 120 | ] 121 | ], DotEnvKey::get('ACCESS_TOKEN_SECRET_KEY')); 122 | self::response(200, [ 123 | 'message' => 'Register successful', 124 | 'token' => $token, 125 | 'expires_at' => date('Y-m-d H:i:s', TimeHelper::setMinutes(30)) 126 | ]); 127 | return true; 128 | } 129 | } 130 | } 131 | return false; 132 | } 133 | 134 | 135 | public static function validate(array $credentials = []) 136 | { 137 | // foreach validate required each credentials 138 | $fields = []; 139 | 140 | foreach ($credentials as $key => $value) { 141 | 142 | // if key email use validate email 143 | if ($key == 'email') { 144 | $fieldset = [ 145 | 'required' => true, 146 | 'email' => true 147 | ]; 148 | $fields[] = [ 149 | $key => $fieldset 150 | ]; 151 | continue; 152 | } 153 | 154 | $fieldset = [ 155 | 'required' => true, 156 | ]; 157 | 158 | $fields[] = [ 159 | $key => $fieldset 160 | ]; 161 | } 162 | 163 | $fields = array_merge(...$fields); 164 | $validator = Validator::validate($fields, $credentials); 165 | 166 | if ($validator->fails()) { 167 | self::response(400, [ 168 | 'message' => 'Failed', 169 | 'errors' => $validator->errors() 170 | ]); 171 | return; 172 | } 173 | return true; 174 | } 175 | 176 | public static function logout() 177 | { 178 | $token = Request::bearerToken(); 179 | $user = self::verify($token); 180 | if ($user) { 181 | $user = DB::table('users')->where('id', $user->id)->first(); 182 | if ($user) { 183 | $token = (new self)->generateToken([ 184 | 'data' => [ 185 | 'id' => $user->id, 186 | 'username' => $user->username, 187 | 'email' => $user->email, 188 | 'created_at' => $user->created_at, 189 | 'updated_at' => $user->updated_at, 190 | 'expires_at' => TimeHelper::setMinutes(30) 191 | ] 192 | ], DotEnvKey::get('ACCESS_TOKEN_SECRET_KEY')); 193 | self::response(200, [ 194 | 'message' => 'Logout successful', 195 | 'token' => $token, 196 | 'expires_at' => date('Y-m-d H:i:s', TimeHelper::setMinutes(30)) 197 | ]); 198 | return true; 199 | } 200 | } 201 | return false; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /App/Core/Commands/Stubs/controller.stub: -------------------------------------------------------------------------------- 1 | increment('id'); 15 | $table->timestamps(); 16 | }); 17 | } 18 | 19 | public function down() 20 | { 21 | DummySchemaClassName::dropIfExists('DummyTable'); 22 | } 23 | }; -------------------------------------------------------------------------------- /App/Core/Commands/Stubs/model.stub: -------------------------------------------------------------------------------- 1 | group(function () { 2 | Router::DummyHttpVerbs('DummyRoute', 'DummyAction'); 3 | }); -------------------------------------------------------------------------------- /App/Core/Commands/Stubs/routes/use-route.stub: -------------------------------------------------------------------------------- 1 | use App\Controllers\DummyController; -------------------------------------------------------------------------------- /App/Core/Commands/Stubs/seeder.stub: -------------------------------------------------------------------------------- 1 | input = new Input(); 21 | $this->env = new DotEnvKey(); 22 | } 23 | 24 | /** 25 | * Model method to load the model 26 | * 27 | * @param mixed $model 28 | * @return object 29 | */ 30 | public function model(string $model): object 31 | { 32 | if (is_array($model)) { 33 | $model = array_map(function ($model) { 34 | $model = 'App\\Models\\' . $model; 35 | $model = $model . 'Models'; 36 | return new $model; 37 | }, $model); 38 | return $model; 39 | } 40 | $model = 'App\\Models\\' . $model; 41 | return new $model; 42 | } 43 | 44 | /** 45 | * load middleware 46 | * 47 | * @param mixed $middleware 48 | * @return void 49 | */ 50 | 51 | public function middleware(string $middleware) 52 | { 53 | $middleware = 'App\\Middlewares\\' . $middleware; 54 | return new $middleware; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /App/Core/Database.php: -------------------------------------------------------------------------------- 1 | connection = new PDO( 18 | "mysql:host=" . DotEnvKey::get('DB_HOST') . ";dbname=" . DotEnvKey::get('DB_NAME'), 19 | DotEnvKey::get('DB_USER'), 20 | DotEnvKey::get('DB_PASS') 21 | ); 22 | $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 23 | } catch (PDOException $e) { 24 | http_response_code(500); 25 | echo $e->getMessage(); 26 | } 27 | return $this->connection; 28 | } 29 | 30 | public static function getConnection(): object 31 | { 32 | return (new self)->connection; 33 | } 34 | } 35 | 36 | $database = new Database(); 37 | -------------------------------------------------------------------------------- /App/Core/DotEnvKey.php: -------------------------------------------------------------------------------- 1 | load(); 13 | return $_ENV[$key]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /App/Core/HasTokens.php: -------------------------------------------------------------------------------- 1 | encodeToken($payload, $key); 25 | return $token; 26 | } 27 | 28 | /** 29 | * Verify the token and return the payload if the token is valid 30 | * 31 | * @param mixed $token 32 | * @param mixed $key 33 | * @return object 34 | */ 35 | public function verifyToken(string $token, string $key) 36 | { 37 | if (strpos($token, 'Bearer') !== false) { 38 | $token = explode(' ', $token)[1]; 39 | } 40 | $decoded = $this->decodeToken($token, $key); 41 | return $decoded; 42 | } 43 | 44 | /** 45 | * Encode the token using the JWT library 46 | * 47 | * @param mixed $payload 48 | * @param mixed $key 49 | * @return string 50 | */ 51 | private function encodeToken(array $payload, string $key): string 52 | { 53 | return JWT::encode($payload, $key, $this->hash); 54 | } 55 | 56 | /** 57 | * Decode the token using the JWT library 58 | * 59 | * @param mixed $token 60 | * @param mixed $key 61 | * @return object 62 | */ 63 | private function decodeToken(string $token, string $key): object 64 | { 65 | try { 66 | return JWT::decode($token, new Key($key, $this->hash)); 67 | } catch (\Exception $e) { 68 | return (object) [ 69 | 'error' => $e->getMessage() 70 | ]; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /App/Core/Input.php: -------------------------------------------------------------------------------- 1 | [], 68 | 'body' => '' 69 | ]; 70 | } 71 | if (strpos($line, ': ') !== false) { 72 | [$header, $value] = explode(': ', $line); 73 | $data['headers'][$header] = $value; 74 | } else { 75 | $data['body'] .= $line; 76 | } 77 | return $data; 78 | }); 79 | }, $parts); 80 | $parts = array_map(function ($part) { 81 | $data = []; 82 | if (isset($part['headers']['Content-Disposition'])) { 83 | $filename = null; 84 | $tmp_name = null; 85 | preg_match('/name="([^"]+)"/', $part['headers']['Content-Disposition'], $match); 86 | $data['name'] = $match[1]; 87 | if (isset($part['headers']['Content-Type'])) { 88 | $data['type'] = $part['headers']['Content-Type']; 89 | } 90 | if (isset($part['headers']['Content-Length'])) { 91 | $data['size'] = $part['headers']['Content-Length']; 92 | } 93 | if (isset($part['headers']['Content-Transfer-Encoding'])) { 94 | $data['error'] = $part['headers']['Content-Transfer-Encoding']; 95 | } 96 | if (preg_match('/filename="([^"]+)"/', $part['headers']['Content-Disposition'], $match)) { 97 | $filename = $match[1]; 98 | $tmp_name = tempnam(ini_get('upload_tmp_dir'), 'php'); 99 | file_put_contents($tmp_name, $part['body']); 100 | } 101 | $data['parameter'] = $data['name']; 102 | $data['tmp_name'] = $tmp_name; 103 | $data['error'] = $filename ? UPLOAD_ERR_OK : UPLOAD_ERR_NO_FILE; 104 | $data['name'] = $filename; 105 | $data['size'] = filesize($tmp_name); 106 | } 107 | return $data; 108 | }, $parts); 109 | $parts = array_filter($parts); 110 | 111 | // rebase array with name as index 112 | 113 | $parts = array_reduce($parts, function ($data, $part) { 114 | if (isset($part['parameter'])) { 115 | $data[$part['parameter']] = $part; 116 | } 117 | return $data; 118 | }, []); 119 | 120 | $parts = array_map(function ($part) { 121 | unset($part['parameter']); 122 | return $part; 123 | }, $parts); 124 | 125 | $_FILES = $parts; 126 | } 127 | } 128 | 129 | 130 | if ($key) { 131 | return $_FILES[$key] ?? $default; 132 | } 133 | 134 | return $_FILES; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /App/Core/Kernel.php: -------------------------------------------------------------------------------- 1 | init(); 12 | } 13 | 14 | protected function init() 15 | { 16 | $this->loadConsole(); 17 | } 18 | 19 | public static function run() 20 | { 21 | new static(); 22 | } 23 | 24 | public function loadConsole() 25 | { 26 | $console = $this->console(); 27 | $application = new Application(); 28 | foreach ($console as $command) { 29 | $application->add(new $command()); 30 | } 31 | $application->run(); 32 | } 33 | 34 | public function console() 35 | { 36 | $console = [ 37 | 'App\Core\Commands\CreateControllerCommand', 38 | 'App\Core\Commands\CreateModelCommand', 39 | 'App\Core\Commands\CreateMigrationCommand', 40 | 'App\Core\Commands\CreateSeederCommand', 41 | 'App\Core\Commands\CreateDotEnvCommand', 42 | 'App\Core\Commands\CreateAuthCommand', 43 | 'App\Core\Commands\CreateMiddlewareCommand', 44 | 'App\Core\Commands\ServeCommand', 45 | 'App\Core\Commands\RunMigrateCommand', 46 | 'App\Core\Commands\RunMigrateRefreshCommand', 47 | 'App\Core\Commands\RunMigrateDatabaseCommand', 48 | 'App\Core\Commands\RunGlobalSeederCommand', 49 | 'App\Core\Commands\RunSeederCommand', 50 | 'App\Core\Commands\CreateUpdateCommand', 51 | 'App\Core\Commands\CreateRouteCommand', 52 | ]; 53 | return $console; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /App/Core/Middleware.php: -------------------------------------------------------------------------------- 1 | connection; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /App/Core/Model.php: -------------------------------------------------------------------------------- 1 | statement = new Database(); 17 | $this->statement = $this->statement->connection; 18 | } 19 | 20 | public static function all(): array 21 | { 22 | $table = (new static)->table; 23 | $query = "SELECT * FROM {$table}"; 24 | $statement = (new static)->statement->prepare($query); 25 | $statement->execute(); 26 | return $statement->fetchAll(PDO::FETCH_OBJ); 27 | } 28 | 29 | public static function find(int $id) 30 | { 31 | $table = (new static)->table; 32 | $primaryKey = (new static)->primaryKey; 33 | $query = "SELECT * FROM {$table} WHERE {$primaryKey} = :id"; 34 | $statement = (new static)->statement->prepare($query); 35 | $statement->bindParam(':id', $id); 36 | $statement->execute(); 37 | return $statement->fetch(PDO::FETCH_OBJ); 38 | } 39 | 40 | public function findWhere(array $data) 41 | { 42 | $query = "SELECT * FROM {$this->table} WHERE "; 43 | $query .= implode(' AND ', array_map(function ($key) { 44 | return $key . ' = :' . $key; 45 | }, array_keys($data))); 46 | $statement = $this->statement->prepare($query); 47 | foreach ($data as $key => $value) { 48 | $statement->bindValue(':' . $key, $value); 49 | } 50 | $statement->execute(); 51 | return $statement->fetch(PDO::FETCH_OBJ); 52 | } 53 | 54 | public function row(): int 55 | { 56 | $query = "SELECT * FROM {$this->table}"; 57 | $statement = $this->statement->prepare($query); 58 | $statement->execute(); 59 | return $statement->rowCount(); 60 | } 61 | 62 | public function where(array $data): array 63 | { 64 | $query = "SELECT * FROM {$this->table} WHERE "; 65 | $query .= implode(' AND ', array_map(function ($key) { 66 | return $key . ' = :' . $key; 67 | }, array_keys($data))); 68 | $statement = $this->statement->prepare($query); 69 | foreach ($data as $key => $value) { 70 | $statement->bindValue(':' . $key, $value); 71 | } 72 | $statement->execute(); 73 | return $statement->fetchAll(PDO::FETCH_OBJ); 74 | } 75 | 76 | public function whereIn(string $column, array $data): array 77 | { 78 | $query = "SELECT * FROM {$this->table} WHERE {$column} IN ("; 79 | $query .= implode(', ', array_map(function ($key) { 80 | return ':' . $key; 81 | }, array_keys($data))); 82 | $query .= ")"; 83 | $statement = $this->statement->prepare($query); 84 | foreach ($data as $key => $value) { 85 | $statement->bindValue(':' . $key, $value); 86 | } 87 | $statement->execute(); 88 | return $statement->fetchAll(PDO::FETCH_OBJ); 89 | } 90 | 91 | public function whereNotIn(string $column, array $data): array 92 | { 93 | $query = "SELECT * FROM {$this->table} WHERE {$column} NOT IN ("; 94 | $query .= implode(', ', array_map(function ($key) { 95 | return ':' . $key; 96 | }, array_keys($data))); 97 | $query .= ")"; 98 | $statement = $this->statement->prepare($query); 99 | foreach ($data as $key => $value) { 100 | $statement->bindValue(':' . $key, $value); 101 | } 102 | $statement->execute(); 103 | return $statement->fetchAll(PDO::FETCH_OBJ); 104 | } 105 | 106 | public function create(array $data): bool 107 | { 108 | $query = "INSERT INTO {$this->table} SET "; 109 | $query .= implode(', ', array_map(function ($key) { 110 | return $key . ' = :' . $key; 111 | }, array_keys($data))); 112 | $statement = $this->statement->prepare($query); 113 | foreach ($data as $key => $value) { 114 | $statement->bindValue(':' . $key, $value); 115 | } 116 | return $statement->execute(); 117 | } 118 | 119 | public function update(int $id, array $data): bool 120 | { 121 | $query = "UPDATE {$this->table} SET "; 122 | $query .= implode(', ', array_map(function ($key) { 123 | return $key . ' = :' . $key; 124 | }, array_keys($data))); 125 | $query .= " WHERE {$this->primaryKey} = :id"; 126 | $statement = $this->statement->prepare($query); 127 | foreach ($data as $key => $value) { 128 | $statement->bindValue(':' . $key, $value); 129 | } 130 | $statement->bindValue(':id', $id); 131 | return $statement->execute(); 132 | } 133 | 134 | public function updateOrCreate(array $data): bool 135 | { 136 | $query = "INSERT INTO {$this->table} SET "; 137 | $query .= implode(', ', array_map(function ($key) { 138 | return $key . ' = :' . $key; 139 | }, array_keys($data))); 140 | $query .= " ON DUPLICATE KEY UPDATE "; 141 | $query .= implode(', ', array_map(function ($key) { 142 | return $key . ' = :' . $key; 143 | }, array_keys($data))); 144 | $statement = $this->statement->prepare($query); 145 | foreach ($data as $key => $value) { 146 | $statement->bindValue(':' . $key, $value); 147 | } 148 | return $statement->execute(); 149 | } 150 | 151 | 152 | public function delete(int $id): bool 153 | { 154 | $query = "DELETE FROM {$this->table} WHERE {$this->primaryKey} = :id"; 155 | $statement = $this->statement->prepare($query); 156 | $statement->bindValue(':id', $id); 157 | return $statement->execute(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /App/Core/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | connection = Database::getConnection(); 26 | } 27 | 28 | 29 | public static function table(string $table): QueryBuilder 30 | { 31 | $queryBuilder = new QueryBuilder(); 32 | $queryBuilder->table = $table; 33 | return $queryBuilder; 34 | } 35 | 36 | public function query(string $query): QueryBuilder 37 | { 38 | $this->query = $query; 39 | return $this; 40 | } 41 | 42 | public function select(array $columns = ['*']): QueryBuilder 43 | { 44 | $this->columns = $columns; 45 | return $this; 46 | } 47 | 48 | public function from(string $table): QueryBuilder 49 | { 50 | $this->table = $table; 51 | return $this; 52 | } 53 | 54 | public function where(string $column, $value, string $operator = '='): QueryBuilder 55 | { 56 | $this->where[] = [ 57 | 'column' => $column, 58 | 'value' => $value, 59 | 'operator' => $operator, 60 | ]; 61 | return $this; 62 | } 63 | 64 | public function orWhere(string $column, $value, string $operator = '='): QueryBuilder 65 | { 66 | $this->where[] = [ 67 | 'column' => $column, 68 | 'value' => $value, 69 | 'operator' => $operator, 70 | 'or' => true, 71 | ]; 72 | return $this; 73 | } 74 | 75 | public function whereIn(string $column, array $values): QueryBuilder 76 | { 77 | $this->where[] = [ 78 | 'column' => $column, 79 | 'value' => $values, 80 | 'operator' => 'IN', 81 | ]; 82 | return $this; 83 | } 84 | 85 | public function whereNotIn(string $column, array $values): QueryBuilder 86 | { 87 | $this->where[] = [ 88 | 'column' => $column, 89 | 'value' => $values, 90 | 'operator' => 'NOT IN', 91 | ]; 92 | return $this; 93 | } 94 | 95 | public function whereNull(string $column): QueryBuilder 96 | { 97 | $this->where[] = [ 98 | 'column' => $column, 99 | 'value' => null, 100 | 'operator' => 'IS NULL', 101 | ]; 102 | return $this; 103 | } 104 | 105 | public function whereNotNull(string $column): QueryBuilder 106 | { 107 | $this->where[] = [ 108 | 'column' => $column, 109 | 'value' => null, 110 | 'operator' => 'IS NOT NULL', 111 | ]; 112 | return $this; 113 | } 114 | 115 | public function orderBy(string $column, string $direction = 'ASC'): QueryBuilder 116 | { 117 | $this->order[] = [ 118 | 'column' => $column, 119 | 'direction' => $direction, 120 | ]; 121 | return $this; 122 | } 123 | 124 | public function orderByAsc(string $column): QueryBuilder 125 | { 126 | return $this->orderBy($column, 'ASC'); 127 | } 128 | 129 | public function orderByDesc(string $column): QueryBuilder 130 | { 131 | return $this->orderBy($column, 'DESC'); 132 | } 133 | 134 | public function groupBy(string $column): QueryBuilder 135 | { 136 | $this->groupBy[] = $column; 137 | return $this; 138 | } 139 | 140 | public function having(string $column, $value, string $operator = '='): QueryBuilder 141 | { 142 | $this->having[] = [ 143 | 'column' => $column, 144 | 'value' => $value, 145 | 'operator' => $operator, 146 | ]; 147 | return $this; 148 | } 149 | 150 | public function sum(string $column, string $alias): QueryBuilder 151 | { 152 | $this->columns[] = "SUM({$column}) AS {$alias}"; 153 | return $this; 154 | } 155 | 156 | public function avg(string $column, string $alias): QueryBuilder 157 | { 158 | $this->columns[] = "AVG({$column}) AS {$alias}"; 159 | return $this; 160 | } 161 | 162 | public function min(string $column, string $alias): QueryBuilder 163 | { 164 | $this->columns[] = "MIN({$column}) AS {$alias}"; 165 | return $this; 166 | } 167 | 168 | public function max(string $column, string $alias): QueryBuilder 169 | { 170 | $this->columns[] = "MAX({$column}) AS {$alias}"; 171 | return $this; 172 | } 173 | 174 | public function join(string $table, string $first, string $second, string $type = ''): QueryBuilder 175 | { 176 | $this->joins[] = [ 177 | 'table' => $table, 178 | 'first' => $first, 179 | 'operator' => '=', 180 | 'second' => $second, 181 | 'type' => $type, 182 | ]; 183 | return $this; 184 | } 185 | 186 | public function leftJoin(string $table, string $first, string $second): QueryBuilder 187 | { 188 | return $this->join($table, $first, $second, 'LEFT'); 189 | } 190 | 191 | public function rightJoin(string $table, string $first, string $second): QueryBuilder 192 | { 193 | return $this->join($table, $first, $second, 'RIGHT'); 194 | } 195 | 196 | public function fullJoin(string $table, string $first, string $second): QueryBuilder 197 | { 198 | return $this->join($table, $first, $second, 'FULL'); 199 | } 200 | 201 | public function innerJoin(string $table, string $first, string $second): QueryBuilder 202 | { 203 | return $this->join($table, $first, $second, 'INNER'); 204 | } 205 | 206 | public function limit(int $limit): QueryBuilder 207 | { 208 | $this->limit = $limit; 209 | return $this; 210 | } 211 | 212 | public function insert($data): bool 213 | { 214 | if (isset($data[0])) { 215 | $columns = implode(', ', array_keys($data[0])); 216 | $values = implode(', ', array_map(function ($column) { 217 | return ':' . $column; 218 | }, array_keys($data[0]))); 219 | foreach ($data as $key => $value) { 220 | $query = "INSERT INTO {$this->table} ({$columns}) VALUES ({$values})"; 221 | $statement = $this->connection->prepare($query); 222 | foreach ($value as $key => $value) { 223 | $statement->bindValue(':' . $key, $value); 224 | } 225 | $statement->execute(); 226 | } 227 | return true; 228 | } else { 229 | $columns = implode(', ', array_keys($data)); 230 | $values = implode(', ', array_map(function ($column) { 231 | return ':' . $column; 232 | }, array_keys($data))); 233 | $query = "INSERT INTO {$this->table} ({$columns}) VALUES ({$values})"; 234 | $statement = $this->connection->prepare($query); 235 | foreach ($data as $key => $value) { 236 | $statement->bindValue(':' . $key, $value); 237 | } 238 | return $statement->execute(); 239 | } 240 | } 241 | 242 | public function insertGetId(array $data): int 243 | { 244 | $columns = implode(', ', array_keys($data)); 245 | $values = implode(', ', array_map(function ($column) { 246 | return ':' . $column; 247 | }, array_keys($data))); 248 | $query = "INSERT INTO {$this->table} ({$columns}) VALUES ({$values})"; 249 | $statement = $this->connection->prepare($query); 250 | foreach ($data as $key => $value) { 251 | $statement->bindValue(':' . $key, $value); 252 | } 253 | $statement->execute(); 254 | return (int)$this->connection->lastInsertId(); 255 | } 256 | 257 | public function get(): array 258 | { 259 | $query = $this->buildQuery(); 260 | $this->statement = $this->connection->prepare($query); 261 | $this->bindValues(); 262 | $this->statement->execute(); 263 | return $this->statement->fetchAll(PDO::FETCH_OBJ); 264 | } 265 | 266 | public function first() 267 | { 268 | $query = $this->buildQuery(); 269 | $this->statement = $this->connection->prepare($query); 270 | $this->bindValues(); 271 | $this->statement->execute(); 272 | return $this->statement->fetch(PDO::FETCH_OBJ); 273 | } 274 | 275 | public function count(): int 276 | { 277 | $query = $this->buildQuery(); 278 | $this->statement = $this->connection->prepare($query); 279 | $this->bindValues(); 280 | $this->statement->execute(); 281 | return $this->statement->rowCount(); 282 | } 283 | 284 | 285 | public function update(array $data): bool 286 | { 287 | 288 | $query = "UPDATE {$this->table} SET "; 289 | $query .= implode(', ', array_map(function ($column) { 290 | return $column . ' = :' . $column; 291 | }, array_keys($data))); 292 | // split select query 293 | $sql = $this->buildQuery(); 294 | $where = substr($sql, strpos($sql, 'WHERE')); 295 | $query .= ' ' . $where; 296 | $this->statement = $this->connection->prepare($query); 297 | foreach ($data as $key => $value) { 298 | $this->statement->bindValue(':' . $key, $value); 299 | } 300 | $this->bindValues(); 301 | return $this->statement->execute(); 302 | } 303 | 304 | 305 | 306 | public function buildQuery(): string 307 | { 308 | $query = "SELECT "; 309 | 310 | if (count($this->columns) > 0) { 311 | $query .= implode(', ', $this->columns); 312 | } else { 313 | $query .= '*'; 314 | } 315 | 316 | $query .= " FROM {$this->table}"; 317 | 318 | if (count($this->joins) > 0) { 319 | foreach ($this->joins as $join) { 320 | $query .= " {$join['type']} JOIN {$join['table']} ON {$join['first']} {$join['operator']} {$join['second']}"; 321 | } 322 | } 323 | 324 | if (count($this->where) > 0) { 325 | $query .= ' WHERE '; 326 | foreach ($this->where as $key => $where) { 327 | if (is_array($where['value'])) { 328 | $query .= "{$where['column']} {$where['operator']} ("; 329 | foreach ($where['value'] as $value) { 330 | //remove reference column name for binding a.id to id 331 | if (strpos($where['column'], '.') !== false) { 332 | // change to _ 333 | $columns = str_replace('.', '_', $where['column']); 334 | } else { 335 | $columns = $where['column']; 336 | } 337 | $query .= ":{$columns}, "; 338 | } 339 | $query = rtrim($query, ', '); 340 | $query .= ')'; 341 | } else { 342 | //remove reference column name for binding a.id to id 343 | if (strpos($where['column'], '.') !== false) { 344 | // change to _ 345 | $columns = str_replace('.', '_', $where['column']); 346 | } else { 347 | $columns = $where['column']; 348 | } 349 | 350 | $query .= "{$where['column']} {$where['operator']} :{$columns}"; 351 | } 352 | if ($key < count($this->where) - 1) { 353 | // if isset or 354 | $query .= ' AND '; 355 | } 356 | } 357 | } 358 | 359 | if (count($this->order) > 0) { 360 | $query .= ' ORDER BY '; 361 | foreach ($this->order as $key => $order) { 362 | $query .= "{$order['column']} {$order['direction']}"; 363 | if ($key < count($this->order) - 1) { 364 | $query .= ', '; 365 | } 366 | } 367 | } 368 | 369 | if (count($this->groupBy) > 0) { 370 | $query .= " GROUP BY " . implode(', ', $this->groupBy); 371 | } 372 | 373 | if ($this->limit > 0) { 374 | $query .= " LIMIT {$this->limit}"; 375 | } 376 | 377 | if (count($this->having) > 0) { 378 | $query .= ' HAVING '; 379 | foreach ($this->having as $key => $having) { 380 | $query .= "{$having['column']} {$having['operator']} :{$having['column']}"; 381 | if ($key < count($this->having) - 1) { 382 | $query .= ' AND '; 383 | } 384 | } 385 | } 386 | 387 | return $query; 388 | } 389 | 390 | public function bindValues() 391 | { 392 | 393 | // fix Invalid bind parameter with alias column 394 | 395 | foreach ($this->where as $where) { 396 | if (is_array($where['value'])) { 397 | foreach ($where['value'] as $value) { 398 | //remove reference column name for binding a.id to id 399 | if (strpos($where['column'], '.') !== false) { 400 | // change to _ 401 | $columns = str_replace('.', '_', $where['column']); 402 | } else { 403 | $columns = $where['column']; 404 | } 405 | $this->statement->bindValue(':' . $columns, $value); 406 | } 407 | } else { 408 | //remove reference column name for binding a.id to id 409 | if (strpos($where['column'], '.') !== false) { 410 | // change to _ 411 | $columns = str_replace('.', '_', $where['column']); 412 | } else { 413 | $columns = $where['column']; 414 | } 415 | $this->statement->bindValue(':' . $columns, $where['value']); 416 | } 417 | } 418 | } 419 | 420 | public function delete(string $column, string $value): bool 421 | { 422 | $query = "DELETE FROM {$this->table} WHERE {$column} = :value"; 423 | $statement = $this->connection->prepare($query); 424 | $statement->bindValue(':value', $value); 425 | return $statement->execute(); 426 | } 427 | 428 | 429 | } 430 | -------------------------------------------------------------------------------- /App/Core/Request.php: -------------------------------------------------------------------------------- 1 | method() === 'GET') { 22 | foreach ($_GET as $key => $value) { 23 | $body[$key] = filter_input(INPUT_GET, $key, FILTER_SANITIZE_SPECIAL_CHARS); 24 | } 25 | } 26 | 27 | if ($this->method() === 'POST') { 28 | foreach ($_POST as $key => $value) { 29 | $body[$key] = filter_input(INPUT_POST, $key, FILTER_SANITIZE_SPECIAL_CHARS); 30 | } 31 | } 32 | 33 | return $body; 34 | } 35 | 36 | public function file(): array 37 | { 38 | $file = []; 39 | 40 | if ($this->method() === 'POST') { 41 | foreach ($_FILES as $key => $value) { 42 | $file[$key] = $value; 43 | } 44 | } 45 | 46 | return $file; 47 | } 48 | 49 | public static function bearerToken(): string 50 | { 51 | $headers = getallheaders(); 52 | $token = $headers['Authorization'] ?? ''; 53 | 54 | if (preg_match('/Bearer\s(\S+)/', $token, $matches)) { 55 | return $matches[1]; 56 | } 57 | 58 | return ''; 59 | } 60 | 61 | // request make dynamic property when request post by key and value 62 | public function __get($key) 63 | { 64 | return $this->body()[$key] ?? ''; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /App/Core/Responses.php: -------------------------------------------------------------------------------- 1 | $method, 23 | 'path' => $path, 24 | 'controller' => $controller, 25 | 'function' => $function, 26 | 'middleware' => $middlewares 27 | ]; 28 | } 29 | 30 | public static function get(string $path, $action = null, array $middlewares = []): void 31 | { 32 | if (is_array($action)) { 33 | $controller = $action[0]; 34 | $function = $action[1]; 35 | } else { 36 | $controller = self::$controller; 37 | $function = $action; 38 | } 39 | 40 | if (count(self::$middlewares) > 0) { 41 | $middlewares = array_merge(self::$middlewares, $middlewares); 42 | } 43 | 44 | self::add('GET', $path, $controller, $function, $middlewares); 45 | } 46 | 47 | public static function post(string $path, $action = null, array $middlewares = []): void 48 | { 49 | if (is_array($action)) { 50 | $controller = $action[0]; 51 | $function = $action[1]; 52 | } else { 53 | $controller = self::$controller; 54 | $function = $action; 55 | } 56 | 57 | if (count(self::$middlewares) > 0) { 58 | $middlewares = array_merge(self::$middlewares, $middlewares); 59 | } 60 | 61 | self::add('POST', $path, $controller, $function, $middlewares); 62 | } 63 | 64 | public static function put(string $path, $action = null, array $middlewares = []): void 65 | { 66 | if (is_array($action)) { 67 | $controller = $action[0]; 68 | $function = $action[1]; 69 | } else { 70 | $controller = self::$controller; 71 | $function = $action; 72 | } 73 | 74 | if (count(self::$middlewares) > 0) { 75 | $middlewares = array_merge(self::$middlewares, $middlewares); 76 | } 77 | 78 | self::add('PUT', $path, $controller, $function, $middlewares); 79 | } 80 | 81 | 82 | public static function patch(string $path, $action = null, array $middlewares = []): void 83 | { 84 | if (is_array($action)) { 85 | $controller = $action[0]; 86 | $function = $action[1]; 87 | } else { 88 | $controller = self::$controller; 89 | $function = $action; 90 | } 91 | 92 | if (count(self::$middlewares) > 0) { 93 | $middlewares = array_merge(self::$middlewares, $middlewares); 94 | } 95 | 96 | self::add('PATCH', $path, $controller, $function, $middlewares); 97 | } 98 | 99 | public static function delete(string $path, $action = null, array $middlewares = []): void 100 | { 101 | if (is_array($action)) { 102 | $controller = $action[0]; 103 | $function = $action[1]; 104 | } else { 105 | $controller = self::$controller; 106 | $function = $action; 107 | } 108 | 109 | if (count(self::$middlewares) > 0) { 110 | $middlewares = array_merge(self::$middlewares, $middlewares); 111 | } 112 | 113 | self::add('DELETE', $path, $controller, $function, $middlewares); 114 | } 115 | 116 | 117 | 118 | public static function controller(string $controller, array $middlewares = []): Router 119 | { 120 | self::$controller = $controller; 121 | self::$middlewares = $middlewares; 122 | return new self; 123 | } 124 | 125 | public function group(callable $callback): void 126 | { 127 | $callback(); 128 | } 129 | 130 | public static function run(): void 131 | { 132 | $requestMethod = $_SERVER['REQUEST_METHOD']; 133 | $requestUri = $_SERVER['REQUEST_URI']; 134 | $requestUri = explode('?', $requestUri)[0]; 135 | 136 | // if default routes response welcome message 137 | if ($requestUri === '/') { 138 | self::response(200, ['message' => 'Welcome to the Mardira Framework']); 139 | return; 140 | } 141 | foreach (self::$routes as $route) { 142 | // remove $requestUri from /users/ to /users if last character is / 143 | if (substr($requestUri, -1) === '/') { 144 | $requestUri = substr($requestUri, 0, -1); 145 | } 146 | 147 | if ($route['path'] === $requestUri) { 148 | if ($route['method'] === $requestMethod) { 149 | self::checkRoute($route['path'], $route['controller'], $route['function'], $requestUri, $route['middleware']); 150 | return; 151 | } 152 | } 153 | } 154 | 155 | self::response(404, ['message' => 'Not Found']); 156 | } 157 | 158 | 159 | private static function checkRoute($routePath, $controller, $function, $requestUri, $middlewares) 160 | { 161 | $path = $routePath; 162 | $path = str_replace('/', '\/', $path); 163 | $path = preg_replace('/\{[a-zA-Z0-9]+\}/', '([a-zA-Z0-9]+)', $path); 164 | $path = '/^' . $path . '$/'; 165 | try { 166 | if (preg_match($path, $requestUri, $matches)) { 167 | 168 | // if route has middleware 169 | if (count($middlewares) > 0) { 170 | foreach ($middlewares as $middleware) { 171 | $middleware = new $middleware; 172 | $middleware->handle(function () { 173 | return; 174 | }); 175 | } 176 | } 177 | 178 | $controller = $controller; 179 | $function = $function; 180 | $controller = new $controller; 181 | $controller->$function(...array_slice($matches, 1)); 182 | return; 183 | } 184 | } catch (\Throwable $th) { 185 | self::response(500, [ 186 | 'message' => $th->getMessage(), 187 | 'file' => $th->getFile(), 188 | 'line' => $th->getLine(), 189 | 'trace' => $th->getTrace(), 190 | ]); 191 | return; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /App/Core/Schema.php: -------------------------------------------------------------------------------- 1 | create(); 14 | } 15 | 16 | public static function drop(string $table) 17 | { 18 | $blueprint = new Blueprint($table); 19 | $blueprint->drop(); 20 | } 21 | 22 | public static function dropIfExists(string $table) 23 | { 24 | $blueprint = new Blueprint($table); 25 | $blueprint->dropIfExists(); 26 | } 27 | 28 | public static function table(string $table, callable $callback) 29 | { 30 | $blueprint = new Blueprint($table); 31 | $callback($blueprint); 32 | $blueprint->execute(); 33 | } 34 | 35 | public static function rename(string $table, string $newName) 36 | { 37 | $blueprint = new Blueprint($table); 38 | $blueprint->rename($newName); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /App/Core/Seeder.php: -------------------------------------------------------------------------------- 1 | call($s); 15 | } 16 | return; 17 | } 18 | $seeder = $this->getClassName($seeder); 19 | $this->addSeederCalled($seeder); 20 | $seeder = new $seeder(); 21 | $seeder->run(); 22 | } 23 | 24 | protected function getClassName(string $name): string 25 | { 26 | return ucfirst($name); 27 | } 28 | 29 | protected function getSeederPath(): string 30 | { 31 | return __DIR__ . '/../Database/Seeders'; 32 | } 33 | 34 | // count seeder called 35 | public function countSeederCalled(): int 36 | { 37 | return count($this->seederCalled); 38 | } 39 | 40 | // get seeder called 41 | public function getSeederCalled(): array 42 | { 43 | return $this->seederCalled; 44 | } 45 | 46 | // add seeder called 47 | public function addSeederCalled(string $seeder): void 48 | { 49 | $this->seederCalled[] = $seeder; 50 | } 51 | 52 | // check seeder called 53 | public function checkSeederCalled(string $seeder): bool 54 | { 55 | return in_array($seeder, $this->seederCalled); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /App/Core/Upload.php: -------------------------------------------------------------------------------- 1 | file = $file; 34 | $this->path = __DIR__ . '/../../public/uploads/'; 35 | $this->name = $file['name'] ?? ''; 36 | $this->extension = pathinfo($this->name, PATHINFO_EXTENSION); 37 | $this->size = $file['size'] ?? 0; 38 | $this->mimeType = $file['type'] ?? ''; 39 | $this->error = $file['error'] ?? 0; 40 | $this->allowedExtensions = []; 41 | $this->allowedMimeTypes = []; 42 | $this->maxSize = 0; 43 | $this->overwrite = false; 44 | $this->randomName = false; 45 | $this->newName = null; 46 | $this->isUploaded = false; 47 | $this->isMoved = false; 48 | $this->isImage = false; 49 | $this->imageWidth = 0; 50 | $this->imageHeight = 0; 51 | $this->imageType = 0; 52 | $this->imageSizeStr = ''; 53 | $this->imageMime = ''; 54 | $this->imageExtension = ''; 55 | } 56 | 57 | public function setPath($path) 58 | { 59 | $this->path = $path; 60 | return $this; 61 | } 62 | 63 | public function setName($name) 64 | { 65 | $this->name = $name; 66 | return $this; 67 | } 68 | 69 | public function setExtension($extension) 70 | { 71 | $this->extension = $extension; 72 | return $this; 73 | } 74 | 75 | public function setSize($size) 76 | { 77 | $this->size = $size; 78 | return $this; 79 | } 80 | 81 | public function setMimeType($mimeType) 82 | { 83 | $this->mimeType = $mimeType; 84 | return $this; 85 | } 86 | 87 | public function setError($error) 88 | { 89 | $this->error = $error; 90 | return $this; 91 | } 92 | 93 | public function setAllowedExtensions($allowedExtensions) 94 | { 95 | $this->allowedExtensions = $allowedExtensions; 96 | return $this; 97 | } 98 | 99 | public function setAllowedMimeTypes($allowedMimeTypes) 100 | { 101 | $this->allowedMimeTypes = $allowedMimeTypes; 102 | return $this; 103 | } 104 | 105 | public function setMaxSize($maxSize) 106 | { 107 | $this->maxSize = $maxSize; 108 | return $this; 109 | } 110 | 111 | public function setOverwrite($overwrite) 112 | { 113 | $this->overwrite = $overwrite; 114 | return $this; 115 | } 116 | 117 | public function setRandomName($randomName) 118 | { 119 | $this->randomName = $randomName; 120 | return $this; 121 | } 122 | 123 | public function setNewName($newName) 124 | { 125 | $this->newName = $newName; 126 | return $this; 127 | } 128 | 129 | public function getPath() 130 | { 131 | return $this->path; 132 | } 133 | 134 | public function getName() 135 | { 136 | return $this->name; 137 | } 138 | 139 | public function getExtension() 140 | { 141 | return $this->extension; 142 | } 143 | 144 | public function getSize() 145 | { 146 | return $this->size; 147 | } 148 | 149 | public function getMimeType() 150 | { 151 | return $this->mimeType; 152 | } 153 | 154 | public function getError() 155 | { 156 | return $this->error; 157 | } 158 | 159 | public function getAllowedExtensions() 160 | { 161 | return $this->allowedExtensions; 162 | } 163 | 164 | public function getAllowedMimeTypes() 165 | { 166 | return $this->allowedMimeTypes; 167 | } 168 | 169 | public function getMaxSize() 170 | { 171 | return $this->maxSize; 172 | } 173 | 174 | public function getOverwrite() 175 | { 176 | return $this->overwrite; 177 | } 178 | 179 | public function getRandomName() 180 | { 181 | return $this->randomName; 182 | } 183 | 184 | public function getNewName() 185 | { 186 | return $this->newName; 187 | } 188 | 189 | public function isUploaded() 190 | { 191 | return $this->isUploaded; 192 | } 193 | 194 | public function isMoved() 195 | { 196 | return $this->isMoved; 197 | } 198 | 199 | 200 | public function getImageWidth() 201 | { 202 | return $this->imageWidth; 203 | } 204 | 205 | public function getImageHeight() 206 | { 207 | return $this->imageHeight; 208 | } 209 | 210 | public function getImageType() 211 | { 212 | return $this->imageType; 213 | } 214 | 215 | public function getImageSizeStr() 216 | { 217 | return $this->imageSizeStr; 218 | } 219 | 220 | public function getImageMime() 221 | { 222 | return $this->imageMime; 223 | } 224 | 225 | public function getImageExtension() 226 | { 227 | return $this->imageExtension; 228 | } 229 | 230 | public static function upload() 231 | { 232 | return new static($_FILES); 233 | } 234 | 235 | public function validate() 236 | { 237 | if ($this->error !== 0) { 238 | throw new \Exception('Error uploading file'); 239 | } 240 | 241 | if (!empty($this->allowedExtensions) && !in_array($this->extension, $this->allowedExtensions)) { 242 | throw new \Exception('Extension not allowed'); 243 | } 244 | 245 | if (!empty($this->allowedMimeTypes) && !in_array($this->mimeType, $this->allowedMimeTypes)) { 246 | throw new \Exception('Mime type not allowed'); 247 | } 248 | 249 | if ($this->maxSize > 0 && $this->size > $this->maxSize) { 250 | throw new \Exception('File size exceeds limit'); 251 | } 252 | 253 | if (!is_dir($this->path)) { 254 | throw new \Exception('Directory does not exist'); 255 | } 256 | 257 | if (!is_writable($this->path)) { 258 | throw new \Exception('Directory is not writable'); 259 | } 260 | 261 | if (file_exists($this->path . $this->name) && !$this->overwrite) { 262 | throw new \Exception('File already exists'); 263 | } 264 | 265 | $this->isUploaded = true; 266 | return $this; 267 | } 268 | 269 | public function move() 270 | { 271 | if (!$this->isUploaded) { 272 | throw new \Exception('File not uploaded'); 273 | } 274 | 275 | if ($this->randomName) { 276 | $this->name = uniqid() . '.' . $this->extension; 277 | } 278 | 279 | if ($this->newName) { 280 | $this->name = $this->newName . '.' . $this->extension; 281 | } 282 | 283 | // check if tmp_name directory exists 284 | if (!file_exists($this->file['tmp_name'])) { 285 | throw new \Exception('File not found'); 286 | } 287 | 288 | // upload file from cross server 289 | if (is_uploaded_file($this->file['tmp_name'])) { 290 | if (!move_uploaded_file($this->file['tmp_name'], $this->path . $this->name)) { 291 | throw new \Exception('Error moving file'); 292 | } 293 | } // upload file from same server 294 | else { 295 | if (!rename($this->file['tmp_name'], $this->path . $this->name)) { 296 | throw new \Exception('Error moving file'); 297 | } 298 | } 299 | 300 | $this->isMoved = true; 301 | return $this; 302 | } 303 | 304 | public function getImageInfo() 305 | { 306 | if (!$this->isImage) { 307 | throw new \Exception('File is not an image'); 308 | } 309 | 310 | $imageInfo = getimagesize($this->file['tmp_name']); 311 | $this->imageWidth = $imageInfo[0]; 312 | $this->imageHeight = $imageInfo[1]; 313 | $this->imageType = $imageInfo[2]; 314 | $this->imageSizeStr = $imageInfo[3]; 315 | $this->imageMime = $imageInfo['mime']; 316 | $this->imageExtension = image_type_to_extension($imageInfo[2], false); 317 | return $this; 318 | } 319 | 320 | public function isImage() 321 | { 322 | $this->isImage = getimagesize($this->file['tmp_name']) !== false; 323 | return $this; 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /App/Core/Validator.php: -------------------------------------------------------------------------------- 1 | $rules) { 18 | foreach ($rules as $rule => $value) { 19 | switch ($rule) { 20 | case 'required': 21 | self::required($field); 22 | break; 23 | case 'min': 24 | self::min($field, $value); 25 | break; 26 | case 'max': 27 | self::max($field, $value); 28 | break; 29 | case 'email': 30 | self::email($field); 31 | break; 32 | case 'file': 33 | self::file($field); 34 | break; 35 | case 'unique': 36 | self::unique($field); 37 | break; 38 | case 'json': 39 | self::json($field); 40 | break; 41 | } 42 | } 43 | } 44 | return new self(); 45 | } 46 | 47 | private static function required(string $field): void 48 | { 49 | if (!isset(self::$data[$field]) || empty(self::$data[$field])) { 50 | self::addError($field, 'The ' . $field . ' field is required'); 51 | } 52 | } 53 | 54 | private static function min(string $field, int $min): void 55 | { 56 | if (strlen(self::$data[$field]) < $min) { 57 | self::addError($field, 'The ' . $field . ' field must be at least ' . $min . ' characters'); 58 | } 59 | } 60 | 61 | private static function max(string $field, int $max): void 62 | { 63 | if (strlen(self::$data[$field]) > $max) { 64 | self::addError($field, 'The ' . $field . ' field must be at most ' . $max . ' characters'); 65 | } 66 | } 67 | 68 | private static function email(string $field): void 69 | { 70 | if (!filter_var(self::$data[$field], FILTER_VALIDATE_EMAIL)) { 71 | self::addError($field, 'The ' . $field . ' field must be a valid email'); 72 | } 73 | } 74 | 75 | private static function file(string $field): void 76 | { 77 | if (!isset($_FILES[$field]) || !is_uploaded_file($_FILES[$field]['tmp_name'])) { 78 | self::addError($field, 'The ' . $field . ' field must be a valid file'); 79 | return; 80 | } 81 | } 82 | 83 | private static function json(string $field): void 84 | { 85 | if (!is_array(json_decode(self::$data[$field], true))) { 86 | self::addError($field, 'The ' . $field . ' field must be a valid json'); 87 | } 88 | } 89 | 90 | private static function unique(string $field): void 91 | { 92 | $table = self::$fields[$field]['table']; 93 | $column = self::$fields[$field]['column']; 94 | $query = "SELECT * FROM {$table} WHERE {$column} = :{$column}"; 95 | $statement = (new Database())->connection->prepare($query); 96 | $statement->bindParam(':' . $column, self::$data[$field]); 97 | $statement->execute(); 98 | $result = $statement->fetch(PDO::FETCH_OBJ); 99 | if ($result) { 100 | self::addError($field, 'The ' . $field . ' field must be unique'); 101 | } 102 | } 103 | 104 | private static function addError(string $field, string $message): void 105 | { 106 | self::$errors[$field] = $message; 107 | } 108 | 109 | public static function fails(): bool 110 | { 111 | return count(self::$errors) > 0; 112 | } 113 | 114 | public function errors(): array 115 | { 116 | return self::$errors; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /App/Database/Migrations/2023_01_31_104005_create_table_users.php: -------------------------------------------------------------------------------- 1 | increment('id'); 15 | $table->string('name', 50); 16 | $table->string('username', 25); 17 | $table->string('email', 30); 18 | $table->string('password', 64); 19 | $table->integer('role_id'); 20 | $table->index('role_id'); 21 | $table->string('remember_token', 100)->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | public function down() 27 | { 28 | Schema::dropIfExists('users'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /App/Database/Migrations/2023_01_31_160221_create_table_roles.php: -------------------------------------------------------------------------------- 1 | increment('id'); 15 | $table->string('name', 50); 16 | $table->string('description', 100)->nullable(); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | public function down() 22 | { 23 | Schema::dropIfExists('roles'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /App/Database/Seeders/GlobalSeeder.php: -------------------------------------------------------------------------------- 1 | call(RoleSeeder::class); 12 | $this->call(UserSeeder::class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /App/Database/Seeders/RoleSeeder.php: -------------------------------------------------------------------------------- 1 | insert( 13 | [ 14 | [ 15 | 'name' => 'admin', 16 | 'description' => 'Administrator' 17 | ], 18 | [ 19 | 'name' => 'user', 20 | 'description' => 'User' 21 | ] 22 | ] 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /App/Database/Seeders/UserSeeder.php: -------------------------------------------------------------------------------- 1 | 'Administrator', 14 | 'username' => 'admin', 15 | 'email' => 'admin@admin.com', 16 | 'password' => password_hash('password', PASSWORD_DEFAULT), 17 | 'role_id' => 1, 18 | ]; 19 | DB::table('users')->insert($data); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /App/Helpers/TimeHelper.php: -------------------------------------------------------------------------------- 1 | role_id; 16 | 17 | if ($role == 1) { 18 | return $next(); 19 | } 20 | return $this->response(401, ['message' => 'Only Admin can access this route']); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /App/Middleware/AuthMiddleware.php: -------------------------------------------------------------------------------- 1 | response(401, ['message' => 'Unauthorized']); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /App/Models/User.php: -------------------------------------------------------------------------------- 1 | verifyToken($token, DotEnvKey::get('ACCESS_TOKEN_SECRET_KEY')); 23 | if (isset($user->data)) { 24 | return $user->data; 25 | } 26 | return false; 27 | } 28 | 29 | public static function check(): bool 30 | { 31 | $token = Request::bearerToken(); 32 | $user = self::verify($token); 33 | if ($user) { 34 | $user = DB::table('users')->where('id', $user->id)->first(); 35 | if ($user) { 36 | return true; 37 | } 38 | } 39 | return false; 40 | } 41 | 42 | public static function user() 43 | { 44 | $token = Request::bearerToken(); 45 | $user = self::verify($token); 46 | if ($user) { 47 | $user = DB::table('users')->where('id', $user->id)->first(); 48 | if ($user) { 49 | return $user; 50 | } 51 | } 52 | return false; 53 | } 54 | 55 | public static function attempt(array $credentials = [], $validator = null) : bool 56 | { 57 | // if validation fails 58 | if ($validator) { 59 | if ($validator->fails()) { 60 | self::response(401, [ 61 | 'message' => 'Invalid credentials', 62 | 'errors' => $validator->errors() 63 | ]); 64 | } 65 | } 66 | 67 | // validate form via validator 68 | if (self::validate($credentials)) { 69 | $user = DB::table('users'); 70 | $field = isset($credentials['email']) ? 'email' : 'username'; 71 | $user = $user->where($field, isset($credentials['email']) ? $credentials['email'] : $credentials['username']); 72 | $user = $user->first(); 73 | 74 | if ($user) { 75 | if (password_verify($credentials['password'], $user->password)) { 76 | $token = (new self)->generateToken([ 77 | 'data' => [ 78 | 'id' => $user->id, 79 | 'username' => $user->username, 80 | 'email' => $user->email, 81 | 'created_at' => $user->created_at, 82 | 'updated_at' => $user->updated_at, 83 | 'expires_at' => TimeHelper::setMinutes(30) 84 | ] 85 | ], DotEnvKey::get('ACCESS_TOKEN_SECRET_KEY')); 86 | self::response(200, [ 87 | 'message' => 'Login successful', 88 | 'token' => $token, 89 | 'expires_at' => date('Y-m-d H:i:s', TimeHelper::setMinutes(30)) 90 | ]); 91 | return true; 92 | } 93 | } 94 | } 95 | return false; 96 | } 97 | 98 | public static function register(array $credentials = []) 99 | { 100 | // validate form via validator 101 | if (self::validate($credentials)) { 102 | $user = DB::table('users'); 103 | $field = isset($credentials['email']) ? 'email' : 'username'; 104 | $user = $user->where($field, isset($credentials['email']) ? $credentials['email'] : $credentials['username']); 105 | $user = $user->first(); 106 | 107 | if (!$user) { 108 | $credentials['password'] = password_hash($credentials['password'], PASSWORD_DEFAULT); 109 | $insert = DB::table('users')->insert($credentials); 110 | $user = DB::table('users')->where('id', $insert)->first(); 111 | if ($user) { 112 | $token = (new self)->generateToken([ 113 | 'data' => [ 114 | 'id' => $user->id, 115 | 'username' => $user->username, 116 | 'email' => $user->email, 117 | 'created_at' => $user->created_at, 118 | 'updated_at' => $user->updated_at, 119 | 'expires_at' => TimeHelper::setMinutes(30) 120 | ] 121 | ], DotEnvKey::get('ACCESS_TOKEN_SECRET_KEY')); 122 | self::response(200, [ 123 | 'message' => 'Register successful', 124 | 'token' => $token, 125 | 'expires_at' => date('Y-m-d H:i:s', TimeHelper::setMinutes(30)) 126 | ]); 127 | return true; 128 | } 129 | } 130 | } 131 | return false; 132 | } 133 | 134 | 135 | public static function validate(array $credentials = []) 136 | { 137 | // foreach validate required each credentials 138 | $fields = []; 139 | 140 | foreach ($credentials as $key => $value) { 141 | 142 | // if key email use validate email 143 | if ($key == 'email') { 144 | $fieldset = [ 145 | 'required' => true, 146 | 'email' => true 147 | ]; 148 | $fields[] = [ 149 | $key => $fieldset 150 | ]; 151 | continue; 152 | } 153 | 154 | $fieldset = [ 155 | 'required' => true, 156 | ]; 157 | 158 | $fields[] = [ 159 | $key => $fieldset 160 | ]; 161 | } 162 | 163 | $fields = array_merge(...$fields); 164 | $validator = Validator::validate($fields, $credentials); 165 | 166 | if ($validator->fails()) { 167 | self::response(400, [ 168 | 'message' => 'Failed', 169 | 'errors' => $validator->errors() 170 | ]); 171 | return; 172 | } 173 | return true; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /App/Routes/Api.php: -------------------------------------------------------------------------------- 1 | group(function () { 11 | Router::get('/users', 'index'); 12 | Router::get('/users/{id}', 'show'); 13 | }); 14 | 15 | Router::controller(AuthController::class)->group(function () { 16 | Router::post('/auth/login', 'login'); 17 | Router::post('/auth/register', 'register'); 18 | }); 19 | 20 | 21 | Router::run(); 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023, STMIK Mardira Indonesia. 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 | 16 | The Software is provided "as is", without warranty of any kind, express or 17 | implied, including but not limited to the warranties of merchantability, 18 | fitness for a particular purpose and noninfringement. In no event shall the 19 | authors or copyright holders be liable for any claim, damages or other 20 | liability, whether in an action of contract, tort or otherwise, arising from, 21 | out of or in connection with the software or the use or other dealings in the 22 | Software. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Mardira Logo

4 | 5 | 6 | 7 | Mardira Framework is a PHP framework Model Controller Based for building web applications and APIs. It is designed to be simple, and fast. 8 | 9 | ![Total Downloads](https://img.shields.io/packagist/dt/mardira/mardira-framework?color=e&style=for-the-badge) 10 | ![Total Stars](https://img.shields.io/github/stars/Bootcamp-STMIK-Mardira-Indonesia/mardira-framework?color=e&style=for-the-badge) 11 | ![Total Forks](https://img.shields.io/github/forks/Bootcamp-STMIK-Mardira-Indonesia/mardira-framework?color=e&style=for-the-badge) 12 | ![Version](https://img.shields.io/packagist/v/mardira/mardira-framework?color=e&style=for-the-badge) 13 | ![License](https://img.shields.io/github/license/Bootcamp-STMIK-Mardira-Indonesia/mardira-framework?color=e&style=for-the-badge) 14 | 15 | ## Table of Contents 16 | 17 | - [Requirements](#requirements) 18 | - [Structure Folders](#structure-folders) 19 | - [Installation](#installation) 20 | - [Usage](#usage) 21 | - [Start Server](#start-server) 22 | - [Create .env](#create-env) 23 | - [Create Controller](#create-controller) 24 | - [Create Model](#create-model) 25 | - [Create Route](#create-route) 26 | - [Create Migration](#create-migration) 27 | - [Run Migration](#run-migration) 28 | - [Refresh Migration](#refresh-migration) 29 | - [Refresh Migration With Seed](#refresh-migration-with-seed) 30 | - [Create Seeder](#create-seeder) 31 | - [Run Seeder](#run-seeder) 32 | - [Run Seeder Specific](#run-seeder-specific) 33 | - [Create Authetication](#create-authetication) 34 | - [Refresh Authetication](#refresh-authetication) 35 | - [Update Framework Version](#update-framework-version) 36 | - [Controller](#controller) 37 | - [Model](#model) 38 | - [Migration](#migration) 39 | - [Seeder](#seeder) 40 | - [Middleware](#middleware) 41 | - [Route](#route) 42 | - [Route Group](#route-group) 43 | - [Query Builder](#query-builder) 44 | - [Select](#select) 45 | - [Where](#where) 46 | - [Or Where](#or-where) 47 | - [Where In](#where-in) 48 | - [Where Not In](#where-not-in) 49 | - [Where Null](#where-null) 50 | - [Where Not Null](#where-not-null) 51 | - [Order By](#order-by) 52 | - [Group By](#group-by) 53 | - [Join](#join) 54 | - [Insert](#insert) 55 | - [Update](#update) 56 | - [Delete](#delete) 57 | - [Count](#count) 58 | 59 | ## Requirements 60 | 61 | - PHP = 7.4 62 | - MySQL >= 5.7.8 63 | - Apache >= 2.4.41 64 | - Composer >= 2.0.9 65 | 66 | ## Structure Folders 67 | 68 | ```shell 69 | mardira-framework 70 | ├── App 71 | │ ├── Controllers 72 | │ │ ├── AuthController.php 73 | │ ├── Core 74 | │ │ ├── Commands 75 | │ ├── Database 76 | │ │ ├── Migrations 77 | │ │ │ ├── 2023_01_31_xxxxxx_create_table_users.php 78 | │ │ │ ├── 2023_01_31_xxxxxx_create_table_roles.php 79 | │ │ ├── Seeders 80 | │ │ │ ├── GlobalSeeder.php 81 | │ ├── Helpers 82 | │ ├── Middleware 83 | │ ├── Models 84 | │ ├── Packages 85 | │ ├── Routes 86 | │ │ ├── Api.php 87 | ``` 88 | 89 | ## Installation 90 | 91 | ### Setup 92 | 93 | > You can create a new project using composer 94 | 95 | ```shell 96 | composer create-project mardira/mardira-framework 97 | ``` 98 | 99 | > or you can clone this project 100 | 101 | 102 | 103 | ### Clone 104 | 105 | - Clone this repo to your local machine using `git clone 106 | 107 | ```shell 108 | git clone https://github.com/Bootcamp-STMIK-Mardira-Indonesia/mardira-framework.git 109 | ``` 110 | 111 | > Then, install the dependencies using composer 112 | 113 | ```shell 114 | composer install 115 | ``` 116 | 117 | > or 118 | 119 | ```shell 120 | composer update 121 | ``` 122 | 123 | ## Usage 124 | 125 | ### Start Server 126 | 127 | ```shell 128 | php mardira serve 129 | ``` 130 | 131 | > or 132 | 133 | ```shell 134 | php mardira serve --port= 135 | ``` 136 | 137 | ### Create .env 138 | 139 | > You can create .env file using command 140 | 141 | ```shell 142 | php mardira make:env 143 | ``` 144 | 145 | ### Create Controller 146 | 147 | ```shell 148 | php mardira make:controller ControllerName 149 | ``` 150 | 151 | ### Create Model 152 | 153 | ```shell 154 | php mardira make:model ModelName 155 | ``` 156 | 157 | ### Create Route 158 | 159 | ```shell 160 | php mardira make:route route_name --controller=ControllerName 161 | ``` 162 | 163 | ### Create Migration 164 | 165 | ```shell 166 | 167 | php mardira make:migration create_table_table_name 168 | ``` 169 | 170 | ### Run Migration 171 | 172 | > If database not exist, will automatically create database from .env 173 | 174 | ```shell 175 | php mardira migrate 176 | ``` 177 | 178 | ### Refresh Migration 179 | 180 | ```shell 181 | php mardira migrate:refresh 182 | ``` 183 | 184 | ### Refresh Migration With Seed 185 | 186 | ```shell 187 | php mardira migrate:refresh --seed 188 | ``` 189 | 190 | ### Create Seeder 191 | 192 | ```shell 193 | php mardira make:seeder SeederName 194 | ``` 195 | 196 | ### Run Seeder 197 | 198 | ```shell 199 | php mardira db:seed 200 | ``` 201 | 202 | ### Run Seeder Specific 203 | 204 | ```shell 205 | php mardira db:seed --class=SeederName 206 | ``` 207 | 208 | ### Create Authetication 209 | 210 | ```shell 211 | php mardira make:auth 212 | ``` 213 | 214 | ### Refresh Authetication 215 | 216 | ```shell 217 | php mardira make:auth --refresh 218 | ``` 219 | 220 | ### Update Framework Version 221 | 222 | ```shell 223 | php mardira update 224 | ``` 225 | 226 | ### Controller 227 | 228 | > Create controller use `php mardira make:controller ControllerName`, here is example controller 229 | 230 | ```php 231 | response(200,[ 242 | 'message' => 'Hello World' 243 | ]); 244 | } 245 | } 246 | ``` 247 | 248 | > to use controller, you can add route in `App/Routes/Api.php` 249 | 250 | ```php 251 | You can use response in controller 262 | 263 | ```php 264 | $this->response(200,[ 265 | 'message' => 'Hello World' 266 | ]); 267 | 268 | ``` 269 | 270 | > return json expected 271 | 272 | ```json 273 | { 274 | "message": "Hello World" 275 | } 276 | ``` 277 | 278 | > another response example 409 279 | 280 | ```php 281 | $this->response->json(409,[ 282 | 'message' => 'Conflict' 283 | ]); 284 | ``` 285 | 286 | ### Model 287 | 288 | > Create model use `php mardira make:model ModelName`, here is example model 289 | 290 | ```php 291 | to use model, you can add model in `App/Controllers/ControllerName.php` 305 | 306 | ```php 307 | response(200,[ 321 | 'message' => 'Hello World', 322 | 'data' => $user 323 | ]); 324 | } 325 | } 326 | ``` 327 | 328 | ### Migration 329 | 330 | > Create migration use `php mardira make:migration create_table_table_name`, here is example migration 331 | 332 | ```php 333 | schema->create('users', function ($table) { 344 | $table->increment('id'); 345 | $table->string('name', 50); 346 | $table->string('email',50)->unique(); 347 | $table->string('password', 64); 348 | $table->timestamps(); 349 | }); 350 | } 351 | 352 | public function down() 353 | { 354 | $this->schema->dropIfExists('users'); 355 | } 356 | } 357 | ``` 358 | 359 | ### Seeder 360 | 361 | > Create seeder use `php mardira make:seeder SeederName`, here is example seeder 362 | 363 | ```php 364 | 'Administrator', 378 | 'username' => 'admin', 379 | 'email' => 'admin@admin.com', 380 | 'password' => password_hash('password', PASSWORD_DEFAULT), 381 | 'role_id' => 1, 382 | ], 383 | [ 384 | 'name' => 'User', 385 | 'username' => 'user', 386 | 'email' => 'user@user.com', 387 | 'password' => password_hash('password', PASSWORD_DEFAULT), 388 | 'role_id' => 2, 389 | ] 390 | ]; 391 | DB::table('users')->insert($data); 392 | } 393 | } 394 | 395 | ``` 396 | 397 | ### Middleware 398 | 399 | > Create middleware use `php mardira make:middleware MiddlewareName`, here is example middleware 400 | 401 | ```php 402 | response(401, ['message' => 'Unauthorized']); 417 | } 418 | } 419 | ``` 420 | 421 | > to use middleware, you can add middleware in route 422 | 423 | ```php 424 | 425 | Router::get('/schedules', [ScheduleController::class, 'index'], [AuthMiddleware::class]); 426 | 427 | ``` 428 | 429 | ### Routing 430 | 431 | > You can add route in `App/Routes/Api.php` 432 | 433 | ```php 434 | 435 | You can add route group in `App/Routes/Api.php` 446 | 447 | ```php 448 | 449 | group(function () { 455 | Router::post('/products/store', 'store'); 456 | }); 457 | 458 | ``` 459 | 460 | ### Query Builder 461 | 462 | 463 | 464 | 465 | 466 | #### 467 | 468 | ```php 469 | 470 | use App\Core\QueryBuilder as DB; 471 | 472 | ``` 473 | 474 | #### Select 475 | 476 | ```php 477 | DB::table('users')->select('name', 'email')->get(); 478 | ``` 479 | 480 | #### Where 481 | 482 | ```php 483 | 484 | // equal 485 | DB::table('users')->where('id', 1)->get(); 486 | 487 | DB::table('users')->where('id', 1, '>')->get(); 488 | 489 | DB::table('users')->where('id', 1, '<')->get(); 490 | 491 | DB::table('users')->where('id', 1, '>=')->get(); 492 | 493 | DB::table('users')->where('id', 1, '<=')->get(); 494 | 495 | DB::table('users')->where('id', 1, '!=')->get(); 496 | 497 | DB::table('users')->where('id', 1, '<>')->get(); 498 | 499 | // like 500 | 501 | DB::table('users')->where('name', 'admin', 'like')->get(); 502 | 503 | DB::table('users')->where('name', 'admin', 'not like')->get(); 504 | 505 | ``` 506 | 507 | #### Or Where 508 | 509 | ```php 510 | 511 | 512 | DB::table('users')->orWhere('id', 1)->get(); 513 | 514 | DB::table('users')->orWhere('id', 1, '>')->get(); 515 | 516 | DB::table('users')->orWhere('id', 1, '<')->get(); 517 | 518 | DB::table('users')->orWhere('id', 1, '>=')->get(); 519 | 520 | DB::table('users')->orWhere('id', 1, '<=')->get(); 521 | 522 | DB::table('users')->orWhere('id', 1, '!=')->get(); 523 | 524 | DB::table('users')->orWhere('id', 1, '<>')->get(); 525 | 526 | ``` 527 | 528 | #### Where In 529 | 530 | ```php 531 | 532 | DB::table('users')->whereIn('id', [1,2,3])->get(); 533 | 534 | DB::table('users')->whereNotIn('id', [1,2,3])->get(); 535 | 536 | ``` 537 | 538 | #### Where Not In 539 | 540 | ```php 541 | 542 | DB::table('users')->whereNotIn('id', [1,2,3])->get(); 543 | 544 | ``` 545 | 546 | #### Where Null 547 | 548 | ```php 549 | 550 | DB::table('users')->whereNull('id')->get(); 551 | ``` 552 | 553 | #### Where Not Null 554 | 555 | ```php 556 | DB::table('users')->whereNotNull('id')->get(); 557 | ``` 558 | 559 | #### Order By 560 | 561 | ```php 562 | 563 | DB::table('users')->orderBy('id', 'desc')->get(); 564 | 565 | DB::table('users')->orderBy('id', 'asc')->get(); 566 | 567 | ``` 568 | 569 | #### Join Table 570 | 571 | ```php 572 | 573 | DB::table('users') 574 | ->join('roles', 'users.role_id', '=', 'roles.id') 575 | ->select('users.*', 'roles.name as role_name') 576 | ->get(); 577 | 578 | ``` 579 | 580 | #### Group By 581 | 582 | ```php 583 | 584 | DB::table('users') 585 | ->groupBy('role_id') 586 | ->get(); 587 | 588 | ``` 589 | 590 | #### Insert 591 | 592 | ```php 593 | 594 | DB::table('users')->insert([ 595 | 'name' => 'user', 596 | 'email' => 'user@user.com', 597 | 'password' => password_hash('password', PASSWORD_DEFAULT), 598 | ]); 599 | 600 | ``` 601 | 602 | #### Update 603 | 604 | ```php 605 | 606 | DB::table('users')->where('id', 1)->update([ 607 | 'name' => 'user', 608 | 'email' => 'user@gmail.com', 609 | ]); 610 | 611 | ``` 612 | 613 | #### Delete 614 | 615 | ```php 616 | 617 | DB::table('users')->where('id', 1)->delete(); 618 | 619 | ``` 620 | 621 | #### Count 622 | 623 | ```php 624 | 625 | DB::table('users')->count(); 626 | 627 | ``` 628 | 629 | ## Support 630 | 631 | Reach out to me at one of the following places! 632 | 633 | - Website at `demostmikmi.com` 634 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mardira/mardira-framework", 3 | "type": "framework", 4 | "license": "MIT", 5 | "keywords": [ 6 | "framework", 7 | "mardira", 8 | "bootcamp" 9 | ], 10 | "description": "Mardira Framework", 11 | "authors": [ 12 | { 13 | "name": "xietsunzao", 14 | "email": "jefri@stmik-mi.ac.id" 15 | } 16 | ], 17 | "require": { 18 | "php": "^7.4", 19 | "vlucas/phpdotenv": "^5.5", 20 | "firebase/php-jwt": "^6.3", 21 | "symfony/console": "2.6.7", 22 | "doctrine/inflector": "^2.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "App\\": "App/" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mardira: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 3 |