diff --git a/.gitignore b/.gitignore index cab49048..b67a9030 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ .idea -*.ben -*.txt node_modules dist .vscode diff --git a/Sources/composer.json b/Sources/composer.json index 72979bf7..f60dc811 100755 --- a/Sources/composer.json +++ b/Sources/composer.json @@ -3,19 +3,34 @@ "autoload": { "psr-4": { "Hearttrack\\": "src/", - "Console\\":"src/Console" + "Console\\": "src/console", + "App\\": "src/app", + "App\\Router\\": "src/app/router", + "App\\Controller\\": "src/app/controller", + "App\\Router\\Response\\" : "src/app/router/response", + "App\\Router\\Middleware\\" : "src/app/router/middleware", + "App\\Router\\Request\\" : "src/app/router/request", + "Shared\\": "src/shared/", + "Shared\\Exception\\": "src/shared/exception", + "Shared\\Attributes\\": "src/shared/attributes", + "App\\Views\\Directives\\" : "src/app/views/directives" + + } }, "require": { - "twig/twig": "^3.0" + "twig/twig": "^3.0", + "altorouter/altorouter": "1.1.0", + "vlucas/phpdotenv": "^5.5", + "psr/container": "^2.0" + }, "require-dev": { "phpunit/phpunit": "*" }, "scripts": { "dev": "php -S localhost:8080 -t public -d display_errors=1 -d error_reporting=E_ALL", - "dev:console": "export APP_ENV=console && php public/index.php" - - } - + "dev:console": "export APP_ENV=console && php public/index.php", + "dev:html" : "export APP_ENV=html && php -S localhost:8080 -t public -d display_errors=1 -d error_reporting=E_ALL" + } } diff --git a/Sources/composer.lock b/Sources/composer.lock index bfd52d0b..42ce6584 100755 --- a/Sources/composer.lock +++ b/Sources/composer.lock @@ -4,8 +4,253 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b084bad56d99d613841073027e5f5e7e", + "content-hash": "c8acec96dcc23612616eedff9886528e", "packages": [ + { + "name": "altorouter/altorouter", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dannyvankooten/AltoRouter.git", + "reference": "09d9d946c546bae6d22a7654cdb3b825ffda54b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dannyvankooten/AltoRouter/zipball/09d9d946c546bae6d22a7654cdb3b825ffda54b4", + "reference": "09d9d946c546bae6d22a7654cdb3b825ffda54b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "AltoRouter.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Danny van Kooten", + "email": "dannyvankooten@gmail.com", + "homepage": "http://dannyvankooten.com/" + }, + { + "name": "Koen Punt", + "homepage": "https://github.com/koenpunt" + }, + { + "name": "niahoo", + "homepage": "https://github.com/niahoo" + } + ], + "description": "A lightning fast router for PHP", + "homepage": "https://github.com/dannyvankooten/AltoRouter", + "keywords": [ + "lightweight", + "router", + "routing" + ], + "support": { + "issues": "https://github.com/dannyvankooten/AltoRouter/issues", + "source": "https://github.com/dannyvankooten/AltoRouter/tree/master" + }, + "time": "2014-04-16T09:44:40+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:16:48+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.2", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2023-11-12T21:59:55+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.28.0", @@ -171,28 +416,112 @@ ], "time": "2023-07-28T09:04:16+00:00" }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-26T09:26:14+00:00" + }, { "name": "twig/twig", - "version": "v3.7.1", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3" + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" }, "type": "library", "autoload": { @@ -228,7 +557,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.1" + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" }, "funding": [ { @@ -240,7 +569,91 @@ "type": "tidelift" } ], - "time": "2023-08-28T11:09:02+00:00" + "time": "2023-11-21T18:54:41+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2023-11-12T22:43:29+00:00" } ], "packages-dev": [ @@ -1928,16 +2341,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -1966,7 +2379,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -1974,7 +2387,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], diff --git a/Sources/config/config.php b/Sources/config/config.php index 24a01547..080b042c 100755 --- a/Sources/config/config.php +++ b/Sources/config/config.php @@ -1,16 +1,20 @@ safeLoad(); +// apenrently getEnv is not a good thing cause // const DB_HOST = $_ENV['DB_HOST'] ?? 'localhost'; // const DB_DATABASE = $_ENV['DB_DATABASE'] ?? 'heartTrack'; // const DB_USER = $_ENV['DB_USER'] ?? 'toto'; // const DB_PASSWORD = $_ENV['DB_PASSWORD'] ?? 'achanger'; -// const APP_ENV = $_ENV['APP_ENV'] ?? 'development'; -const DB_HOST ='localhost'; -const DB_DATABASE = 'heartTrack'; +define("APP_ENV", getenv('APP_ENV') ?? 'development'); + +const DB_HOST = 'localhost'; +const DB_DATABASE = 'heartTrack'; const DB_USER = 'toto'; -const DB_PASSWORD = 'achanger'; -const APP_ENV = 'development'; +const DB_PASSWORD = 'achanger'; const DSN = "mysql:host=" . DB_HOST . ";dbname=" . DB_DATABASE; diff --git a/Sources/public/index.php b/Sources/public/index.php index 0a70f945..d38ebc5b 100755 --- a/Sources/public/index.php +++ b/Sources/public/index.php @@ -1,9 +1,36 @@ registerService(IArgumentResolver::class, ArgumentControllerResolver::class); + +$appFactory->registerService(\Twig\Loader\LoaderInterface::class, function() { + return new FilesystemLoader(__DIR__ . '/../src/app/views/Templates'); +}); + +$appFactory->registerService(\Twig\Environment::class,\Twig\Environment::class); + +// Connexion à la base de données +// $databaseContext = DatabaseContext::getInstance(); + +$appFactory->AddControllers(); +$app = $appFactory->create(); +if (!is_null($app)){ + // Ajout des Middleware + /*$app->use(new LoggingMiddleware());*/ + + $app->mapControllers(); + $app->run(RequestFactory::createFromGlobals()); } + diff --git a/Sources/src/app/App.php b/Sources/src/app/App.php new file mode 100644 index 00000000..c80e71f3 --- /dev/null +++ b/Sources/src/app/App.php @@ -0,0 +1,158 @@ +appName = $appName; + $this->version = $version; + $this->container = $diContainer; + $this->router = new Router(""); + $this->frontController = new FrontController($this->router,$this->container); + } + + public function use(IHttpMiddleware $middleware) + { + if ($this->middlewarePipeline === null) { + $this->middlewarePipeline = $middleware; + } else { + // Chain the new middleware to the end of the existing pipeline + $currentMiddleware = $this->middlewarePipeline; + while ($currentMiddleware->getNext() !== null) { + $currentMiddleware = $currentMiddleware->getNext(); + } + $currentMiddleware->setNext($middleware); + } + } + + public function getAppName(): string + { + return $this->appName; + } + +/* public function twigConfigure(array $extensionClassNames = []): void + { + if (!$this->container->has(\Twig\Environment::class)) { + throw new \LogicException('You cannot use the "twigConfigure" method if the Twig Bundle is not available. Try running "composer require twig/twig".'); + } + + $twigEnvironment = $this->container->get(\Twig\Environment::class); + + if (empty($extensionClassNames)) { + $twigEnvironment->addExtension(new Navigate($this->router)); + + } else { + foreach ($extensionClassNames as $extensionClassName) { + if (class_exists($extensionClassName)) { + $extensionInstance = new $extensionClassName(); + if ($extensionInstance instanceof \Twig\Extension\ExtensionInterface) { + $twigEnvironment->addExtension($extensionInstance); + } else { + throw new \InvalidArgumentException("Class '$extensionClassName' does not implement Twig\Extension\ExtensionInterface."); + } + } else { + throw new \InvalidArgumentException("Class '$extensionClassName' does not exist."); + } + } + } + }*/ + + + + public function getVersion(): int + { + return $this->version; + } + + public function run(IRequest $request) + { + if ($this->middlewarePipeline == null) { + return $this->frontController->dispatch($request); + } + // Exécutez le middleware en utilisant le pipeline + return $this->middlewarePipeline->handle($request, function($request) { + // Logique de gestion principale de la requête ici + $this->frontController->dispatch($request); + }); + + } + + /** + * @throws \ReflectionException + */ + public function mapControllers(): void + { + $classes = $this->container->getAllRegisteredClassNames(); + + foreach ($classes as $class) { + if ($this->isController($class)) { + $this->mapControllerRoutes($class); + } + } + } + /** + * @throws \ReflectionException + */ + function mapControllerRoutes(string $controllerClass): void + { + $reflectionClass = new \ReflectionClass($controllerClass); + $attributes = $reflectionClass->getAttributes(RouteAttribute::class); + $prefix = ''; + if (!empty($attributes)) { + + $prefix = $attributes[0]->newInstance()->getPath(); + } + + foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + + foreach ($method->getAttributes(RouteAttribute::class) as $attribute) { + + /** @var RouteAttribute $route */ + $route = $attribute->newInstance(); + + $this->router->addControllerRoute( + implode('|', $route->getMethods()), + $prefix . $route->getPath(), + $controllerClass, + $method->getName(), + $route->getName() + ); + } + } + } + + + function isController(string $class): bool + { + $reflectionClass = new \ReflectionClass($class); + return $reflectionClass->isSubclassOf(BaseController::class); + } +} + diff --git a/Sources/src/app/AppCreator.php b/Sources/src/app/AppCreator.php new file mode 100644 index 00000000..d2ac0de6 --- /dev/null +++ b/Sources/src/app/AppCreator.php @@ -0,0 +1,91 @@ +container = new Container; + } + + public function registerService(string $serviceId, callable|string $service): self + { + $this->container->set($serviceId, $service); + $this->services[] = $serviceId; + return $this; + } + + /** + * Create an instance or perform actions based on the current application environment. + * + * @return App|null An instance of the App class in the 'development' environment, or null in other environments. + */ + public function create(): ?App + { + // Check the application environment + switch (APP_ENV) { + case 'console': + // Load the Console.php file in case of the 'console' environment + require_once __DIR__ . '/../console/Console.php'; + break; + case 'development': + // Create a new instance of the App class in the 'development' environment + return new App("HeartTrack", 1, $this->container); + break; + case 'html': + // Load the index.test.php file in case of the 'html' environment + require_once __DIR__ . '/index.test.php'; + break; + default: + // Handle other environment cases here, if necessary + break; + } + return null; + } + + + + function AddControllers($namespacePrefix = 'App\Controller', $pathToControllers = __DIR__ . '/controller'): self + { + $controllerFiles = glob($pathToControllers . '/*.php'); + + foreach ($controllerFiles as $file) { + // Get class name from file name + $class = basename($file, '.php'); + $fullClassName = $namespacePrefix . '\\' . $class; + if (!class_exists($fullClassName)) { + continue; + } + + // Use reflection to check if class extends BaseController + $reflectionClass = new \ReflectionClass($fullClassName); + if ($reflectionClass->isSubclassOf(BaseController::class)) { + // Register in DI container + $this->container->set($fullClassName, function () use ($fullClassName) { + $controllerInstance = new $fullClassName(); + $controllerInstance->setContainer($this->container); + return $controllerInstance; + }); + + } + } + + return $this; + } + + public function getServiceRegistered(): array + { + return $this->services; + } +} + + diff --git a/Sources/src/app/Container.php b/Sources/src/app/Container.php new file mode 100644 index 00000000..b254271f --- /dev/null +++ b/Sources/src/app/Container.php @@ -0,0 +1,109 @@ +has($id)) { + $entry = $this->entries[$id]; + if (is_callable($entry)) { + + return $entry($this); + } + + $id = $entry; + } + + return $this->resolve($id); + } + + + public function has(string $id): bool + { + return isset($this->entries[$id]); + } + + public function set(string $id, callable|string $concrete): void + { + $this->entries[$id] = $concrete; + + } + + public function resolve(string $id) + { + // 1. Inspect the class that we are trying to get from the container + try { + $reflectionClass = new \ReflectionClass($id); + } catch (\ReflectionException $e) { + throw new \Exception($e->getMessage(), $e->getCode(), $e); + } + if (!$reflectionClass->isInstantiable()) { + throw new \Exception('Class "' . $id . '" is not instantiable'); + } + + // 2. Inspect the constructor of the class + $constructor = $reflectionClass->getConstructor(); + + if (!$constructor) { + return new $id; + } + + // 3. Inspect the constructor parameters (dependencies) + $parameters = $constructor->getParameters(); + + if (!$parameters) { + return new $id; + } + + // 4. If the constructor parameter is a class then try to resolve that class using the container + $dependencies = array_map( + function (\ReflectionParameter $param) use ($id) { + $name = $param->getName(); + $type = $param->getType(); + + // Check for a default value + if ($param->isDefaultValueAvailable()) { + return $param->getDefaultValue(); + } + + if (!$type) { + throw new \Exception( + 'Failed to resolve class "' . $id . '" because param "' . $name . '" is missing a type hint' + ); + } + + if ($type instanceof \ReflectionUnionType) { + throw new \Exception( + 'Failed to resolve class "' . $id . '" because of union type for param "' . $name . '"' + ); + } + + if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) { + return $this->get($type->getName()); + } + + throw new \Exception( + 'Failed to resolve class "' . $id . '" because invalid param "' . $name . '"' + ); + }, + $parameters + ); + + return $reflectionClass->newInstanceArgs($dependencies); + } + + public function getAllRegisteredClassNames(): array + { + return array_keys($this->entries); + } +} \ No newline at end of file diff --git a/Sources/src/app/controller/BaseController.php b/Sources/src/app/controller/BaseController.php new file mode 100644 index 00000000..5e321b07 --- /dev/null +++ b/Sources/src/app/controller/BaseController.php @@ -0,0 +1,74 @@ +container = $container; + } + + abstract public function index(): Response; + + protected function renderView(string $view, array $parameters = []): string + { + if (!$this->container->has(\Twig\Environment::class)) { + throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); + } + + return $this->container->get(\Twig\Environment::class)->render($view, $parameters); + } + /** + * Renders a view. + * + * If an invalid form is found in the list of parameters, a 422 status code is returned. + * Forms found in parameters are auto-cast to form views. + */ + protected function render(string $view, array $parameters = [], Response $response = null): Response + { + $content = $this->renderView($view, $parameters); + $response ??= new Response(); + + /* if (200 === $response->getStatusCode()) { + foreach ($parameters as $v) { + if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) { + $response->setStatusCode(422); + break; + } + } + }*/ + + $response->setContent($content); + + return $response; + } + + protected function redirect(string $url, int $status = 302): RedirectResponse + { + return new RedirectResponse($url, $status); + } + + protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse + { + return $this->redirect($this->generateUrl($route, $parameters), $status); + } + + /* + * TODO : Should hanle ierror if the route is not existing + * */ + protected function generateUrl(string $route, array $parameters = []): string + { + return $this->container->get(\App\Router\Router::class)->generate($route, $parameters); + } + + +} \ No newline at end of file diff --git a/Sources/src/app/controller/Controller.php b/Sources/src/app/controller/Controller.php new file mode 100644 index 00000000..29ad5cd4 --- /dev/null +++ b/Sources/src/app/controller/Controller.php @@ -0,0 +1,141 @@ +render('./page/home.html.twig',[ + 'pp' => "test2", + 'user' => "Ladmion le Cochon", + 'role' => "Athlète", + 'friendship' => [], + 'analyzes' => [], + 'mails' => [], + 'users' => [], + 'infoUser' => [], + 'exos' => [], + 'member' => [] + ]); + } + + #[Route(path: '/exercice', name: 'exercice', methods: ['GET'])] // 8 + public function exercice(): Response + { + return $this->render('./page/exercice.html.twig',[ + 'pp' => "test2", + 'user' => "Ladmion le Cochon", + 'role' => "Athlète", + 'friendship' => [], + 'analyzes' => [], + 'mails' => [], + 'users' => [], + 'infoUser' => [], + 'exos' => [], + 'member' => [] + ]); + } + + #[Route(path: '/friend', name: 'friend', methods: ['GET'])] // 8 + public function friend(): Response + { + return $this->render('./page/friend.html.twig',[ + 'pp' => "test2", + 'user' => "Ladmion le Cochon", + 'role' => "Athlète", + 'friendship' => [], + 'analyzes' => [], + 'mails' => [], + 'users' => [], + 'infoUser' => [], + 'exos' => [], + 'member' => [] + ]); + } + + #[Route(path: '/coaching', name: 'coaching', methods: ['GET'])] // 8 + public function coaching(): Response + { + return $this->render('./page/coaching.html.twig',[ + 'pp' => "test2", + 'user' => "Ladmion le Cochon", + 'role' => "Athlète", + 'friendship' => [], + 'analyzes' => [], + 'mails' => [], + 'users' => [], + 'infoUser' => [], + 'exos' => [], + 'member' => [] + ]); + } + + #[Route(path: '/mail', name: 'mail', methods: ['GET'])] // 8 + public function mail(): Response + { + return $this->render('./page/mail.html.twig',[ + 'pp' => "test2", + 'user' => "Ladmion le Cochon", + 'role' => "Athlète", + 'friendship' => [], + 'analyzes' => [], + 'mails' => [], + 'users' => [], + 'infoUser' => [], + 'exos' => [], + 'member' => [] + ]); + } + + #[Route(path: '/import', name: 'import', methods: ['GET'])] // 8 + public function import(): Response + { + return $this->render('./page/import.html.twig',[ + 'pp' => "test2", + 'user' => "Ladmion le Cochon", + 'role' => "Athlète", + 'friendship' => [], + 'analyzes' => [], + 'mails' => [], + 'users' => [], + 'infoUser' => [], + 'exos' => [], + 'member' => [] + ]); + } + + + + + #[Route(path: '/hello', name: 'hello', methods: ['GET'])] + public function hello(): Response + { + return new Response('Hello'); + } + + #[Route(path: '/hi', name: 'hi', methods: ['GET'])] + public function hi(string $name, IRequest $req): Response + { + return new Response($name); + } + +} + + + diff --git a/Sources/src/app/controller/FrontController.php b/Sources/src/app/controller/FrontController.php new file mode 100644 index 00000000..5fa64fac --- /dev/null +++ b/Sources/src/app/controller/FrontController.php @@ -0,0 +1,73 @@ +router = $router; + $this->container = $container; + + } + + public function dispatch(IRequest $request) { + try { + $match = $this->router->match($request); + if (!is_null($match)) { + $method = $match['target']; + + $controller = $this->getController($match['target']); + $callable = array($controller,$method[1]); + $request->addToBody($match['params']); + + if (!is_callable($callable)){ + throw new NotImplementedException('Controller target is not callable' .'Handle when route target is not a callable : not handle'); + } + $argumentResolver = $this->container->get(IArgumentResolver::class); + $arguments = $argumentResolver->getArguments($request, $callable); + // check role + $response = call_user_func_array($callable, $arguments); + + // should handle response properly like if it's a HTML, STING, JSON,.... + $response->send(); + } else { + $this->handleError(404, "Page not found"); + } + } catch (NotFoundHttpException $e) { + $this->handleError(404, $e->getMessage()); + } + } + + private function getController($controllerSpec) { + if (is_array($controllerSpec)) { + $controllerName = $controllerSpec[0]; + } else { + $controllerName = $controllerSpec; + } + + return $this->container->get($controllerName); + } + + // TODO : Don't work need Antoine help + private function handleError(int $statusCode, $message) : void { + if (!$this->container->has(\Twig\Environment::class)) { + throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); + } + + $response = new Response($this->container->get(\Twig\Environment::class)->render('./errorbase.html.twig',['title'=> $message , "nb" => $statusCode, "name" => $message, "descr" => $message ]),$statusCode); + $response->send(); + } + +} + +?> diff --git a/Sources/src/app/router/Route.php b/Sources/src/app/router/Route.php new file mode 100644 index 00000000..97727e68 --- /dev/null +++ b/Sources/src/app/router/Route.php @@ -0,0 +1,94 @@ +path = $path; + $this->callable = $callable; + $this->name = $name; + } + + /** + * Gets the name of the route. + * + * @return string|null The name of the route. + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * Sets the name of the route. + * + * @param string|null $name The name to set. + */ + public function setName(?string $name): void + { + $this->name = $name; + } + + /** + * Gets the callable associated with the route. + * + * @return callable The callable for this route. + */ + public function getCallable() + { + return $this->callable; + } + + /** + * Gets the path for the route. + * + * @return string The path for the route. + */ + public function getPath(): string + { + return $this->path; + } + + /** + * Sets the callable for the route. + * + * @param callable $callable The callable to set for this route. + */ + public function setCallable(callable $callable) + { + $this->callable = $callable; + } +} diff --git a/Sources/src/app/router/Router.php b/Sources/src/app/router/Router.php new file mode 100644 index 00000000..1461e7cf --- /dev/null +++ b/Sources/src/app/router/Router.php @@ -0,0 +1,116 @@ +path = $path; + $this->routes = new \AltoRouter(); + } + + /** + * Adds a new Route to the collection. + * + * @param string $method The HTTP method. + * @param Route $route The route object. + * @throws \InvalidArgumentException If method is not supported. + */ + public function add(string $method, Route $route) { + if (!in_array($method, self::$verbs)) { + throw new \InvalidArgumentException("Method not supported"); + } + $this->routes->map($method, $route->getPath(), $route->getCallable(), $route->getName()); + } + + /** + * Adds a route for a controller action. + * + * @param string $method The HTTP method. + * @param string $path The path for the route. + * @param mixed $controller The controller object. + * @param string $action The action method in the controller. + * @param string $name (Optional) The name of the route. + * @throws \InvalidArgumentException If method is not supported. + */ + public function addControllerRoute(string $method, string $path, $controller, string $action, string $name = '') { + if (!in_array($method, self::$verbs)) { + throw new \InvalidArgumentException("Method not supported"); + } + $this->routes->map($method, $path, [$controller, $action], $name); + } + + // TODO: Implement the extractParams method. + // public function extractParams(string $path) {} + + /** + * Adds a GET route. + * + * @param string $path The path for the route. + * @param callable $callable The callback function. + * @param string $name The name of the route. + */ + public function get(string $path, callable $callable, $name) { + $this->routes->map('GET', $path, $callable, $name); + } + + // Similar methods for post, put, etc. can be added here. + + /** + * Checks if the request can be processed. + * + * @param IRequest $request The request object. + * @return array|null The matched route or null if no match. + */ + public function match(IRequest $request): ?array { + return $this->routes->match($request->getRequestUri(), $request->getMethod()) ?: null; + } + + /** + * Returns all routes. + * + * @return array The array of routes. + */ + public function getRoutes() { + return []; // TODO: Implement the actual logic to return routes. + } + + + public function generate (string $routeName, array $params = array()): string + { + return $this->routes->generate($routeName,$params); + } + +} + +?> diff --git a/Sources/src/app/router/Session.php b/Sources/src/app/router/Session.php new file mode 100644 index 00000000..3c97d606 --- /dev/null +++ b/Sources/src/app/router/Session.php @@ -0,0 +1,121 @@ +startSession(); + + return self::$instance; + } + + + /** + * (Re)starts the session. + * + * @return bool TRUE if the session has been initialized, else FALSE. + **/ + + private function startSession() + { + if ( $this->sessionState == self::SESSION_NOT_STARTED ) + { + $this->sessionState = session_start(); + } + + return $this->sessionState; + } + + + /** + * Stores datas in the session. + * Example: $instance->foo = 'bar'; + * + * @param name Name of the datas. + * @param value Your datas. + * @return void + **/ + + public function __set( $name , $value ) + { + $_SESSION[$name] = $value; + } + + + /** + * Gets datas from the session. + * Example: echo $instance->foo; + * + * @param name Name of the datas to get. + * @return mixed Datas stored in session. + **/ + + public function __get( string $name ) + { + if ( isset($_SESSION[$name])) + { + return $_SESSION[$name]; + } + } + + + public function __isset( $name ) + { + return isset($_SESSION[$name]); + } + + + public function __unset( $name ) + { + unset( $_SESSION[$name] ); + } + + + /** + * Destroys the current session. + * + * @return bool TRUE is session has been deleted, else FALSE. + **/ + + public function destroy() + { + if ( $this->sessionState == self::SESSION_STARTED ) + { + $this->sessionState = !session_destroy(); + unset( $_SESSION ); + + return !$this->sessionState; + } + + return FALSE; + } +} diff --git a/Sources/src/app/router/middleware/HttpValidationMiddleware.php b/Sources/src/app/router/middleware/HttpValidationMiddleware.php new file mode 100644 index 00000000..9c03bd0e --- /dev/null +++ b/Sources/src/app/router/middleware/HttpValidationMiddleware.php @@ -0,0 +1,39 @@ +validator = $validator; + $this->rules = $rules; + } + + public function handle(IRequest $request, callable $next) { + $this->validateRequest($request); + return parent::handle($request, $next); + } + + private function validateRequest(IRequest $request) { + foreach ($this->rules as $param => $ruleSet) { + foreach ($ruleSet as $rule) { + $this->validator->rule($param, $rule['callback'], $rule['message']); + } + } + + $requestData = array_merge($request->getQueryParameters(), $request->getRequestParameters()); + $this->validator->assert($requestData); + } + +} + +// $validationRules = [ +// 'email' => [ +// ['callback' => Validator::required(), 'message' => 'Email is required.'], +// ['callback' => Validator::email(), 'message' => 'Email must be a valid email address.'] +// ], +// // Add more rules as needed +// ]; \ No newline at end of file diff --git a/Sources/src/app/router/middleware/IHttpMiddleware.php b/Sources/src/app/router/middleware/IHttpMiddleware.php new file mode 100644 index 00000000..a8e69938 --- /dev/null +++ b/Sources/src/app/router/middleware/IHttpMiddleware.php @@ -0,0 +1,9 @@ +getMethod()}, URI: {$request->getRequestUri()}\n"; + return parent::handle($request, $next); + } +} \ No newline at end of file diff --git a/Sources/src/app/router/middleware/Middleware.php b/Sources/src/app/router/middleware/Middleware.php new file mode 100644 index 00000000..5cfe8f39 --- /dev/null +++ b/Sources/src/app/router/middleware/Middleware.php @@ -0,0 +1,20 @@ +next = $nextMiddleware; + } + + public function handle(IRequest $request, callable $next) { + if ($this->next !== null) { + return $this->next->handle($request, $next); + } + return $next($request); + } +} diff --git a/Sources/src/app/router/request/ContentStrategy.php b/Sources/src/app/router/request/ContentStrategy.php new file mode 100644 index 00000000..00743dda --- /dev/null +++ b/Sources/src/app/router/request/ContentStrategy.php @@ -0,0 +1,6 @@ + JsonContentStrategy::class, + // Format... + ]; + + public static function createContentStrategy(string $contentType, string $requestMethod): ContentStrategy { + foreach (self::$strategyMap as $type => $className) { + if ($contentType === $type || in_array($requestMethod, ['PUT', 'PATCH', 'DELETE'])) { + return new $className(); + } + } + return new FormContentStrategy(); + } + + public static function registerStrategy(string $contentType, string $className): void { + self::$strategyMap[$contentType] = $className; + } +} \ No newline at end of file diff --git a/Sources/src/app/router/request/FormContentStrategy.php b/Sources/src/app/router/request/FormContentStrategy.php new file mode 100644 index 00000000..71bc92a9 --- /dev/null +++ b/Sources/src/app/router/request/FormContentStrategy.php @@ -0,0 +1,9 @@ +queryParameters = $query; + $this->requestUri = $server['REQUEST_URI'] ?? ''; + $this->method = strtoupper($server['REQUEST_METHOD'] ?? 'GET'); + $this->headers = $headers; + $this->requestParameters = $contentStrategy->getContent(); + $this->body = $body; + } + + public function getQueryParameters(): array { + return $this->queryParameters; + } + + public function getRequestParameters(): array { + return $this->requestParameters; + } + + public function getMethod(): string { + return $this->method; + } + + public function getRequestUri(): string { + return $this->requestUri; + } + + public function getHeaders(): array { + return $this->headers; + } + + public function getBody(): array{ + return $this->body; + } + public function addToBody(string|array $attributes){ + $this->body[] = $attributes; + } + +} \ No newline at end of file diff --git a/Sources/src/app/router/request/IRequest.php b/Sources/src/app/router/request/IRequest.php new file mode 100644 index 00000000..987558e1 --- /dev/null +++ b/Sources/src/app/router/request/IRequest.php @@ -0,0 +1,16 @@ + $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5))))); + $headers[$header] = $value; + } + } + return $headers; + } +} \ No newline at end of file diff --git a/Sources/src/app/router/response/IResponse.php b/Sources/src/app/router/response/IResponse.php new file mode 100644 index 00000000..efb35f78 --- /dev/null +++ b/Sources/src/app/router/response/IResponse.php @@ -0,0 +1,12 @@ +url = $url; + $this->statusCode = $statusCode; + $this->headers = $headers; + $this->content = ''; + } + + public function getContent(): string + { + return $this->content; + } + + public function setContent(string $content): void + { + $this->content = $content; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function setStatusCode(int $statusCode): void + { + $this->statusCode = $statusCode; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function setHeader(string $key, string $value): void + { + $this->headers[$key] = $value; + } + + public function send(): void + { + http_response_code($this->statusCode); + + foreach ($this->headers as $name => $value) { + header("$name: $value"); + } + + header("Location: " . $this->url); + + // Optionally echo content if any + echo $this->content; + + exit(); + } +} diff --git a/Sources/src/app/router/response/Response.php b/Sources/src/app/router/response/Response.php new file mode 100644 index 00000000..e3c364ac --- /dev/null +++ b/Sources/src/app/router/response/Response.php @@ -0,0 +1,49 @@ +content = $content; + $this->statusCode = $statusCode; + $this->headers = $headers; + } + + public function getContent(): string { + return $this->content; + } + + public function setContent(string $content): void { + $this->content = $content; + } + + public function getStatusCode(): int { + return $this->statusCode; + } + + public function setStatusCode(int $statusCode): void { + $this->statusCode = $statusCode; + } + + public function getHeaders(): array { + return $this->headers; + } + + public function setHeader(string $key, string $value): void { + $this->headers[$key] = $value; + } + + public function send(): void { + foreach ($this->headers as $key => $value) { + header("{$key}: {$value}"); + } + http_response_code($this->statusCode); + echo $this->content; + } +} + + +?> \ No newline at end of file diff --git a/Sources/src/app/views/Templates/base.html.twig b/Sources/src/app/views/Templates/base.html.twig index d80861c7..6b72d411 100755 --- a/Sources/src/app/views/Templates/base.html.twig +++ b/Sources/src/app/views/Templates/base.html.twig @@ -46,12 +46,12 @@