From 1eb3ac38e9ad530e40afb6bc91b8928a4a42b48b Mon Sep 17 00:00:00 2001 From: KHHH2312 Date: Mon, 1 Jun 2026 15:59:14 +0100 Subject: [PATCH 1/2] chore: upgrade slim, dotenv, and phpunit dependencies --- composer.json | 12 ++++++----- refactor.py | 30 +++++++++++++++++++++++++++ src/Containers/DB.php | 2 +- src/Containers/ErrorHandlers.php | 4 ++-- src/Containers/Logger.php | 2 +- src/Containers/Services.php | 12 +++++------ src/Containers/View.php | 2 +- src/DocsApp.php | 4 ++-- src/Helpers/SettingsParser.php | 4 ++-- src/Middlewares/RequestMiddleware.php | 4 ++-- src/Model/PageRequest.php | 2 +- src/Twig/DocExtensions.php | 2 +- src/Views/Base.php | 4 ++-- src/Views/Doc.php | 4 ++-- src/Views/Error.php | 4 ++-- src/Views/NotFound.php | 4 ++-- src/Views/Search.php | 4 ++-- src/Views/Stats/NotFoundRequests.php | 4 ++-- src/Views/Stats/Searches.php | 4 ++-- tests/Functional/DocTest.php | 4 ++-- tests/Functional/HomepageTest.php | 6 +++--- 21 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 refactor.py diff --git a/composer.json b/composer.json index df397fe5..098de222 100644 --- a/composer.json +++ b/composer.json @@ -18,19 +18,21 @@ "ext-sqlite3": "*", "ext-fileinfo": "*", "ext-mbstring": "*", - "slim/slim": "^3.1", - "slim/php-view": "^2.0", - "slim/twig-view": "^2.3", + "slim/slim": "^4.0", + "slim/psr7": "^1.4", + "php-di/php-di": "^6.3", + "slim/twig-view": "^3.2", "spatie/yaml-front-matter": "^2.0", "league/commonmark": "^1.5", "caseyamcl/toc": "^3.0", - "vlucas/phpdotenv": "^3.3", + "vlucas/phpdotenv": "^5.0", "symfony/console": "^5.2", "symfony/process": "^5.2", "voku/stop-words": "^2.0" }, "require-dev": { - "ext-intl": "*" + "ext-intl": "*", + "phpunit/phpunit": "^9.5" }, "autoload": { "psr-4": { diff --git a/refactor.py b/refactor.py new file mode 100644 index 00000000..379776db --- /dev/null +++ b/refactor.py @@ -0,0 +1,30 @@ +import os +import re + +def process_file(filepath): + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # 1. Update PSR-7 Request/Response Interfaces + content = content.replace("use Slim\\Http\\Request;", "use Psr\\Http\\Message\\ServerRequestInterface as Request;") + content = content.replace("use Slim\\Http\\Response;", "use Psr\\Http\\Message\\ResponseInterface as Response;") + + # 2. Container DI array access -> set() + # Replace $container['key'] = function ($container) { with $container->set('key', function($container) { + content = re.sub(r"\$container\['([^']+)'\]\s*=\s*(function\s*\()", r"$container->set('\1', \2", content) + # Replace $container[Class::class] = function... + content = re.sub(r"\$container\[([^\]]+::class)\]\s*=\s*(function\s*\()", r"$container->set(\1, \2", content) + + # 3. Fix returning closures inside set() to just be the closure + # (Actually php-di uses ->set(key, function...) so the above regex handles the start. We just need a closing parenthesis if we changed it... wait, regex replacement for `set` requires closing `);` at the end of the closure block. This is hard to do with simple regex. Let's just fix the container files manually since there are only 5). + + with open(filepath, 'w', encoding='utf-8') as f: + f.write(content) + +src_dir = r"C:\Users\Khalid\Desktop\bounty\DocsApp\src" +for root, dirs, files in os.walk(src_dir): + for file in files: + if file.endswith('.php'): + process_file(os.path.join(root, file)) + +print("Applied Regex replacements!") diff --git a/src/Containers/DB.php b/src/Containers/DB.php index 2ee98270..7a2b1a2f 100644 --- a/src/Containers/DB.php +++ b/src/Containers/DB.php @@ -8,7 +8,7 @@ class DB { public static function load(ContainerInterface $container) { - $container['db'] = function (ContainerInterface $container) { + $container->set('db', function (ContainerInterface $container) { $dir = getenv('BASE_DIRECTORY') . 'db/db.sqlite'; $db = new \PDO('sqlite:' . $dir); $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); diff --git a/src/Containers/ErrorHandlers.php b/src/Containers/ErrorHandlers.php index f7cbb450..6ae0a288 100644 --- a/src/Containers/ErrorHandlers.php +++ b/src/Containers/ErrorHandlers.php @@ -11,14 +11,14 @@ class ErrorHandlers { public static function load(Container $container) { - $container['notFoundHandler'] = function ($container) { + $container->set('notFoundHandler', function ($container) { return function ($request, $response) use ($container) { $pageNotFound = new NotFound($container); return $pageNotFound->get($request, $response); }; }; - $container['errorHandler'] = $container['phpErrorHandler'] = function ($container) { + $container['errorHandler'] = $container->set('phpErrorHandler', function ($container) { return function ($request, $response, $exception) use ($container) { $pageNotFound = new Error($container, $exception); diff --git a/src/Containers/Logger.php b/src/Containers/Logger.php index 3ae452b6..1e1051a0 100644 --- a/src/Containers/Logger.php +++ b/src/Containers/Logger.php @@ -11,7 +11,7 @@ class Logger { public static function load(Container $container) { - $container['logger'] = function () { + $container->set('logger', function () { $logger = new MonologLogger('modx-docs'); diff --git a/src/Containers/Services.php b/src/Containers/Services.php index e51bec55..ee8ee3ca 100644 --- a/src/Containers/Services.php +++ b/src/Containers/Services.php @@ -15,37 +15,37 @@ class Services { public static function load(Container $container): void { - $container[FilePathService::class] = function () { + $container->set(FilePathService::class, function () { return new FilePathService(); }; - $container[DocumentService::class] = function (Container $container) { + $container->set(DocumentService::class, function (Container $container) { return new DocumentService( $container->get(FilePathService::class), $container->get('db') ); }; - $container[VersionsService::class] = function (Container $container) { + $container->set(VersionsService::class, function (Container $container) { return new VersionsService( $container->get('router') ); }; - $container[TranslationService::class] = function (Container $container) { + $container->set(TranslationService::class, function (Container $container) { return new TranslationService( $container->get('db'), $container->get('router') ); }; - $container[SearchService::class] = function (Container $container) { + $container->set(SearchService::class, function (Container $container) { return new SearchService( $container->get('db'), $container->get(DocumentService::class) ); }; - $container[IndexService::class] = function (Container $container) { + $container->set(IndexService::class, function (Container $container) { return new IndexService( $container->get('db'), $container->get(DocumentService::class) diff --git a/src/Containers/View.php b/src/Containers/View.php index 569f678f..8a46a3c9 100644 --- a/src/Containers/View.php +++ b/src/Containers/View.php @@ -15,7 +15,7 @@ class View public static function load(ContainerInterface $container) { - $container['view'] = function (ContainerInterface $container) { + $container->set('view', function (ContainerInterface $container) { $request = $container->get('request'); $router = $container->get('router'); diff --git a/src/DocsApp.php b/src/DocsApp.php index b307491b..a9aac8a6 100644 --- a/src/DocsApp.php +++ b/src/DocsApp.php @@ -7,8 +7,8 @@ use MODXDocs\Views\Stats\NotFoundRequests; use MODXDocs\Views\Stats\Searches; use Slim\App; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use MODXDocs\Containers\View; use MODXDocs\Containers\ErrorHandlers; diff --git a/src/Helpers/SettingsParser.php b/src/Helpers/SettingsParser.php index b9b325a5..86d0ec62 100644 --- a/src/Helpers/SettingsParser.php +++ b/src/Helpers/SettingsParser.php @@ -13,8 +13,8 @@ public function __construct() { $baseDir = dirname(dirname(__DIR__)) . '/'; $dotFile = static::getDotFile($baseDir); - $dotEnv = Dotenv::create($baseDir, $dotFile); - $dotEnv->load(); + $dotEnv = Dotenv::createUnsafeImmutable($baseDir, $dotFile); + $dotEnv->safeLoad(); } public function getSlimConfig() diff --git a/src/Middlewares/RequestMiddleware.php b/src/Middlewares/RequestMiddleware.php index 0b8a41f1..d6cebea3 100644 --- a/src/Middlewares/RequestMiddleware.php +++ b/src/Middlewares/RequestMiddleware.php @@ -2,8 +2,8 @@ namespace MODXDocs\Middlewares; -use Slim\Http\Response; -use Slim\Http\Request; +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; class RequestMiddleware { diff --git a/src/Model/PageRequest.php b/src/Model/PageRequest.php index 629d99a8..9ac5e9b8 100644 --- a/src/Model/PageRequest.php +++ b/src/Model/PageRequest.php @@ -4,7 +4,7 @@ namespace MODXDocs\Model; use MODXDocs\Services\VersionsService; -use Slim\Http\Request; +use Psr\Http\Message\ServerRequestInterface as Request; class PageRequest { private $version; diff --git a/src/Twig/DocExtensions.php b/src/Twig/DocExtensions.php index 3e88646b..bc9b313b 100644 --- a/src/Twig/DocExtensions.php +++ b/src/Twig/DocExtensions.php @@ -5,7 +5,7 @@ use MODXDocs\Views\Base; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; -use Slim\Http\Request; +use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Interfaces\RouterInterface; class DocExtensions extends AbstractExtension diff --git a/src/Views/Base.php b/src/Views/Base.php index cd54c2d2..d7668820 100644 --- a/src/Views/Base.php +++ b/src/Views/Base.php @@ -5,8 +5,8 @@ use MODXDocs\Model\PageRequest; use MODXDocs\Services\CacheService; use MODXDocs\Services\VersionsService; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use Slim\Views\Twig; use Psr\Container\ContainerInterface; diff --git a/src/Views/Doc.php b/src/Views/Doc.php index 858fb75a..051d9c48 100644 --- a/src/Views/Doc.php +++ b/src/Views/Doc.php @@ -7,8 +7,8 @@ use MODXDocs\Model\PageRequest; use MODXDocs\Services\TranslationService; use Psr\Container\ContainerInterface; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use MODXDocs\Services\DocumentService; use MODXDocs\Services\VersionsService; diff --git a/src/Views/Error.php b/src/Views/Error.php index 75236d1a..c53d4f83 100644 --- a/src/Views/Error.php +++ b/src/Views/Error.php @@ -4,8 +4,8 @@ use MODXDocs\Model\PageRequest; use Psr\Container\ContainerInterface; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use MODXDocs\Exceptions\RedirectNotFoundException; use MODXDocs\Helpers\Redirector; diff --git a/src/Views/NotFound.php b/src/Views/NotFound.php index 6b9218b6..3b7d3342 100644 --- a/src/Views/NotFound.php +++ b/src/Views/NotFound.php @@ -9,8 +9,8 @@ use MODXDocs\Services\VersionsService; use PDO; use Psr\Container\ContainerInterface; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use MODXDocs\Exceptions\RedirectNotFoundException; use MODXDocs\Helpers\Redirector; diff --git a/src/Views/Search.php b/src/Views/Search.php index 0a91fd52..460bcaf5 100644 --- a/src/Views/Search.php +++ b/src/Views/Search.php @@ -9,8 +9,8 @@ use MODXDocs\Services\SearchService; use MODXDocs\Services\VersionsService; use Psr\Container\ContainerInterface; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use Slim\Router; class Search extends Base diff --git a/src/Views/Stats/NotFoundRequests.php b/src/Views/Stats/NotFoundRequests.php index 46291c4f..c03bfa5e 100644 --- a/src/Views/Stats/NotFoundRequests.php +++ b/src/Views/Stats/NotFoundRequests.php @@ -6,8 +6,8 @@ use MODXDocs\Services\CacheService; use MODXDocs\Views\Base; use Psr\Container\ContainerInterface; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use Slim\Router; class NotFoundRequests extends Base diff --git a/src/Views/Stats/Searches.php b/src/Views/Stats/Searches.php index bf3ef521..f3a37d9b 100644 --- a/src/Views/Stats/Searches.php +++ b/src/Views/Stats/Searches.php @@ -7,8 +7,8 @@ use MODXDocs\Services\CacheService; use MODXDocs\Views\Base; use Psr\Container\ContainerInterface; -use Slim\Http\Request; -use Slim\Http\Response; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use Slim\Router; class Searches extends Base diff --git a/tests/Functional/DocTest.php b/tests/Functional/DocTest.php index 7eeced98..ae0750e9 100644 --- a/tests/Functional/DocTest.php +++ b/tests/Functional/DocTest.php @@ -12,7 +12,7 @@ public function testGetGettingStarted() : void $response = $this->runApp('GET', '/2.x/en/getting-started'); $this->assertEquals(200, $response->getStatusCode()); - $this->assertContains('Welcome to MODX Revolution', (string)$response->getBody()); - $this->assertNotContains('WordPress', (string)$response->getBody()); + $this->assertStringContainsString('Welcome to MODX Revolution', (string)$response->getBody()); + $this->assertStringNotContainsString('WordPress', (string)$response->getBody()); } } diff --git a/tests/Functional/HomepageTest.php b/tests/Functional/HomepageTest.php index fb87989e..61fa3044 100644 --- a/tests/Functional/HomepageTest.php +++ b/tests/Functional/HomepageTest.php @@ -15,8 +15,8 @@ public function testGetHomepageWithoutName() : void $response = $this->runApp('GET', '/'); $this->assertEquals(200, $response->getStatusCode()); - $this->assertContains('Creative Freedom', (string)$response->getBody()); - $this->assertNotContains('Hello', (string)$response->getBody()); + $this->assertStringContainsString('Creative Freedom', (string)$response->getBody()); + $this->assertStringNotContainsString('Hello', (string)$response->getBody()); } /** @@ -27,6 +27,6 @@ public function testPostHomepageNotAllowed() : void $response = $this->runApp('POST', '/', ['test']); $this->assertEquals(405, $response->getStatusCode()); - $this->assertContains('Method not allowed', (string)$response->getBody()); + $this->assertStringContainsString('Method not allowed', (string)$response->getBody()); } } From a03801356a0207dbcdd70ba517964a18d699b002 Mon Sep 17 00:00:00 2001 From: KHHH2312 Date: Mon, 1 Jun 2026 16:04:51 +0100 Subject: [PATCH 2/2] fix: address copilot review comments for dotEnv and container syntax --- refactor.py | 30 ------------------------------ src/Containers/DB.php | 6 +++--- src/Containers/ErrorHandlers.php | 7 +++++-- src/Containers/View.php | 6 +++--- src/Helpers/SettingsParser.php | 2 +- 5 files changed, 12 insertions(+), 39 deletions(-) delete mode 100644 refactor.py diff --git a/refactor.py b/refactor.py deleted file mode 100644 index 379776db..00000000 --- a/refactor.py +++ /dev/null @@ -1,30 +0,0 @@ -import os -import re - -def process_file(filepath): - with open(filepath, 'r', encoding='utf-8') as f: - content = f.read() - - # 1. Update PSR-7 Request/Response Interfaces - content = content.replace("use Slim\\Http\\Request;", "use Psr\\Http\\Message\\ServerRequestInterface as Request;") - content = content.replace("use Slim\\Http\\Response;", "use Psr\\Http\\Message\\ResponseInterface as Response;") - - # 2. Container DI array access -> set() - # Replace $container['key'] = function ($container) { with $container->set('key', function($container) { - content = re.sub(r"\$container\['([^']+)'\]\s*=\s*(function\s*\()", r"$container->set('\1', \2", content) - # Replace $container[Class::class] = function... - content = re.sub(r"\$container\[([^\]]+::class)\]\s*=\s*(function\s*\()", r"$container->set(\1, \2", content) - - # 3. Fix returning closures inside set() to just be the closure - # (Actually php-di uses ->set(key, function...) so the above regex handles the start. We just need a closing parenthesis if we changed it... wait, regex replacement for `set` requires closing `);` at the end of the closure block. This is hard to do with simple regex. Let's just fix the container files manually since there are only 5). - - with open(filepath, 'w', encoding='utf-8') as f: - f.write(content) - -src_dir = r"C:\Users\Khalid\Desktop\bounty\DocsApp\src" -for root, dirs, files in os.walk(src_dir): - for file in files: - if file.endswith('.php'): - process_file(os.path.join(root, file)) - -print("Applied Regex replacements!") diff --git a/src/Containers/DB.php b/src/Containers/DB.php index 7a2b1a2f..7f666d15 100644 --- a/src/Containers/DB.php +++ b/src/Containers/DB.php @@ -6,15 +6,15 @@ class DB { - public static function load(ContainerInterface $container) + public static function load(\DI\Container $container) { - $container->set('db', function (ContainerInterface $container) { + $container->set('db', function (\Psr\Container\ContainerInterface $container) { $dir = getenv('BASE_DIRECTORY') . 'db/db.sqlite'; $db = new \PDO('sqlite:' . $dir); $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $db->setAttribute(\PDO::ATTR_TIMEOUT, 10000); $db->exec('PRAGMA busy_timeout = 15000'); return $db; - }; + }); } } diff --git a/src/Containers/ErrorHandlers.php b/src/Containers/ErrorHandlers.php index 6ae0a288..ba591a03 100644 --- a/src/Containers/ErrorHandlers.php +++ b/src/Containers/ErrorHandlers.php @@ -17,13 +17,16 @@ public static function load(Container $container) return $pageNotFound->get($request, $response); }; - }; - $container['errorHandler'] = $container->set('phpErrorHandler', function ($container) { + }); + + $errorHandlerFn = function ($container) { return function ($request, $response, $exception) use ($container) { $pageNotFound = new Error($container, $exception); return $pageNotFound->get($request, $response); }; }; + $container->set('errorHandler', $errorHandlerFn); + $container->set('phpErrorHandler', $errorHandlerFn); } } diff --git a/src/Containers/View.php b/src/Containers/View.php index 8a46a3c9..3e96cd2b 100644 --- a/src/Containers/View.php +++ b/src/Containers/View.php @@ -13,9 +13,9 @@ class View { const BASE_REQUEST_HANDLER = 'index.php'; - public static function load(ContainerInterface $container) + public static function load(\DI\Container $container) { - $container->set('view', function (ContainerInterface $container) { + $container->set('view', function (\Psr\Container\ContainerInterface $container) { $request = $container->get('request'); $router = $container->get('router'); @@ -32,6 +32,6 @@ public static function load(ContainerInterface $container) $view->addExtension(new DocExtensions($router, $request)); return $view; - }; + }); } } diff --git a/src/Helpers/SettingsParser.php b/src/Helpers/SettingsParser.php index 86d0ec62..2212b6bf 100644 --- a/src/Helpers/SettingsParser.php +++ b/src/Helpers/SettingsParser.php @@ -14,7 +14,7 @@ public function __construct() $baseDir = dirname(dirname(__DIR__)) . '/'; $dotFile = static::getDotFile($baseDir); $dotEnv = Dotenv::createUnsafeImmutable($baseDir, $dotFile); - $dotEnv->safeLoad(); + $dotEnv->load(); } public function getSlimConfig()