├── dev ├── app │ ├── Models │ │ └── .gitkeep │ ├── Filters │ │ └── .gitkeep │ ├── Helpers │ │ └── .gitkeep │ ├── Language │ │ ├── .gitkeep │ │ └── en │ │ │ └── Validation.php │ ├── Libraries │ │ └── .gitkeep │ ├── ThirdParty │ │ └── .gitkeep │ ├── Database │ │ ├── Seeds │ │ │ └── .gitkeep │ │ └── Migrations │ │ │ └── .gitkeep │ ├── .htaccess │ ├── Views │ │ └── errors │ │ │ ├── cli │ │ │ ├── error_404.php │ │ │ ├── production.php │ │ │ └── error_exception.php │ │ │ └── html │ │ │ ├── production.php │ │ │ ├── error_404.php │ │ │ ├── debug.js │ │ │ └── debug.css │ ├── index.html │ ├── Config │ │ ├── ForeignCharacters.php │ │ ├── CURLRequest.php │ │ ├── Images.php │ │ ├── Boot │ │ │ ├── production.php │ │ │ ├── testing.php │ │ │ └── development.php │ │ ├── Honeypot.php │ │ ├── Publisher.php │ │ ├── Feature.php │ │ ├── Services.php │ │ ├── Validation.php │ │ ├── Pager.php │ │ ├── View.php │ │ ├── Filters.php │ │ ├── Events.php │ │ ├── Kint.php │ │ ├── Modules.php │ │ ├── Migrations.php │ │ ├── Routes.php │ │ ├── Generators.php │ │ ├── Exceptions.php │ │ ├── Encryption.php │ │ ├── Database.php │ │ ├── Format.php │ │ ├── DocTypes.php │ │ ├── Paths.php │ │ ├── Autoload.php │ │ ├── Toolbar.php │ │ ├── Email.php │ │ ├── Security.php │ │ ├── Constants.php │ │ ├── Cookie.php │ │ ├── ContentSecurityPolicy.php │ │ ├── Logger.php │ │ └── Cache.php │ ├── Controllers │ │ ├── Home.php │ │ ├── SessionTest.php │ │ ├── BaseController.php │ │ ├── FileUploadTest.php │ │ ├── TestRest.php │ │ └── BasicTest.php │ └── Common.php ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── .htaccess │ └── index.php ├── tests │ ├── codeigniter4Roadrunner │ │ ├── httpTest │ │ │ ├── testFiles │ │ │ │ ├── upload1.text │ │ │ │ └── upload2.text │ │ │ ├── SessionTest.php │ │ │ ├── FileUploadTest.php │ │ │ ├── RestTest.php │ │ │ └── BasicTest.php │ │ ├── UploadedFileBridgeTest.php │ │ └── RequestBridgeTest.php │ ├── bootstrap.php │ ├── _support │ │ ├── Libraries │ │ │ └── ConfigReader.php │ │ ├── Models │ │ │ └── ExampleModel.php │ │ └── Database │ │ │ ├── Migrations │ │ │ └── 2020-02-22-222222_example_migration.php │ │ │ └── Seeds │ │ │ └── ExampleSeeder.php │ └── README.md ├── writable │ ├── .htaccess │ ├── cache │ │ └── index.html │ ├── logs │ │ └── index.html │ ├── session │ │ └── index.html │ └── uploads │ │ └── index.html ├── LICENSE ├── composer.json ├── README_ZH-TW.md ├── phpunit.xml.dist ├── .gitignore ├── README.md ├── spark ├── builds └── env ├── .gitignore ├── src ├── Commands │ ├── file │ │ ├── .rr.yaml │ │ └── psr-worker.php │ └── InitLibrary.php ├── HandleDBConnection.php ├── UriBridge.php ├── RequestHandler.php ├── UploadedFileBridge.php ├── ResponseBridge.php └── UploadedFile.php ├── .php-cs-fixer.dist.php ├── composer.json ├── .github └── workflows │ └── test-phpunit.yml ├── rector.php └── README_zh-TW.md /dev/app/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/app/Filters/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/app/Helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/app/Language/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/app/Libraries/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/app/ThirdParty/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/app/Database/Seeds/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/app/Database/Migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /dev/tests/codeigniter4Roadrunner/httpTest/testFiles/upload1.text: -------------------------------------------------------------------------------- 1 | upload1 -------------------------------------------------------------------------------- /dev/tests/codeigniter4Roadrunner/httpTest/testFiles/upload2.text: -------------------------------------------------------------------------------- 1 | upload2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | vendor/ 3 | .idea/ 4 | composer.lock 5 | .php-cs-fixer.cache 6 | -------------------------------------------------------------------------------- /dev/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDPM-lab/Codeigniter4-Roadrunner/HEAD/dev/public/favicon.ico -------------------------------------------------------------------------------- /dev/app/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Require all denied 3 | 4 | 5 | Deny from all 6 | 7 | -------------------------------------------------------------------------------- /dev/app/Language/en/Validation.php: -------------------------------------------------------------------------------- 1 | 2 | Require all denied 3 | 4 | 5 | Deny from all 6 | 7 | -------------------------------------------------------------------------------- /dev/app/Views/errors/cli/production.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dev/writable/cache/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dev/writable/logs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dev/writable/session/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dev/writable/uploads/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dev/app/Config/ForeignCharacters.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Whoops! 8 | 9 | 12 | 13 | 14 | 15 |
16 | 17 |

Whoops!

18 | 19 |

We seem to have hit a snag. Please try again later...

20 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Commands/file/.rr.yaml: -------------------------------------------------------------------------------- 1 | version: "2.7" 2 | 3 | rpc: 4 | listen: tcp://127.0.0.1:6001 5 | 6 | server: 7 | command: "php psr-worker.php" 8 | # env: 9 | # XDEBUG_SESSION: 1 10 | 11 | http: 12 | address: "0.0.0.0:8080" 13 | static: 14 | dir: "./public" 15 | forbid: [".htaccess", ".php"] 16 | pool: 17 | num_workers: 1 18 | # max_jobs: 64 19 | # debug: true 20 | 21 | # reload: 22 | # interval: 1s 23 | # patterns: [ ".php" ] 24 | # services: 25 | # http: 26 | # recursive: true 27 | # ignore: [ "vendor" ] 28 | # patterns: [ ".php", ".go", ".md" ] 29 | # dirs: [ "." ] -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | files() 9 | ->in([ 10 | __DIR__ . '/src/', 11 | // __DIR__ . '/tests/', 12 | __DIR__ . '/dev/app/', 13 | __DIR__ . '/dev/tests/', 14 | ]) 15 | ->exclude('build') 16 | ->append([__FILE__]); 17 | 18 | $overrides = []; 19 | 20 | $options = [ 21 | 'finder' => $finder, 22 | 'cacheFile' => 'build/.php-cs-fixer.cache', 23 | ]; 24 | 25 | return Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects(); 26 | -------------------------------------------------------------------------------- /dev/app/Config/CURLRequest.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public $handlers = [ 32 | 'gd' => GDHandler::class, 33 | 'imagick' => ImageMagickHandler::class, 34 | ]; 35 | } 36 | -------------------------------------------------------------------------------- /dev/app/Config/Boot/production.php: -------------------------------------------------------------------------------- 1 | {label}'; 36 | 37 | /** 38 | * Honeypot container 39 | * 40 | * @var string 41 | */ 42 | public $container = '
{template}
'; 43 | } 44 | -------------------------------------------------------------------------------- /dev/app/Config/Publisher.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | public $restrictions = [ 25 | ROOTPATH => '*', 26 | FCPATH => '#\.(?css|js|map|htm?|xml|json|webmanifest|tff|eot|woff?|gif|jpe?g|tiff?|png|webp|bmp|ico|svg)$#i', 27 | ]; 28 | } 29 | -------------------------------------------------------------------------------- /src/HandleDBConnection.php: -------------------------------------------------------------------------------- 1 | close(); 16 | } 17 | } 18 | 19 | public static function reconnect() 20 | { 21 | $dbInstances = Database::getConnections(); 22 | 23 | foreach ($dbInstances as $connection) { 24 | if ($connection->DBDriver === 'MySQLi') { 25 | try { 26 | $connection->mysqli->ping(); 27 | } catch (Throwable $th) { 28 | $connection->reconnect(); 29 | } 30 | } else { 31 | $connection->reconnect(); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /dev/app/Config/Feature.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | public $templates = [ 36 | 'list' => 'CodeIgniter\Validation\Views\list', 37 | 'single' => 'CodeIgniter\Validation\Views\single', 38 | ]; 39 | 40 | //-------------------------------------------------------------------- 41 | // Rules 42 | //-------------------------------------------------------------------- 43 | } 44 | -------------------------------------------------------------------------------- /src/UriBridge.php: -------------------------------------------------------------------------------- 1 | getPath(); 24 | 25 | if ($rPath === '/') { 26 | Services::request()->setPath($rPath); 27 | 28 | return; 29 | } 30 | 31 | $pathArr = explode('/', $rPath); 32 | 33 | if ($pathArr[1] === 'index.php') { 34 | unset($pathArr[1]); 35 | array_values($pathArr); 36 | } 37 | if ($pathArr[count($pathArr) - 1] === '') { 38 | unset($pathArr[count($pathArr) - 1]); 39 | array_values($pathArr); 40 | } 41 | 42 | $path = '/' . implode('/', $pathArr); 43 | Services::request()->setPath($path); 44 | } 45 | 46 | protected static function transferQuery() 47 | { 48 | Services::uri()->setQuery(self::$_rURI->getQuery()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdpmlab/codeigniter4-roadrunner", 3 | "description": "Make Codeigniter4 work on Roadrunner Server.", 4 | "keywords": [ 5 | "codeigniter", 6 | "codeigniter4", 7 | "Roadrunner" 8 | ], 9 | "type": "library", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "MonkenWu", 14 | "homepage": "https://github.com/monkenWu" 15 | }, 16 | { 17 | "name": "SDPM-lab", 18 | "homepage": "https://github.com/SDPM-lab" 19 | } 20 | ], 21 | "minimum-stability": "dev", 22 | "prefer-stable": true, 23 | "require": { 24 | "php" : "^7.4 || ^8.0", 25 | "codeigniter4/framework": "^4.2.0", 26 | "spiral/roadrunner": "^2", 27 | "spiral/dumper": "^2.8", 28 | "laminas/laminas-diactoros": "^2.8", 29 | "nyholm/psr7": "^1.4" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "SDPMlab\\Ci4Roadrunner\\": "src/" 34 | } 35 | }, 36 | "require-dev": { 37 | "codeigniter4/devkit": "^1.0", 38 | "rector/rector": "0.12.16" 39 | }, 40 | "config": { 41 | "allow-plugins": { 42 | "phpstan/extension-installer": true, 43 | "composer/package-versions-deprecated": true 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dev/app/Controllers/SessionTest.php: -------------------------------------------------------------------------------- 1 | _session = service('session'); 26 | } 27 | 28 | /** 29 | * Created Session 30 | * Success return 201 code 31 | * 32 | * @return 33 | */ 34 | public function createdSession() 35 | { 36 | if ($data = $this->request->getPost('text')) { 37 | $this->_session->set('text', $data); 38 | 39 | return $this->respondCreated([]); 40 | } 41 | 42 | return $this->failServerError('Post Data Note Found', 400); 43 | } 44 | 45 | /** 46 | * Get session text 47 | * Not found return 404 code 48 | */ 49 | public function getSessionText() 50 | { 51 | if ($text = $this->_session->get('text')) { 52 | return $this->respond(['text' => $text], 200); 53 | } 54 | 55 | return $this->failNotFound(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /dev/app/Config/Pager.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public $templates = [ 24 | 'default_full' => 'CodeIgniter\Pager\Views\default_full', 25 | 'default_simple' => 'CodeIgniter\Pager\Views\default_simple', 26 | 'default_head' => 'CodeIgniter\Pager\Views\default_head', 27 | ]; 28 | 29 | /** 30 | * -------------------------------------------------------------------------- 31 | * Items Per Page 32 | * -------------------------------------------------------------------------- 33 | * 34 | * The default number of results shown in a single page. 35 | * 36 | * @var int 37 | */ 38 | public $perPage = 20; 39 | } 40 | -------------------------------------------------------------------------------- /dev/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php: -------------------------------------------------------------------------------- 1 | forge->addField('id'); 14 | $this->forge->addField([ 15 | 'name' => ['type' => 'varchar', 'constraint' => 31], 16 | 'uid' => ['type' => 'varchar', 'constraint' => 31], 17 | 'class' => ['type' => 'varchar', 'constraint' => 63], 18 | 'icon' => ['type' => 'varchar', 'constraint' => 31], 19 | 'summary' => ['type' => 'varchar', 'constraint' => 255], 20 | 'created_at' => ['type' => 'datetime', 'null' => true], 21 | 'updated_at' => ['type' => 'datetime', 'null' => true], 22 | 'deleted_at' => ['type' => 'datetime', 'null' => true], 23 | ]); 24 | 25 | $this->forge->addKey('name'); 26 | $this->forge->addKey('uid'); 27 | $this->forge->addKey(['deleted_at', 'id']); 28 | $this->forge->addKey('created_at'); 29 | 30 | $this->forge->createTable('factories'); 31 | } 32 | 33 | public function down() 34 | { 35 | $this->forge->dropTable('factories'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /dev/app/Config/Boot/testing.php: -------------------------------------------------------------------------------- 1 | 'Test Factory', 14 | 'uid' => 'test001', 15 | 'class' => 'Factories\Tests\NewFactory', 16 | 'icon' => 'fas fa-puzzle-piece', 17 | 'summary' => 'Longer sample text for testing', 18 | ], 19 | [ 20 | 'name' => 'Widget Factory', 21 | 'uid' => 'widget', 22 | 'class' => 'Factories\Tests\WidgetPlant', 23 | 'icon' => 'fas fa-puzzle-piece', 24 | 'summary' => 'Create widgets in your factory', 25 | ], 26 | [ 27 | 'name' => 'Evil Factory', 28 | 'uid' => 'evil-maker', 29 | 'class' => 'Factories\Evil\MyFactory', 30 | 'icon' => 'fas fa-book-dead', 31 | 'summary' => 'Abandon all hope, ye who enter here', 32 | ], 33 | ]; 34 | 35 | $builder = $this->db->table('factories'); 36 | 37 | foreach ($factories as $factory) { 38 | $builder->insert($factory); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dev/app/Config/Boot/development.php: -------------------------------------------------------------------------------- 1 | 'http://localhost:8080/', 17 | ], null, null, false); 18 | $checkText = uniqid(); 19 | $response = $client->post('/sessionTest/createdSession', [ 20 | 'form_params' => [ 21 | 'text' => $checkText, 22 | ], 23 | 'http_errors' => false, 24 | ]); 25 | $this->assertSame(201, $response->getStatusCode()); 26 | //check session 27 | $setCookie = $response->getHeaders()['Set-Cookie']->getValue(); 28 | $session = explode('=', explode(';', $setCookie)[0]); 29 | $response = $client->get('/sessionTest/getSessionText', [ 30 | 'headers' => [ 31 | 'Cookie' => "{$session[0]}={$session[1]}", 32 | ], 33 | ]); 34 | $this->assertSame(200, $response->getStatusCode()); 35 | $getServerCheckText = json_decode($response->getBody(), true)['text']; 36 | $this->assertSame($checkText, $getServerCheckText); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /dev/app/Config/View.php: -------------------------------------------------------------------------------- 1 | CSRF::class, 20 | 'toolbar' => DebugToolbar::class, 21 | 'honeypot' => Honeypot::class, 22 | ]; 23 | 24 | /** 25 | * List of filter aliases that are always 26 | * applied before and after every request. 27 | * 28 | * @var array 29 | */ 30 | public $globals = [ 31 | 'before' => [ 32 | // 'honeypot', 33 | // 'csrf', 34 | ], 35 | 'after' => [ 36 | 'toolbar', 37 | // 'honeypot', 38 | ], 39 | ]; 40 | 41 | /** 42 | * List of filter aliases that works on a 43 | * particular HTTP method (GET, POST, etc.). 44 | * 45 | * Example: 46 | * 'post' => ['csrf', 'throttle'] 47 | * 48 | * @var array 49 | */ 50 | public $methods = []; 51 | 52 | /** 53 | * List of filter aliases that should run on any 54 | * before or after URI patterns. 55 | * 56 | * Example: 57 | * 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']] 58 | * 59 | * @var array 60 | */ 61 | public $filters = []; 62 | } 63 | -------------------------------------------------------------------------------- /dev/app/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | session = \Config\Services::session(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /dev/app/Config/Events.php: -------------------------------------------------------------------------------- 1 | 0) { 32 | ob_end_flush(); 33 | } 34 | 35 | ob_start(static fn ($buffer) => $buffer); 36 | } 37 | 38 | /* 39 | * -------------------------------------------------------------------- 40 | * Debug Toolbar Listeners. 41 | * -------------------------------------------------------------------- 42 | * If you delete, they will no longer be collected. 43 | */ 44 | if (CI_DEBUG && ! is_cli()) { 45 | Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'); 46 | Services::toolbar()->respond(); 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /dev/app/Config/Kint.php: -------------------------------------------------------------------------------- 1 | getClientFilename()); 27 | $fileEx = array_pop($fileNameArr); 28 | $newFileName = uniqid(mt_rand()) . '.' . $fileEx; 29 | $newFilePath = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . $newFileName; 30 | $file->moveTo($newFilePath); 31 | $data[$file->getClientFilename()] = md5_file($newFilePath); 32 | } 33 | 34 | return $this->respondCreated($data); 35 | } 36 | 37 | /** 38 | * form-data multiple upload 39 | */ 40 | public function fileMultipleUpload() 41 | { 42 | $files = UploadedFileBridge::getPsr7UploadedFiles()['data']; 43 | $data = []; 44 | 45 | foreach ($files as $file) { 46 | $fileNameArr = explode('.', $file->getClientFilename()); 47 | $fileEx = array_pop($fileNameArr); 48 | $newFileName = uniqid(mt_rand()) . '.' . $fileEx; 49 | $newFilePath = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . $newFileName; 50 | $file->moveTo($newFilePath); 51 | $data[$file->getClientFilename()] = md5_file($newFilePath); 52 | } 53 | 54 | return $this->respondCreated($data); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /dev/app/Config/Modules.php: -------------------------------------------------------------------------------- 1 | 11 | Options +FollowSymlinks 12 | RewriteEngine On 13 | 14 | # If you installed CodeIgniter in a subfolder, you will need to 15 | # change the following line to match the subfolder you need. 16 | # http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase 17 | # RewriteBase / 18 | 19 | # Redirect Trailing Slashes... 20 | RewriteCond %{REQUEST_FILENAME} !-d 21 | RewriteCond %{REQUEST_URI} (.+)/$ 22 | RewriteRule ^ %1 [L,R=301] 23 | 24 | # Rewrite "www.example.com -> example.com" 25 | RewriteCond %{HTTPS} !=on 26 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 27 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 28 | 29 | # Checks to see if the user is attempting to access a valid file, 30 | # such as an image or css document, if this isn't true it sends the 31 | # request to the front controller, index.php 32 | RewriteCond %{REQUEST_FILENAME} !-f 33 | RewriteCond %{REQUEST_FILENAME} !-d 34 | RewriteRule ^([\s\S]*)$ index.php/$1 [L,NC,QSA] 35 | 36 | # Ensure Authorization header is passed along 37 | RewriteCond %{HTTP:Authorization} . 38 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 39 | 40 | 41 | 42 | # If we don't have mod_rewrite installed, all 404's 43 | # can be sent to index.php, and everything works as normal. 44 | ErrorDocument 404 index.php 45 | 46 | 47 | # Disable server signature start 48 | ServerSignature Off 49 | # Disable server signature end 50 | -------------------------------------------------------------------------------- /dev/app/Controllers/TestRest.php: -------------------------------------------------------------------------------- 1 | respond([ 14 | 'status' => true, 15 | 'msg' => 'index method successful.', 16 | ]); 17 | } 18 | 19 | public function show($id = null) 20 | { 21 | return $this->respond([ 22 | 'status' => true, 23 | 'id' => $id, 24 | 'msg' => 'show method successful.', 25 | ]); 26 | } 27 | 28 | public function create() 29 | { 30 | $data = $this->request->getJSON(true); 31 | if ($data === null) { 32 | return $this->failValidationError('data not found', 400); 33 | } 34 | 35 | return $this->respondCreated([ 36 | 'status' => true, 37 | 'data' => $data, 38 | 'msg' => 'create method successful.', 39 | ]); 40 | } 41 | 42 | public function update($id = null) 43 | { 44 | $data = $this->request->getJSON(true); 45 | 46 | return $this->respond([ 47 | 'status' => true, 48 | 'id' => $id, 49 | 'data' => $data, 50 | 'msg' => 'update method successful.', 51 | ]); 52 | } 53 | 54 | public function new() 55 | { 56 | return 'newView'; 57 | } 58 | 59 | public function edit($id = null) 60 | { 61 | return $id . 'editView'; 62 | } 63 | 64 | public function delete($id = null) 65 | { 66 | return $this->respondDeleted([ 67 | 'status' => true, 68 | 'id' => $id, 69 | 'msg' => 'delede method successful.', 70 | ]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /dev/app/Views/errors/html/error_404.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Page Not Found 6 | 7 | 70 | 71 | 72 |
73 |

404 - File Not Found

74 | 75 |

76 | 77 | 78 | 79 | Sorry! Cannot seem to find the page you were looking for. 80 | 81 |

82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /dev/app/Config/Migrations.php: -------------------------------------------------------------------------------- 1 | php spark migrate:create 46 | * 47 | * Typical formats: 48 | * - YmdHis_ 49 | * - Y-m-d-His_ 50 | * - Y_m_d_His_ 51 | * 52 | * @var string 53 | */ 54 | public $timestampFormat = 'Y-m-d-His_'; 55 | } 56 | -------------------------------------------------------------------------------- /dev/app/Config/Routes.php: -------------------------------------------------------------------------------- 1 | setDefaultNamespace('App\Controllers'); 20 | $routes->setDefaultController('Home'); 21 | $routes->setDefaultMethod('index'); 22 | $routes->setTranslateURIDashes(false); 23 | $routes->set404Override(); 24 | $routes->setAutoRoute(true); 25 | 26 | /* 27 | * -------------------------------------------------------------------- 28 | * Route Definitions 29 | * -------------------------------------------------------------------- 30 | */ 31 | 32 | // We get a performance increase by specifying the default 33 | // route since we don't have to scan directories. 34 | $routes->get('/', 'Home::index'); 35 | 36 | $routes->resource('testRest', [ 37 | 'controller' => '\App\Controllers\TestRest', 38 | ]); 39 | 40 | /* 41 | * -------------------------------------------------------------------- 42 | * Additional Routing 43 | * -------------------------------------------------------------------- 44 | * 45 | * There will often be times that you need additional routing and you 46 | * need it to be able to override any defaults in this file. Environment 47 | * based routes is one such time. require() additional route files here 48 | * to make that happen. 49 | * 50 | * You will have access to the $routes object within that file without 51 | * needing to reload it. 52 | */ 53 | if (file_exists(APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php')) { 54 | require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php'; 55 | } 56 | -------------------------------------------------------------------------------- /dev/app/Config/Generators.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | public $views = [ 29 | 'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php', 30 | 'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php', 31 | 'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php', 32 | 'make:entity' => 'CodeIgniter\Commands\Generators\Views\entity.tpl.php', 33 | 'make:filter' => 'CodeIgniter\Commands\Generators\Views\filter.tpl.php', 34 | 'make:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php', 35 | 'make:model' => 'CodeIgniter\Commands\Generators\Views\model.tpl.php', 36 | 'make:seeder' => 'CodeIgniter\Commands\Generators\Views\seeder.tpl.php', 37 | 'make:validation' => 'CodeIgniter\Commands\Generators\Views\validation.tpl.php', 38 | 'session:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php', 39 | ]; 40 | } 41 | -------------------------------------------------------------------------------- /dev/public/index.php: -------------------------------------------------------------------------------- 1 | systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; 27 | 28 | // Load environment settings from .env files into $_SERVER and $_ENV 29 | require_once SYSTEMPATH . 'Config/DotEnv.php'; 30 | (new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); 31 | 32 | /* 33 | * --------------------------------------------------------------- 34 | * GRAB OUR CODEIGNITER INSTANCE 35 | * --------------------------------------------------------------- 36 | * 37 | * The CodeIgniter class contains the core functionality to make 38 | * the application run, and does all of the dirty work to get 39 | * the pieces all working together. 40 | */ 41 | 42 | $app = Config\Services::codeigniter(); 43 | $app->initialize(); 44 | $context = is_cli() ? 'php-cli' : 'web'; 45 | $app->setContext($context); 46 | 47 | /* 48 | *--------------------------------------------------------------- 49 | * LAUNCH THE APPLICATION 50 | *--------------------------------------------------------------- 51 | * Now that everything is setup, it's time to actually fire 52 | * up the engines and make this app do its thang. 53 | */ 54 | 55 | $app->run(); 56 | -------------------------------------------------------------------------------- /dev/app/Controllers/BasicTest.php: -------------------------------------------------------------------------------- 1 | request->getGet('texts')[0]; 38 | $text2 = $this->request->getGet('texts')[1]; 39 | $text3 = $this->request->getGet('text3'); 40 | 41 | return md5($text1 . $text2 . $text3); 42 | } 43 | 44 | /** 45 | * test x-www-form-urlencoded($_POST) 46 | */ 47 | public function formparams() 48 | { 49 | $text1 = $this->request->getPost('texts')[0]; 50 | $text2 = $this->request->getPost('texts')[1]; 51 | $text3 = $this->request->getPost('text3'); 52 | 53 | return md5($text1 . $text2 . $text3); 54 | } 55 | 56 | /** 57 | * x-www-form-urlencoded and query mix test 58 | */ 59 | public function formparamsandquery() 60 | { 61 | $text1 = $this->request->getGet('text1'); 62 | $text2 = $this->request->getPost('text2'); 63 | 64 | return md5($text1 . $text2); 65 | } 66 | 67 | /** 68 | * read header 69 | */ 70 | public function readHeader() 71 | { 72 | $token = $this->request->getHeader('X-Auth-Token')->getValueLine(); 73 | 74 | return $this->respond(['X-Auth-Token' => $token]); 75 | } 76 | 77 | /** 78 | * send header 79 | */ 80 | public function sendHeader() 81 | { 82 | $this->response->setHeader('X-Set-Auth-Token', uniqid()); 83 | 84 | return $this->respond(['status' => true]); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /dev/app/Config/Exceptions.php: -------------------------------------------------------------------------------- 1 | [ 25 | 'name' => 'someFile.txt', 26 | 'type' => 'text/plain', 27 | 'size' => '124', 28 | 'tmp_name' => '/tmp/myTempFile.txt', 29 | 'error' => 0, 30 | ], 31 | ]; 32 | 33 | $files = UploadedFileBridge::getPsr7UploadedFiles(); 34 | 35 | $this->assertCount(1, $files); 36 | 37 | $file = array_shift($files); 38 | $this->assertInstanceOf(UploadedFile::class, $file); 39 | 40 | $this->assertSame('someFile.txt', $file->getClientFilename()); 41 | $this->assertSame(124, $file->getSize()); 42 | } 43 | 44 | public function testGetFileMultiple() 45 | { 46 | $_FILES = [ 47 | 'userfile' => [ 48 | 'name' => [ 49 | 'someFile.txt', 50 | 'someFile2.txt', 51 | ], 52 | 'type' => [ 53 | 'text/plain', 54 | 'text/plain', 55 | ], 56 | 'size' => [ 57 | '124', 58 | '125', 59 | ], 60 | 'tmp_name' => [ 61 | '/tmp/myTempFile.txt', 62 | '/tmp/myTempFile2.txt', 63 | ], 64 | 'error' => [ 65 | 0, 66 | 0, 67 | ], 68 | ], 69 | ]; 70 | 71 | $gotit = UploadedFileBridge::getPsr7UploadedFiles()['userfile']; 72 | $this->assertSame(124, $gotit[0]->getSize()); 73 | $this->assertSame(125, $gotit[1]->getSize()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /dev/tests/codeigniter4Roadrunner/httpTest/FileUploadTest.php: -------------------------------------------------------------------------------- 1 | 'http://localhost:8080/', 15 | ], null, null, false); 16 | $dir = __DIR__ . DIRECTORY_SEPARATOR . 'testFiles' . DIRECTORY_SEPARATOR; 17 | $upload1 = $dir . 'upload1.text'; 18 | $upload2 = $dir . 'upload2.text'; 19 | $response = $client->post('/FileUploadTest/fileUpload', [ 20 | 'multipart' => [ 21 | 'upload1' => new CURLFile($upload1, 'text/plain', 'upload1.text'), 22 | 'upload2' => new CURLFile($upload2, 'text/plain', 'upload2.text'), 23 | ], 24 | ]); 25 | $this->assertSame(201, $response->getStatusCode()); 26 | $getServerMD5Text = json_decode($response->getBody(), true); 27 | $this->assertTrue( 28 | $getServerMD5Text['upload1.text'] === md5_file($upload1) 29 | && $getServerMD5Text['upload2.text'] === md5_file($upload2) 30 | ); 31 | } 32 | 33 | public function testFileMultipleUpload() 34 | { 35 | $client = Services::curlrequest([ 36 | 'base_uri' => 'http://localhost:8080/', 37 | ], null, null, false); 38 | $dir = __DIR__ . DIRECTORY_SEPARATOR . 'testFiles' . DIRECTORY_SEPARATOR; 39 | $upload1 = $dir . 'upload1.text'; 40 | $upload2 = $dir . 'upload2.text'; 41 | $response = $client->post('/FileUploadTest/fileMultipleUpload', [ 42 | 'multipart' => [ 43 | 'data[0]' => new CURLFile($upload1, 'text/plain', 'upload1.text'), 44 | 'data[1]' => new CURLFile($upload2, 'text/plain', 'upload2.text'), 45 | ], 46 | ]); 47 | $this->assertSame(201, $response->getStatusCode()); 48 | $getServerMD5Text = json_decode($response->getBody(), true); 49 | $this->assertTrue( 50 | $getServerMD5Text['upload1.text'] === md5_file($upload1) 51 | && $getServerMD5Text['upload2.text'] === md5_file($upload2) 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /dev/app/Views/errors/cli/error_exception.php: -------------------------------------------------------------------------------- 1 | getFile()) . ':' . $exception->getLine(), 'green')); 12 | CLI::newLine(); 13 | 14 | // The backtrace 15 | if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) { 16 | $backtraces = $exception->getTrace(); 17 | 18 | if ($backtraces) { 19 | CLI::write('Backtrace:', 'green'); 20 | } 21 | 22 | foreach ($backtraces as $i => $error) { 23 | $padFile = ' '; // 4 spaces 24 | $padClass = ' '; // 7 spaces 25 | $c = str_pad($i + 1, 3, ' ', STR_PAD_LEFT); 26 | 27 | if (isset($error['file'])) { 28 | $filepath = clean_path($error['file']) . ':' . $error['line']; 29 | 30 | CLI::write($c . $padFile . CLI::color($filepath, 'yellow')); 31 | } else { 32 | CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow')); 33 | } 34 | 35 | $function = ''; 36 | 37 | if (isset($error['class'])) { 38 | $type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type']; 39 | $function .= $padClass . $error['class'] . $type . $error['function']; 40 | } elseif (! isset($error['class']) && isset($error['function'])) { 41 | $function .= $padClass . $error['function']; 42 | } 43 | 44 | $args = implode(', ', array_map(static function ($value) { 45 | switch (true) { 46 | case is_object($value): 47 | return 'Object(' . get_class($value) . ')'; 48 | 49 | case is_array($value): 50 | return count($value) ? '[...]' : '[]'; 51 | 52 | case $value === null: 53 | return 'null'; // return the lowercased version 54 | 55 | default: 56 | return var_export($value, true); 57 | } 58 | }, array_values($error['args'] ?? []))); 59 | 60 | $function .= '(' . $args . ')'; 61 | 62 | CLI::write($function); 63 | CLI::newLine(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /dev/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | ../src 17 | 18 | 19 | ./app 20 | ./app/Views 21 | ./app/Config/Routes.php 22 | ../src/Commands/file/psr-worker.php 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ./tests 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /dev/.gitignore: -------------------------------------------------------------------------------- 1 | #------------------------- 2 | # Operating Specific Junk Files 3 | #------------------------- 4 | 5 | # OS X 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # OS X Thumbnails 11 | ._* 12 | 13 | # Windows image file caches 14 | Thumbs.db 15 | ehthumbs.db 16 | Desktop.ini 17 | 18 | # Recycle Bin used on file shares 19 | $RECYCLE.BIN/ 20 | 21 | # Windows Installer files 22 | *.cab 23 | *.msi 24 | *.msm 25 | *.msp 26 | 27 | # Windows shortcuts 28 | *.lnk 29 | 30 | # Linux 31 | *~ 32 | 33 | # KDE directory preferences 34 | .directory 35 | 36 | # Linux trash folder which might appear on any partition or disk 37 | .Trash-* 38 | 39 | #------------------------- 40 | # Environment Files 41 | #------------------------- 42 | # These should never be under version control, 43 | # as it poses a security risk. 44 | .env 45 | .vagrant 46 | Vagrantfile 47 | 48 | #------------------------- 49 | # Temporary Files 50 | #------------------------- 51 | writable/cache/* 52 | !writable/cache/index.html 53 | 54 | writable/logs/* 55 | !writable/logs/index.html 56 | 57 | writable/session/* 58 | !writable/session/index.html 59 | 60 | writable/uploads/* 61 | !writable/uploads/index.html 62 | 63 | writable/debugbar/* 64 | 65 | php_errors.log 66 | 67 | #------------------------- 68 | # User Guide Temp Files 69 | #------------------------- 70 | user_guide_src/build/* 71 | user_guide_src/cilexer/build/* 72 | user_guide_src/cilexer/dist/* 73 | user_guide_src/cilexer/pycilexer.egg-info/* 74 | 75 | #------------------------- 76 | # Test Files 77 | #------------------------- 78 | tests/coverage* 79 | 80 | # Don't save phpunit under version control. 81 | phpunit 82 | 83 | #------------------------- 84 | # Composer 85 | #------------------------- 86 | vendor/ 87 | 88 | #------------------------- 89 | # IDE / Development Files 90 | #------------------------- 91 | 92 | # Modules Testing 93 | _modules/* 94 | 95 | # phpenv local config 96 | .php-version 97 | 98 | # Jetbrains editors (PHPStorm, etc) 99 | .idea/ 100 | *.iml 101 | 102 | # Netbeans 103 | nbproject/ 104 | build/ 105 | nbbuild/ 106 | dist/ 107 | nbdist/ 108 | nbactions.xml 109 | nb-configuration.xml 110 | .nb-gradle/ 111 | 112 | # Sublime Text 113 | *.tmlanguage.cache 114 | *.tmPreferences.cache 115 | *.stTheme.cache 116 | *.sublime-workspace 117 | *.sublime-project 118 | .phpintel 119 | /api/ 120 | 121 | # Visual Studio Code 122 | .vscode/ 123 | 124 | /results/ 125 | /phpunit*.xml 126 | /.phpunit.*.cache 127 | 128 | rr 129 | rr.exe 130 | .rr.yaml 131 | psr-worker.php 132 | -------------------------------------------------------------------------------- /dev/app/Config/Database.php: -------------------------------------------------------------------------------- 1 | '', 35 | 'hostname' => 'localhost', 36 | 'username' => '', 37 | 'password' => '', 38 | 'database' => '', 39 | 'DBDriver' => 'MySQLi', 40 | 'DBPrefix' => '', 41 | 'pConnect' => false, 42 | 'DBDebug' => (ENVIRONMENT !== 'production'), 43 | 'charset' => 'utf8', 44 | 'DBCollat' => 'utf8_general_ci', 45 | 'swapPre' => '', 46 | 'encrypt' => false, 47 | 'compress' => false, 48 | 'strictOn' => false, 49 | 'failover' => [], 50 | 'port' => 3306, 51 | ]; 52 | 53 | /** 54 | * This database connection is used when 55 | * running PHPUnit database tests. 56 | * 57 | * @var array 58 | */ 59 | public $tests = [ 60 | 'DSN' => '', 61 | 'hostname' => '127.0.0.1', 62 | 'username' => '', 63 | 'password' => '', 64 | 'database' => ':memory:', 65 | 'DBDriver' => 'SQLite3', 66 | 'DBPrefix' => 'db_', // Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS 67 | 'pConnect' => false, 68 | 'DBDebug' => (ENVIRONMENT !== 'production'), 69 | 'charset' => 'utf8', 70 | 'DBCollat' => 'utf8_general_ci', 71 | 'swapPre' => '', 72 | 'encrypt' => false, 73 | 'compress' => false, 74 | 'strictOn' => false, 75 | 'failover' => [], 76 | 'port' => 3306, 77 | ]; 78 | 79 | public function __construct() 80 | { 81 | parent::__construct(); 82 | 83 | // Ensure that we always set the database group to 'tests' if 84 | // we are currently running an automated test suite, so that 85 | // we don't overwrite live data on accident. 86 | if (ENVIRONMENT === 'testing') { 87 | $this->defaultGroup = 'tests'; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/RequestHandler.php: -------------------------------------------------------------------------------- 1 | getHeaderLine('User-Agent'); 22 | 23 | Services::request(new App(), false); 24 | Services::request()->getUserAgent()->parse($_SERVER['HTTP_USER_AGENT']); 25 | 26 | UriBridge::setUri(self::$_rRequest->getUri()); 27 | 28 | // $rRequest->g 29 | Services::request()->setBody(self::getBody()); 30 | 31 | self::setParams(); 32 | self::setHeader(); 33 | 34 | return Services::request(); 35 | } 36 | 37 | protected static function setFile() 38 | { 39 | if (self::$_rRequest->getUploadedFiles() !== []) { 40 | UploadedFileBridge::getPsr7UploadedFiles(self::$_rRequest->getUploadedFiles(), true); 41 | } 42 | } 43 | 44 | protected static function getBody() 45 | { 46 | if (strpos(self::$_rRequest->getHeaderLine('content-type'), 'application/json') === 0) { 47 | return self::$_rRequest->getBody(); 48 | } 49 | 50 | return self::$_rRequest->getBody()->getContents(); 51 | } 52 | 53 | protected static function setParams() 54 | { 55 | Services::request()->setMethod(self::$_rRequest->getMethod()); 56 | Services::request()->setGlobal('get', self::$_rRequest->getQueryParams()); 57 | 58 | if (self::$_rRequest->getMethod() === 'POST') { 59 | Services::request()->setGlobal('post', self::$_rRequest->getParsedBody()); 60 | } 61 | 62 | $_COOKIE = []; 63 | Services::request()->setGlobal('cookie', self::$_rRequest->getCookieParams()); 64 | 65 | foreach (self::$_rRequest->getCookieParams() as $key => $value) { 66 | $_COOKIE[$key] = $value; 67 | } 68 | 69 | if (isset($_COOKIE[config(App::class)->sessionCookieName])) { 70 | session_id($_COOKIE[config(App::class)->sessionCookieName]); 71 | } 72 | 73 | Services::request()->setGlobal('server', self::$_rRequest->getServerParams()); 74 | } 75 | 76 | protected static function setHeader() 77 | { 78 | $rHeader = self::$_rRequest->getHeaders(); 79 | 80 | foreach ($rHeader as $key => $datas) { 81 | foreach ($datas as $values) { 82 | Services::request()->setHeader($key, $values); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /dev/app/Config/Format.php: -------------------------------------------------------------------------------- 1 | 41 | */ 42 | public $formatters = [ 43 | 'application/json' => 'CodeIgniter\Format\JSONFormatter', 44 | 'application/xml' => 'CodeIgniter\Format\XMLFormatter', 45 | 'text/xml' => 'CodeIgniter\Format\XMLFormatter', 46 | ]; 47 | 48 | /** 49 | * -------------------------------------------------------------------------- 50 | * Formatters Options 51 | * -------------------------------------------------------------------------- 52 | * 53 | * Additional Options to adjust default formatters behaviour. 54 | * For each mime type, list the additional options that should be used. 55 | * 56 | * @var array 57 | */ 58 | public $formatterOptions = [ 59 | 'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, 60 | 'application/xml' => 0, 61 | 'text/xml' => 0, 62 | ]; 63 | 64 | /** 65 | * A Factory method to return the appropriate formatter for the given mime type. 66 | * 67 | * @return FormatterInterface 68 | * 69 | * @deprecated This is an alias of `\CodeIgniter\Format\Format::getFormatter`. Use that instead. 70 | */ 71 | public function getFormatter(string $mime) 72 | { 73 | return Services::format()->getFormatter($mime); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /dev/app/Config/DocTypes.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | public $list = [ 13 | 'xhtml11' => '', 14 | 'xhtml1-strict' => '', 15 | 'xhtml1-trans' => '', 16 | 'xhtml1-frame' => '', 17 | 'xhtml-basic11' => '', 18 | 'html5' => '', 19 | 'html4-strict' => '', 20 | 'html4-trans' => '', 21 | 'html4-frame' => '', 22 | 'mathml1' => '', 23 | 'mathml2' => '', 24 | 'svg10' => '', 25 | 'svg11' => '', 26 | 'svg11-basic' => '', 27 | 'svg11-tiny' => '', 28 | 'xhtml-math-svg-xh' => '', 29 | 'xhtml-math-svg-sh' => '', 30 | 'xhtml-rdfa-1' => '', 31 | 'xhtml-rdfa-2' => '', 32 | ]; 33 | } 34 | -------------------------------------------------------------------------------- /dev/README.md: -------------------------------------------------------------------------------- 1 | # Test Case 2 | 3 | This is the Codeigniter4-RoadRunner test case project, it is built by Codeigniter4, and has loaded the Codeigniter4-Roadrunner library class inside the upper directory included in the `src` in. 4 | 5 | You can run the test right after you modified the Codeigniter4-Roadrunner project files, verify if the functions are complete; Or write some related program logic in this project to assist your development. 6 | 7 | ## Test Range 8 | 9 | This test case takes the actual sent CURL Request as test approach, because what Codeigniter4-Roadrunner provide is the synchronization on HTTP Request and Response objects of Roadrunner-Worker and Codeigniter4 (Since Codeigniter4 doesn't implement PSR-7 interface standard). In other words, we just have to verify if the server workes as what we wanted under the actual HTTP connection. 10 | 11 | 1. BasicTest:Test HTTP `GET`、`POST`、`query`、`form-data`, and the `php echo` output command, and if `header` can process normally and give us outputs. 12 | 2. FileUploadTest:Test if file upload class can work correctly and move files. 13 | 3. RestTest:Test if Codeigniter4 RESTful library can work properly and can parse every verbs 14 | 4. SessionTest:Test if the Session mode, triggered by the file system can work properly. 15 | 16 | ## Requirements 17 | 18 | We recommend you to use the latest PHPUnit. While we're writing scripts, the version we're running at is version `9.5.19`. You might need to use Composer to download the library your project needed back to your develop environment. 19 | 20 | ``` 21 | composer install 22 | ``` 23 | 24 | Next, you must initialize the environment that Roadrunner needed. 25 | 26 | ``` 27 | php spark ciroad:init 28 | ``` 29 | 30 | Finally, please confirm if the directory has these three files including `rr` (if you are developing under Windows, you will see `rr.exe`), `.rr.yaml`, `psr-worker.php`. 31 | 32 | ## Run Tests 33 | 34 | Before running tests, please open `.rr.yaml` file first, and ensure this configuration file has these settings: 35 | 36 | ```yaml 37 | rpc: 38 | listen: tcp://127.0.0.1:6001 39 | 40 | server: 41 | command: "php psr-worker.php" 42 | 43 | http: 44 | address: "0.0.0.0:8080" 45 | static: 46 | dir: "./public" 47 | forbid: [".htaccess", ".php"] 48 | pool: 49 | num_workers: 1 50 | ``` 51 | 52 | Since Roadrunner-Worker lasts inside RAMs, HTTP requests will reuse Workers to process. Hence, we need to test the stability under the environment with only one worker to prove that it can work properly under several workers. 53 | 54 | Next, you have to open a terminal and cd to the root directory, type the commands below to run the Roadrunner server: 55 | 56 | ``` 57 | ./rr serve -d 58 | ``` 59 | 60 | Finally, open another new terminal and cd to the test project, type the commands below to run tests: 61 | 62 | ``` 63 | ./vendor/bin/phpunit 64 | ``` 65 | 66 | If you're running tests under Windows CMD, your command should be like this: 67 | 68 | ``` 69 | vendor\bin\phpunit 70 | ``` 71 | -------------------------------------------------------------------------------- /dev/app/Views/errors/html/debug.js: -------------------------------------------------------------------------------- 1 | // Tabs 2 | 3 | var tabLinks = new Array(); 4 | var contentDivs = new Array(); 5 | 6 | function init() 7 | { 8 | // Grab the tab links and content divs from the page 9 | var tabListItems = document.getElementById('tabs').childNodes; 10 | console.log(tabListItems); 11 | for (var i = 0; i < tabListItems.length; i ++) 12 | { 13 | if (tabListItems[i].nodeName == "LI") 14 | { 15 | var tabLink = getFirstChildWithTagName(tabListItems[i], 'A'); 16 | var id = getHash(tabLink.getAttribute('href')); 17 | tabLinks[id] = tabLink; 18 | contentDivs[id] = document.getElementById(id); 19 | } 20 | } 21 | 22 | // Assign onclick events to the tab links, and 23 | // highlight the first tab 24 | var i = 0; 25 | 26 | for (var id in tabLinks) 27 | { 28 | tabLinks[id].onclick = showTab; 29 | tabLinks[id].onfocus = function () { 30 | this.blur() 31 | }; 32 | if (i == 0) 33 | { 34 | tabLinks[id].className = 'active'; 35 | } 36 | i ++; 37 | } 38 | 39 | // Hide all content divs except the first 40 | var i = 0; 41 | 42 | for (var id in contentDivs) 43 | { 44 | if (i != 0) 45 | { 46 | console.log(contentDivs[id]); 47 | contentDivs[id].className = 'content hide'; 48 | } 49 | i ++; 50 | } 51 | } 52 | 53 | function showTab() 54 | { 55 | var selectedId = getHash(this.getAttribute('href')); 56 | 57 | // Highlight the selected tab, and dim all others. 58 | // Also show the selected content div, and hide all others. 59 | for (var id in contentDivs) 60 | { 61 | if (id == selectedId) 62 | { 63 | tabLinks[id].className = 'active'; 64 | contentDivs[id].className = 'content'; 65 | } 66 | else 67 | { 68 | tabLinks[id].className = ''; 69 | contentDivs[id].className = 'content hide'; 70 | } 71 | } 72 | 73 | // Stop the browser following the link 74 | return false; 75 | } 76 | 77 | function getFirstChildWithTagName(element, tagName) 78 | { 79 | for (var i = 0; i < element.childNodes.length; i ++) 80 | { 81 | if (element.childNodes[i].nodeName == tagName) 82 | { 83 | return element.childNodes[i]; 84 | } 85 | } 86 | } 87 | 88 | function getHash(url) 89 | { 90 | var hashPos = url.lastIndexOf('#'); 91 | return url.substring(hashPos + 1); 92 | } 93 | 94 | function toggle(elem) 95 | { 96 | elem = document.getElementById(elem); 97 | 98 | if (elem.style && elem.style['display']) 99 | { 100 | // Only works with the "style" attr 101 | var disp = elem.style['display']; 102 | } 103 | else if (elem.currentStyle) 104 | { 105 | // For MSIE, naturally 106 | var disp = elem.currentStyle['display']; 107 | } 108 | else if (window.getComputedStyle) 109 | { 110 | // For most other browsers 111 | var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display'); 112 | } 113 | 114 | // Toggle the state of the "display" style 115 | elem.style.display = disp == 'block' ? 'none' : 'block'; 116 | 117 | return false; 118 | } 119 | -------------------------------------------------------------------------------- /dev/app/Config/Paths.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view 10 | * the LICENSE file that was distributed with this source code. 11 | */ 12 | 13 | /* 14 | * -------------------------------------------------------------------- 15 | * CodeIgniter command-line tools 16 | * -------------------------------------------------------------------- 17 | * The main entry point into the CLI system and allows you to run 18 | * commands and perform maintenance on your application. 19 | * 20 | * Because CodeIgniter can handle CLI requests as just another web request 21 | * this class mainly acts as a passthru to the framework itself. 22 | */ 23 | 24 | // Refuse to run when called from php-cgi 25 | if (strpos(PHP_SAPI, 'cgi') === 0) { 26 | exit("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n"); 27 | } 28 | 29 | // We want errors to be shown when using it from the CLI. 30 | error_reporting(-1); 31 | ini_set('display_errors', '1'); 32 | 33 | /** 34 | * @var bool 35 | * 36 | * @deprecated No longer in use. `CodeIgniter` has `$context` property. 37 | */ 38 | define('SPARKED', true); 39 | 40 | // Path to the front controller 41 | define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR); 42 | 43 | // Ensure the current directory is pointing to the front controller's directory 44 | chdir(FCPATH); 45 | 46 | /* 47 | *--------------------------------------------------------------- 48 | * BOOTSTRAP THE APPLICATION 49 | *--------------------------------------------------------------- 50 | * This process sets up the path constants, loads and registers 51 | * our autoloader, along with Composer's, loads our constants 52 | * and fires up an environment-specific bootstrapping. 53 | */ 54 | 55 | // Load our paths config file 56 | // This is the line that might need to be changed, depending on your folder structure. 57 | require FCPATH . '../app/Config/Paths.php'; 58 | // ^^^ Change this line if you move your application folder 59 | 60 | $paths = new Config\Paths(); 61 | 62 | // Location of the framework bootstrap file. 63 | require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; 64 | 65 | // Load environment settings from .env files into $_SERVER and $_ENV 66 | require_once SYSTEMPATH . 'Config/DotEnv.php'; 67 | (new CodeIgniter\Config\DotEnv(ROOTPATH))->load(); 68 | 69 | // Grab our CodeIgniter 70 | $app = Config\Services::codeigniter(); 71 | $app->initialize(); 72 | $app->setContext('spark'); 73 | 74 | // Grab our Console 75 | $console = new CodeIgniter\CLI\Console($app); 76 | 77 | // Show basic information before we do anything else. 78 | if (is_int($suppress = array_search('--no-header', $_SERVER['argv'], true))) { 79 | unset($_SERVER['argv'][$suppress]); // @codeCoverageIgnore 80 | $suppress = true; 81 | } 82 | 83 | $console->showHeader($suppress); 84 | 85 | // fire off the command in the main framework. 86 | $response = $console->run(); 87 | 88 | if ($response->getStatusCode() >= 300) { 89 | exit($response->getStatusCode()); 90 | } 91 | -------------------------------------------------------------------------------- /dev/app/Config/Autoload.php: -------------------------------------------------------------------------------- 1 | SYSTEMPATH, 37 | * 'App' => APPPATH 38 | * ]; 39 | *``` 40 | * 41 | * @var array 42 | */ 43 | public $psr4 = [ 44 | APP_NAMESPACE => APPPATH, // For custom app namespace 45 | 'Config' => APPPATH . 'Config', 46 | 'SDPMlab\\Ci4Roadrunner\\' => ROOTPATH . '..' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, 47 | ]; 48 | 49 | /** 50 | * ------------------------------------------------------------------- 51 | * Class Map 52 | * ------------------------------------------------------------------- 53 | * The class map provides a map of class names and their exact 54 | * location on the drive. Classes loaded in this manner will have 55 | * slightly faster performance because they will not have to be 56 | * searched for within one or more directories as they would if they 57 | * were being autoloaded through a namespace. 58 | * 59 | * Prototype: 60 | *``` 61 | * $classmap = [ 62 | * 'MyClass' => '/path/to/class/file.php' 63 | * ]; 64 | *``` 65 | * 66 | * @var array 67 | */ 68 | public $classmap = []; 69 | 70 | /** 71 | * ------------------------------------------------------------------- 72 | * Files 73 | * ------------------------------------------------------------------- 74 | * The files array provides a list of paths to __non-class__ files 75 | * that will be autoloaded. This can be useful for bootstrap operations 76 | * or for loading functions. 77 | * 78 | * Prototype: 79 | * ``` 80 | * $files = [ 81 | * '/path/to/my/file.php', 82 | * ]; 83 | * ``` 84 | * 85 | * @var array 86 | */ 87 | public $files = []; 88 | } 89 | -------------------------------------------------------------------------------- /dev/app/Config/Toolbar.php: -------------------------------------------------------------------------------- 1 | 'http://localhost:8080/', 15 | ], null, null, false); 16 | $response = $client->get('/testRest'); 17 | $this->assertSame(200, $response->getStatusCode()); 18 | } 19 | 20 | public function testShow() 21 | { 22 | $client = Services::curlrequest([ 23 | 'base_uri' => 'http://localhost:8080/', 24 | ], null, null, false); 25 | $id = uniqid(); 26 | $response = $client->get("/testRest/{$id}"); 27 | $this->assertSame(200, $response->getStatusCode()); 28 | $getJson = json_decode($response->getBody(), true); 29 | $this->assertSame($id, $getJson['id']); 30 | } 31 | 32 | public function testCreate() 33 | { 34 | $client = Services::curlrequest([ 35 | 'base_uri' => 'http://localhost:8080/', 36 | ], null, null, false); 37 | $text1 = uniqid(); 38 | $text2 = uniqid(); 39 | $verify = md5($text1 . $text2); 40 | $response = $client->post('/testRest', [ 41 | 'http_errors' => false, 42 | 'json' => [ 43 | 'text1' => $text1, 44 | 'text2' => $text2, 45 | ], 46 | ]); 47 | $this->assertSame(201, $response->getStatusCode()); 48 | $getJson = json_decode($response->getBody(), true); 49 | $resVerify = md5($getJson['data']['text1'] . $getJson['data']['text2']); 50 | $this->assertSame($verify, $resVerify); 51 | } 52 | 53 | public function testUpdate() 54 | { 55 | $client = Services::curlrequest([ 56 | 'base_uri' => 'http://localhost:8080/', 57 | ], null, null, false); 58 | $text1 = uniqid(); 59 | $text2 = uniqid(); 60 | $verify = md5($text1 . $text2); 61 | $id = uniqid(); 62 | $response = $client->put("/testRest/{$id}", [ 63 | 'http_errors' => false, 64 | 'json' => [ 65 | 'text1' => $text1, 66 | 'text2' => $text2, 67 | ], 68 | ]); 69 | $this->assertSame(200, $response->getStatusCode()); 70 | $getJson = json_decode($response->getBody(), true); 71 | $resVerify = md5($getJson['data']['text1'] . $getJson['data']['text2']); 72 | $this->assertSame($verify, $resVerify); 73 | $this->assertSame($id, $getJson['id']); 74 | } 75 | 76 | public function testNew() 77 | { 78 | $client = Services::curlrequest([ 79 | 'base_uri' => 'http://localhost:8080/', 80 | ], null, null, false); 81 | $response = $client->get('/testRest/new'); 82 | $this->assertSame(200, $response->getStatusCode()); 83 | $this->assertSame('newView', $response->getBody()); 84 | } 85 | 86 | public function testEdit() 87 | { 88 | $client = Services::curlrequest([ 89 | 'base_uri' => 'http://localhost:8080/', 90 | ], null, null, false); 91 | $id = uniqid(); 92 | $response = $client->get("/testRest/{$id}/edit"); 93 | $this->assertSame(200, $response->getStatusCode()); 94 | $this->assertSame($id . 'editView', $response->getBody()); 95 | } 96 | 97 | public function testDelete() 98 | { 99 | $client = Services::curlrequest([ 100 | 'base_uri' => 'http://localhost:8080/', 101 | ], null, null, false); 102 | $id = uniqid(); 103 | $response = $client->delete("/testRest/{$id}"); 104 | $this->assertSame(200, $response->getStatusCode()); 105 | $getJson = json_decode($response->getBody(), true); 106 | $this->assertSame($id, $getJson['id']); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /dev/app/Config/Cookie.php: -------------------------------------------------------------------------------- 1 | _psrFiles = &$files; 25 | } else { 26 | $this->handleFile(); 27 | } 28 | } 29 | 30 | public static function getPsr7UploadedFiles( 31 | array $files = [], 32 | bool $isRoadrunner = false 33 | ): array { 34 | if (! (self::$_instance instanceof UploadedFileBridge)) { 35 | if ($isRoadrunner) { 36 | self::$_instance = new UploadedFileBridge($files, $isRoadrunner); 37 | } else { 38 | self::$_instance = new UploadedFileBridge(); 39 | } 40 | } 41 | 42 | return self::$_instance->_psrFiles; 43 | } 44 | 45 | public static function reset(): void 46 | { 47 | self::$_instance = null; 48 | } 49 | 50 | private function handleFile() 51 | { 52 | $files = $this->fixFilesArray($_FILES); 53 | 54 | foreach ($files as $name => $file) { 55 | $this->_psrFiles[$name] = $this->createFileObject($file); 56 | } 57 | } 58 | 59 | /** 60 | * Reformats the odd $_FILES array into something much more like 61 | * we would expect, with each object having its own array. 62 | * 63 | * Thanks to Jack Sleight on the PHP Manual page for the basis 64 | * of this method. 65 | * 66 | * @see http://php.net/manual/en/reserved.variables.files.php#118294 67 | */ 68 | private function fixFilesArray(array $data): array 69 | { 70 | $output = []; 71 | 72 | foreach ($data as $name => $array) { 73 | foreach ($array as $field => $value) { 74 | $pointer = &$output[$name]; 75 | 76 | if (! is_array($value)) { 77 | $pointer[$field] = $value; 78 | 79 | continue; 80 | } 81 | 82 | $stack = [&$pointer]; 83 | $iterator = new RecursiveIteratorIterator( 84 | new RecursiveArrayIterator($value), 85 | RecursiveIteratorIterator::SELF_FIRST 86 | ); 87 | 88 | foreach ($iterator as $key => $val) { 89 | array_splice($stack, $iterator->getDepth() + 1); 90 | $pointer = &$stack[count($stack) - 1]; 91 | $pointer = &$pointer[$key]; 92 | $stack[] = &$pointer; 93 | if (! $iterator->hasChildren()) { 94 | $pointer[$field] = $val; 95 | } 96 | } 97 | } 98 | } 99 | 100 | return $output; 101 | } 102 | 103 | /** 104 | * Given a file array, will create UploadedFile instances. Will 105 | * loop over an array and create objects for each. 106 | * 107 | * @return array|ReplaceUploadedFile 108 | */ 109 | protected function createFileObject(array $array) 110 | { 111 | if (! isset($array['name'])) { 112 | $output = []; 113 | 114 | foreach ($array as $key => $values) { 115 | if (! is_array($values)) { 116 | continue; 117 | } 118 | $output[$key] = $this->createFileObject($values); 119 | } 120 | 121 | return $output; 122 | } 123 | 124 | return new ReplaceUploadedFile( 125 | $array['tmp_name'] ?? null, 126 | $array['name'] ?? null, 127 | $array['type'] ?? null, 128 | $array['size'] ?? null, 129 | $array['error'] ?? null 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /dev/tests/codeigniter4Roadrunner/httpTest/BasicTest.php: -------------------------------------------------------------------------------- 1 | 'http://localhost:8080/', 15 | ], null, null, false); 16 | $response = $client->get('/basicTest/loadView'); 17 | $this->assertSame(200, $response->getStatusCode()); 18 | } 19 | 20 | public function testEchoText() 21 | { 22 | $client = Services::curlrequest([ 23 | 'base_uri' => 'http://localhost:8080/', 24 | ], null, null, false); 25 | $response = $client->get('/basicTest/echoText'); 26 | $this->assertSame('testText', $response->getBody()); 27 | } 28 | 29 | public function testUrlQuery() 30 | { 31 | $client = Services::curlrequest([ 32 | 'base_uri' => 'http://localhost:8080/', 33 | ], null, null, false); 34 | $text1 = uniqid(); 35 | $text2 = uniqid(); 36 | $text3 = uniqid(); 37 | $verify = md5($text1 . $text2 . $text3); 38 | $response = $client->get('/basicTest/urlqyery', [ 39 | 'query' => [ 40 | 'texts' => [$text1, $text2], 41 | 'text3' => $text3, 42 | ], 43 | ]); 44 | $this->assertSame($verify, $response->getBody()); 45 | } 46 | 47 | public function testFormParams() 48 | { 49 | $client = Services::curlrequest([ 50 | 'base_uri' => 'http://localhost:8080/', 51 | ], null, null, false); 52 | $text1 = uniqid(); 53 | $text2 = uniqid(); 54 | $text3 = uniqid(); 55 | $verify = md5($text1 . $text2 . $text3); 56 | $response = $client->post('/basicTest/formparams', [ 57 | 'form_params' => [ 58 | 'texts' => [$text1, $text2], 59 | 'text3' => $text3, 60 | ], 61 | ]); 62 | $this->assertSame($verify, $response->getBody()); 63 | } 64 | 65 | public function testFormParamsAndQuery() 66 | { 67 | $client = Services::curlrequest([ 68 | 'base_uri' => 'http://localhost:8080/', 69 | ], null, null, false); 70 | $text1 = uniqid(); 71 | $text2 = uniqid(); 72 | $verify = md5($text1 . $text2); 73 | $response = $client->post('/basicTest/formparamsandquery', [ 74 | 'query' => [ 75 | 'text1' => $text1, 76 | ], 77 | 'form_params' => [ 78 | 'text2' => $text2, 79 | ], 80 | ]); 81 | $this->assertSame($verify, $response->getBody()); 82 | } 83 | 84 | public function testReadHeader() 85 | { 86 | for ($i = 0; $i < 2; $i++) { 87 | $client = Services::curlrequest([ 88 | 'base_uri' => 'http://localhost:8080/', 89 | ], null, null, false); 90 | $token = uniqid(); 91 | $response = $client->get('/basicTest/readHeader', [ 92 | 'headers' => [ 93 | 'X-Auth-Token' => $token, 94 | ], 95 | ]); 96 | $this->assertSame(200, $response->getStatusCode()); 97 | $getServerCheckText = json_decode($response->getBody(), true)['X-Auth-Token']; 98 | $this->assertSame($token, $getServerCheckText); 99 | } 100 | } 101 | 102 | public function testSendHeader() 103 | { 104 | $tokens = []; 105 | 106 | for ($i = 0; $i < 2; $i++) { 107 | $client = Services::curlrequest([ 108 | 'base_uri' => 'http://localhost:8080/', 109 | ], null, null, false); 110 | $token = uniqid(); 111 | $response = $client->get('/basicTest/sendHeader'); 112 | $this->assertSame(200, $response->getStatusCode()); 113 | $tokens[] = $response->getHeader('X-Set-Auth-Token')->getValueLine(); 114 | } 115 | $this->assertNotSame($token[1], $tokens[0]); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /dev/app/Views/errors/html/debug.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg-color: #fff; 3 | --main-text-color: #555; 4 | --dark-text-color: #222; 5 | --light-text-color: #c7c7c7; 6 | --brand-primary-color: #E06E3F; 7 | --light-bg-color: #ededee; 8 | --dark-bg-color: #404040; 9 | } 10 | 11 | body { 12 | height: 100%; 13 | background: var(--main-bg-color); 14 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 15 | color: var(--main-text-color); 16 | font-weight: 300; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | h1 { 21 | font-weight: lighter; 22 | letter-spacing: 0.8; 23 | font-size: 3rem; 24 | color: var(--dark-text-color); 25 | margin: 0; 26 | } 27 | h1.headline { 28 | margin-top: 20%; 29 | font-size: 5rem; 30 | } 31 | .text-center { 32 | text-align: center; 33 | } 34 | p.lead { 35 | font-size: 1.6rem; 36 | } 37 | .container { 38 | max-width: 75rem; 39 | margin: 0 auto; 40 | padding: 1rem; 41 | } 42 | .header { 43 | background: var(--light-bg-color); 44 | color: var(--dark-text-color); 45 | } 46 | .header .container { 47 | padding: 1rem 1.75rem 1.75rem 1.75rem; 48 | } 49 | .header h1 { 50 | font-size: 2.5rem; 51 | font-weight: 500; 52 | } 53 | .header p { 54 | font-size: 1.2rem; 55 | margin: 0; 56 | line-height: 2.5; 57 | } 58 | .header a { 59 | color: var(--brand-primary-color); 60 | margin-left: 2rem; 61 | display: none; 62 | text-decoration: none; 63 | } 64 | .header:hover a { 65 | display: inline; 66 | } 67 | 68 | .footer { 69 | background: var(--dark-bg-color); 70 | color: var(--light-text-color); 71 | } 72 | .footer .container { 73 | border-top: 1px solid #e7e7e7; 74 | margin-top: 1rem; 75 | text-align: center; 76 | } 77 | 78 | .source { 79 | background: #343434; 80 | color: var(--light-text-color); 81 | padding: 0.5em 1em; 82 | border-radius: 5px; 83 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 84 | font-size: 0.85rem; 85 | margin: 0; 86 | overflow-x: scroll; 87 | } 88 | .source span.line { 89 | line-height: 1.4; 90 | } 91 | .source span.line .number { 92 | color: #666; 93 | } 94 | .source .line .highlight { 95 | display: block; 96 | background: var(--dark-text-color); 97 | color: var(--light-text-color); 98 | } 99 | .source span.highlight .number { 100 | color: #fff; 101 | } 102 | 103 | .tabs { 104 | list-style: none; 105 | list-style-position: inside; 106 | margin: 0; 107 | padding: 0; 108 | margin-bottom: -1px; 109 | } 110 | .tabs li { 111 | display: inline; 112 | } 113 | .tabs a:link, 114 | .tabs a:visited { 115 | padding: 0rem 1rem; 116 | line-height: 2.7; 117 | text-decoration: none; 118 | color: var(--dark-text-color); 119 | background: var(--light-bg-color); 120 | border: 1px solid rgba(0,0,0,0.15); 121 | border-bottom: 0; 122 | border-top-left-radius: 5px; 123 | border-top-right-radius: 5px; 124 | display: inline-block; 125 | } 126 | .tabs a:hover { 127 | background: var(--light-bg-color); 128 | border-color: rgba(0,0,0,0.15); 129 | } 130 | .tabs a.active { 131 | background: var(--main-bg-color); 132 | color: var(--main-text-color); 133 | } 134 | .tab-content { 135 | background: var(--main-bg-color); 136 | border: 1px solid rgba(0,0,0,0.15); 137 | } 138 | .content { 139 | padding: 1rem; 140 | } 141 | .hide { 142 | display: none; 143 | } 144 | 145 | .alert { 146 | margin-top: 2rem; 147 | display: block; 148 | text-align: center; 149 | line-height: 3.0; 150 | background: #d9edf7; 151 | border: 1px solid #bcdff1; 152 | border-radius: 5px; 153 | color: #31708f; 154 | } 155 | ul, ol { 156 | line-height: 1.8; 157 | } 158 | 159 | table { 160 | width: 100%; 161 | overflow: hidden; 162 | } 163 | th { 164 | text-align: left; 165 | border-bottom: 1px solid #e7e7e7; 166 | padding-bottom: 0.5rem; 167 | } 168 | td { 169 | padding: 0.2rem 0.5rem 0.2rem 0; 170 | } 171 | tr:hover td { 172 | background: #f1f1f1; 173 | } 174 | td pre { 175 | white-space: pre-wrap; 176 | } 177 | 178 | .trace a { 179 | color: inherit; 180 | } 181 | .trace table { 182 | width: auto; 183 | } 184 | .trace tr td:first-child { 185 | min-width: 5em; 186 | font-weight: bold; 187 | } 188 | .trace td { 189 | background: var(--light-bg-color); 190 | padding: 0 1rem; 191 | } 192 | .trace td pre { 193 | margin: 0; 194 | } 195 | .args { 196 | display: none; 197 | } 198 | -------------------------------------------------------------------------------- /dev/builds: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'vcs', 56 | 'url' => GITHUB_URL, 57 | ]; 58 | } 59 | 60 | $array['require']['codeigniter4/codeigniter4'] = 'dev-develop'; 61 | unset($array['require']['codeigniter4/framework']); 62 | } else { 63 | unset($array['minimum-stability']); 64 | 65 | if (isset($array['repositories'])) { 66 | foreach ($array['repositories'] as $i => $repository) { 67 | if ($repository['url'] === GITHUB_URL) { 68 | unset($array['repositories'][$i]); 69 | break; 70 | } 71 | } 72 | 73 | if (empty($array['repositories'])) { 74 | unset($array['repositories']); 75 | } 76 | } 77 | 78 | $array['require']['codeigniter4/framework'] = LATEST_RELEASE; 79 | unset($array['require']['codeigniter4/codeigniter4']); 80 | } 81 | 82 | file_put_contents($file, json_encode($array, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL); 83 | 84 | $modified[] = $file; 85 | } else { 86 | echo 'Warning: Unable to decode composer.json! Skipping...' . PHP_EOL; 87 | } 88 | } else { 89 | echo 'Warning: Unable to read composer.json! Skipping...' . PHP_EOL; 90 | } 91 | } 92 | 93 | $files = [ 94 | __DIR__ . DIRECTORY_SEPARATOR . 'app/Config/Paths.php', 95 | __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml.dist', 96 | __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml', 97 | ]; 98 | 99 | foreach ($files as $file) { 100 | if (is_file($file)) { 101 | $contents = file_get_contents($file); 102 | 103 | if ($dev) { 104 | $contents = str_replace('vendor/codeigniter4/framework', 'vendor/codeigniter4/codeigniter4', $contents); 105 | } else { 106 | $contents = str_replace('vendor/codeigniter4/codeigniter4', 'vendor/codeigniter4/framework', $contents); 107 | } 108 | 109 | file_put_contents($file, $contents); 110 | 111 | $modified[] = $file; 112 | } 113 | } 114 | 115 | if ($modified === []) { 116 | echo 'No files modified.' . PHP_EOL; 117 | } else { 118 | echo 'The following files were modified:' . PHP_EOL; 119 | 120 | foreach ($modified as $file) { 121 | echo " * {$file}" . PHP_EOL; 122 | } 123 | 124 | echo 'Run `composer update` to sync changes with your vendor folder.' . PHP_EOL; 125 | } 126 | -------------------------------------------------------------------------------- /src/Commands/file/psr-worker.php: -------------------------------------------------------------------------------- 1 | systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php'; 41 | require realpath($botstorap); 42 | 43 | require_once SYSTEMPATH . 'Config/DotEnv.php'; 44 | (new \CodeIgniter\Config\DotEnv(ROOTPATH))->load(); 45 | 46 | $app = \Config\Services::codeigniter(); 47 | $app->initialize(); 48 | $app->setContext('web'); 49 | 50 | /** 51 | * RoadRunner worker init 52 | */ 53 | $worker = Worker::create(); 54 | $psrFactory = new Psr17Factory(); 55 | $psr7 = new PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory); 56 | 57 | while (true) { 58 | // get psr7 request 59 | try { 60 | $request = $psr7->waitRequest(); 61 | if (! ($request instanceof ServerRequestInterface)) { // Termination request received 62 | break; 63 | } 64 | } catch (Exception $e) { 65 | $psr7->respond(new Response(400)); // Bad Request 66 | 67 | continue; 68 | } 69 | 70 | // handle request object 71 | try { 72 | $ci4Request = RequestHandler::initRequest($request); 73 | } catch (Throwable $e) { 74 | var_dump((string) $e); 75 | $psr7->getWorker()->error((string) $e); 76 | } 77 | 78 | // handle debug-bar 79 | try { 80 | if (ENVIRONMENT === 'development') { 81 | Kint::$mode_default_cli = null; 82 | $toolbar = new Toolbar(config('Toolbar'), $ci4Request); 83 | 84 | if ($ci4BarResponse = $toolbar->respond()) { 85 | $response = new ResponseBridge($ci4BarResponse, $request); 86 | $psr7->respond($response); 87 | refreshCodeIgniter4(); 88 | unset($app); 89 | 90 | continue; 91 | } 92 | } 93 | } catch (Throwable $e) { 94 | $psr7->getWorker()->error((string) $e); 95 | } 96 | 97 | // run framework and error handling 98 | try { 99 | if (! env('CIROAD_DB_AUTOCLOSE')) { 100 | HandleDBConnection::reconnect(); 101 | } 102 | 103 | $app = \Config\Services::codeigniter(); 104 | $app->initialize(); 105 | $app->setContext('web')->setRequest($ci4Request)->run(); 106 | 107 | $ci4Response = Services::response(); 108 | } catch (Throwable $e) { 109 | $exception = new Exceptions($request); 110 | $response = $exception->exceptionHandler($e); 111 | $psr7->respond($response); 112 | 113 | refreshCodeIgniter4(); 114 | unset($app); 115 | 116 | continue; 117 | } 118 | 119 | // handle response object 120 | try { 121 | // Application code logic 122 | $response = new ResponseBridge($ci4Response, $request); 123 | $psr7->respond($response); 124 | 125 | refreshCodeIgniter4(); 126 | unset($app); 127 | } catch (Exception $e) { 128 | $psr7->respond(new Response(500, [], 'Something Went Wrong!')); 129 | } 130 | } 131 | 132 | function refreshCodeIgniter4() 133 | { 134 | $input = fopen('php://input', 'wb'); 135 | fwrite($input, ''); 136 | fclose($input); 137 | 138 | try { 139 | ob_end_clean(); 140 | } catch (Throwable $th) { 141 | } 142 | 143 | Services::reset(true); 144 | 145 | UploadedFileBridge::reset(); 146 | 147 | if (env('CIROAD_DB_AUTOCLOSE')) { 148 | HandleDBConnection::closeConnect(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /dev/app/Config/ContentSecurityPolicy.php: -------------------------------------------------------------------------------- 1 | ` element. 81 | * 82 | * Will default to self if not overridden 83 | * 84 | * @var string|string[]|null 85 | */ 86 | public $baseURI; 87 | 88 | /** 89 | * Lists the URLs for workers and embedded frame contents 90 | * 91 | * @var string|string[] 92 | */ 93 | public $childSrc = 'self'; 94 | 95 | /** 96 | * Limits the origins that you can connect to (via XHR, 97 | * WebSockets, and EventSource). 98 | * 99 | * @var string|string[] 100 | */ 101 | public $connectSrc = 'self'; 102 | 103 | /** 104 | * Specifies the origins that can serve web fonts. 105 | * 106 | * @var string|string[] 107 | */ 108 | public $fontSrc; 109 | 110 | /** 111 | * Lists valid endpoints for submission from `
` tags. 112 | * 113 | * @var string|string[] 114 | */ 115 | public $formAction = 'self'; 116 | 117 | /** 118 | * Specifies the sources that can embed the current page. 119 | * This directive applies to ``, `