tp5
This commit is contained in:
parent
49798f4a7f
commit
5fe41c9a9e
R4.01_R4.A.10
README.md
cours
td_tp/tp5
README.md
src
api_php
todo-riot
@ -12,5 +12,8 @@
|
||||
#### Semaine 4
|
||||
[RIOT.js](cours/riot.pdf), [tp4](./td_tp/tp4)
|
||||
|
||||
#### Semaine 5
|
||||
[API REST](cours/api.pdf), service firebase [tp5](./td_tp/tp5)
|
||||
|
||||
|
||||
|
||||
|
BIN
R4.01_R4.A.10/cours/api.pdf
Normal file
BIN
R4.01_R4.A.10/cours/api.pdf
Normal file
Binary file not shown.
106
R4.01_R4.A.10/td_tp/tp5/README.md
Normal file
106
R4.01_R4.A.10/td_tp/tp5/README.md
Normal file
@ -0,0 +1,106 @@
|
||||
# TP : Une api rest pour la todo list.
|
||||
|
||||
Le but de l'exercice est d'écrire une api de données restful pour l'application todolist du
|
||||
[tp2](../tp2) ou [tp4](../tp4).
|
||||
|
||||
|
||||
Les routes de notre api :
|
||||
|
||||
```
|
||||
get /todo/(:id)
|
||||
post /todo
|
||||
delete /todo/:id
|
||||
put /todo/:id
|
||||
```
|
||||
|
||||
- Pour routage et les entrées/sorties http, on utilise [flight php](https://flightphp.com/).
|
||||
- Pour les entrées/sorties avec la base données, je vous donne un son utilise l'orm [readbean php](https://www.redbeanphp.com/index.php).
|
||||
|
||||
Copiez le fichier `.htaccess` à la racine de vos sources pour activer la réécriture d'urls.
|
||||
|
||||
```apache
|
||||
Require method GET POST PUT DELETE OPTIONS
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||
```
|
||||
|
||||
Votre api pour l'instant
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require 'flight/Flight.php';
|
||||
require 'model/model.php';
|
||||
Flight::route('GET /todo(/@id)','getTodos');
|
||||
Flight::route('POST /todo','addTodo');
|
||||
Flight::route('DELETE /todo/@id','deleteTodo');
|
||||
Flight::route('PUT /todo/@id','updateTodo');
|
||||
|
||||
function deleteTodo($id)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
function updateTodo($id)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
function addTodo()
|
||||
{
|
||||
$todo = [
|
||||
"title" => Flight::request()->data->title ,
|
||||
"done" => Flight::request()->data->done
|
||||
];
|
||||
|
||||
$id = Todo::create($todo);
|
||||
Flight::response()->header("Location",Flight::request()->url.$id);
|
||||
$todo['id'] = $id;
|
||||
Flight::json($todo,201);
|
||||
|
||||
}
|
||||
|
||||
function getTodos($id = null)
|
||||
{
|
||||
$filter = Flight::request()->query->filter ?? "all";
|
||||
|
||||
if ($id === null){
|
||||
switch($filter){
|
||||
case "done":
|
||||
$todos = Todo::findCompleted();
|
||||
break;
|
||||
case "active":
|
||||
$todos = Todo::findUnCompleted();
|
||||
break;
|
||||
default:
|
||||
$todos = Todo::findAll();
|
||||
}
|
||||
Flight::json(
|
||||
[
|
||||
"results" => $todos
|
||||
]
|
||||
);
|
||||
|
||||
} else {
|
||||
$todo = Todo::find($id);
|
||||
if ($todo)
|
||||
Flight::json($todo);
|
||||
else
|
||||
Flight::halt(404);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Flight::start();
|
||||
```
|
||||
|
||||
Le modèle utilise PDO, en php. Il vous faudra créér une table avec les attributs nécessaires.
|
||||
|
||||
|
||||
Complétez le fichier index.php et connectez votre application todolist avec l'api.
|
||||
|
||||
Écrivez un module "abstrait" en javascript pour l'interaction
|
||||
avec l'api. Ce module devra pouvoir être changer pour utiliser firebase **sans que l'application cliente ne change**.
|
911
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/Engine.php
Normal file
911
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/Engine.php
Normal file
File diff suppressed because it is too large
Load Diff
144
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/Flight.php
Normal file
144
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/Flight.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use flight\Engine;
|
||||
use flight\net\Request;
|
||||
use flight\net\Response;
|
||||
use flight\net\Router;
|
||||
use flight\template\View;
|
||||
use flight\net\Route;
|
||||
|
||||
require_once __DIR__ . '/autoload.php';
|
||||
|
||||
/**
|
||||
* The Flight class is a static representation of the framework.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*
|
||||
* # Core methods
|
||||
* @method static void start() Starts the framework.
|
||||
* @method static void path(string $path) Adds a path for autoloading classes.
|
||||
* @method static void stop(?int $code = null) Stops the framework and sends a response.
|
||||
* @method static void halt(int $code = 200, string $message = '', bool $actuallyExit = true)
|
||||
* Stop the framework with an optional status code and message.
|
||||
* @method static void register(string $name, string $class, array $params = [], ?callable $callback = null)
|
||||
* Registers a class to a framework method.
|
||||
* @method static void unregister(string $methodName)
|
||||
* Unregisters a class to a framework method.
|
||||
* @method static void registerContainerHandler(callable|object $containerHandler) Registers a container handler.
|
||||
*
|
||||
* # Routing
|
||||
* @method static Route route(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Maps a URL pattern to a callback with all applicable methods.
|
||||
* @method static void group(string $pattern, callable $callback, callable[] $group_middlewares = [])
|
||||
* Groups a set of routes together under a common prefix.
|
||||
* @method static Route post(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Routes a POST URL to a callback function.
|
||||
* @method static Route put(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Routes a PUT URL to a callback function.
|
||||
* @method static Route patch(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Routes a PATCH URL to a callback function.
|
||||
* @method static Route delete(string $pattern, callable|string $callback, bool $pass_route = false, string $alias = '')
|
||||
* Routes a DELETE URL to a callback function.
|
||||
* @method static Router router() Returns Router instance.
|
||||
* @method static string getUrl(string $alias, array<string, mixed> $params = []) Gets a url from an alias
|
||||
*
|
||||
* @method static void map(string $name, callable $callback) Creates a custom framework method.
|
||||
*
|
||||
* @method static void before(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
|
||||
* Adds a filter before a framework method.
|
||||
* @method static void after(string $name, Closure(array<int, mixed> &$params, string &$output): (void|false) $callback)
|
||||
* Adds a filter after a framework method.
|
||||
*
|
||||
* @method static void set(string|iterable<string, mixed> $key, mixed $value) Sets a variable.
|
||||
* @method static mixed get(?string $key) Gets a variable.
|
||||
* @method static bool has(string $key) Checks if a variable is set.
|
||||
* @method static void clear(?string $key = null) Clears a variable.
|
||||
*
|
||||
* # Views
|
||||
* @method static void render(string $file, ?array<string, mixed> $data = null, ?string $key = null)
|
||||
* Renders a template file.
|
||||
* @method static View view() Returns View instance.
|
||||
*
|
||||
* # Request-Response
|
||||
* @method static Request request() Returns Request instance.
|
||||
* @method static Response response() Returns Response instance.
|
||||
* @method static void redirect(string $url, int $code = 303) Redirects to another URL.
|
||||
* @method static void json(mixed $data, int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
|
||||
* Sends a JSON response.
|
||||
* @method static void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = "utf8", int $encodeOption = 0, int $encodeDepth = 512)
|
||||
* Sends a JSONP response.
|
||||
* @method static void error(Throwable $exception) Sends an HTTP 500 response.
|
||||
* @method static void notFound() Sends an HTTP 404 response.
|
||||
*
|
||||
* # HTTP caching
|
||||
* @method static void etag(string $id, ('strong'|'weak') $type = 'strong') Performs ETag HTTP caching.
|
||||
* @method static void lastModified(int $time) Performs last modified HTTP caching.
|
||||
*/
|
||||
class Flight
|
||||
{
|
||||
/** Framework engine. */
|
||||
private static Engine $engine;
|
||||
|
||||
/** Whether or not the app has been initialized. */
|
||||
private static bool $initialized = false;
|
||||
|
||||
/**
|
||||
* Don't allow object instantiation
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Forbid cloning the class
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
private function __clone()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles calls to static methods.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param array<int, mixed> $params Method parameters
|
||||
*
|
||||
* @return mixed Callback results
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function __callStatic(string $name, array $params)
|
||||
{
|
||||
return self::app()->{$name}(...$params);
|
||||
}
|
||||
|
||||
/** @return Engine Application instance */
|
||||
public static function app(): Engine
|
||||
{
|
||||
if (!self::$initialized) {
|
||||
require_once __DIR__ . '/autoload.php';
|
||||
|
||||
self::setEngine(new Engine());
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
return self::$engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the engine instance
|
||||
*
|
||||
* @param Engine $engine Vroom vroom!
|
||||
*/
|
||||
public static function setEngine(Engine $engine): void
|
||||
{
|
||||
self::$engine = $engine;
|
||||
}
|
||||
}
|
10
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/autoload.php
Normal file
10
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/autoload.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use flight\core\Loader;
|
||||
|
||||
require_once __DIR__ . '/Flight.php';
|
||||
require_once __DIR__ . '/core/Loader.php';
|
||||
|
||||
Loader::autoload(true, [dirname(__DIR__)]);
|
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\commands;
|
||||
|
||||
use Nette\PhpGenerator\ClassType;
|
||||
use Nette\PhpGenerator\PhpFile;
|
||||
use Nette\PhpGenerator\PhpNamespace;
|
||||
|
||||
class ControllerCommand extends AbstractBaseCommand
|
||||
{
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param array<string,mixed> $config JSON config from .runway-config.json
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
parent::__construct('make:controller', 'Create a controller', $config);
|
||||
$this->argument('<controller>', 'The name of the controller to create (with or without the Controller suffix)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute(string $controller)
|
||||
{
|
||||
$io = $this->app()->io();
|
||||
if (isset($this->config['app_root']) === false) {
|
||||
$io->error('app_root not set in .runway-config.json', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!preg_match('/Controller$/', $controller)) {
|
||||
$controller .= 'Controller';
|
||||
}
|
||||
|
||||
$controllerPath = getcwd() . DIRECTORY_SEPARATOR . $this->config['app_root'] . 'controllers' . DIRECTORY_SEPARATOR . $controller . '.php';
|
||||
if (file_exists($controllerPath) === true) {
|
||||
$io->error($controller . ' already exists.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir(dirname($controllerPath)) === false) {
|
||||
$io->info('Creating directory ' . dirname($controllerPath), true);
|
||||
mkdir(dirname($controllerPath), 0755, true);
|
||||
}
|
||||
|
||||
$file = new PhpFile();
|
||||
$file->setStrictTypes();
|
||||
|
||||
$namespace = new PhpNamespace('app\\controllers');
|
||||
$namespace->addUse('flight\\Engine');
|
||||
|
||||
$class = new ClassType($controller);
|
||||
$class->addProperty('app')
|
||||
->setVisibility('protected')
|
||||
->setType('flight\\Engine')
|
||||
->addComment('@var Engine');
|
||||
$method = $class->addMethod('__construct')
|
||||
->addComment('Constructor')
|
||||
->setVisibility('public')
|
||||
->setBody('$this->app = $app;');
|
||||
$method->addParameter('app')
|
||||
->setType('flight\\Engine');
|
||||
|
||||
$namespace->add($class);
|
||||
$file->addNamespace($namespace);
|
||||
|
||||
$this->persistClass($controller, $file);
|
||||
|
||||
$io->ok('Controller successfully created at ' . $controllerPath, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the class name to a file
|
||||
*
|
||||
* @param string $controllerName Name of the Controller
|
||||
* @param PhpFile $file Class Object from Nette\PhpGenerator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function persistClass(string $controllerName, PhpFile $file)
|
||||
{
|
||||
$printer = new \Nette\PhpGenerator\PsrPrinter();
|
||||
file_put_contents(getcwd() . DIRECTORY_SEPARATOR . $this->config['app_root'] . 'controllers' . DIRECTORY_SEPARATOR . $controllerName . '.php', $printer->printFile($file));
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\commands;
|
||||
|
||||
use Flight;
|
||||
use flight\net\Route;
|
||||
|
||||
/**
|
||||
* @property-read ?bool $get
|
||||
* @property-read ?bool $post
|
||||
* @property-read ?bool $delete
|
||||
* @property-read ?bool $put
|
||||
* @property-read ?bool $patch
|
||||
*/
|
||||
class RouteCommand extends AbstractBaseCommand
|
||||
{
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param array<string,mixed> $config JSON config from .runway-config.json
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
parent::__construct('routes', 'Gets all routes for an application', $config);
|
||||
|
||||
$this->option('--get', 'Only return GET requests');
|
||||
$this->option('--post', 'Only return POST requests');
|
||||
$this->option('--delete', 'Only return DELETE requests');
|
||||
$this->option('--put', 'Only return PUT requests');
|
||||
$this->option('--patch', 'Only return PATCH requests');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$io = $this->app()->io();
|
||||
|
||||
if (isset($this->config['index_root']) === false) {
|
||||
$io->error('index_root not set in .runway-config.json', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$io->bold('Routes', true);
|
||||
|
||||
$cwd = getcwd();
|
||||
|
||||
$index_root = $cwd . '/' . $this->config['index_root'];
|
||||
|
||||
// This makes it so the framework doesn't actually execute
|
||||
Flight::map('start', function () {
|
||||
return;
|
||||
});
|
||||
include($index_root);
|
||||
$routes = Flight::router()->getRoutes();
|
||||
$arrayOfRoutes = [];
|
||||
foreach ($routes as $route) {
|
||||
if ($this->shouldAddRoute($route) === true) {
|
||||
$middlewares = [];
|
||||
if (!empty($route->middleware)) {
|
||||
try {
|
||||
$middlewares = array_map(function ($middleware) {
|
||||
$middleware_class_name = explode("\\", get_class($middleware));
|
||||
return preg_match("/^class@anonymous/", end($middleware_class_name)) ? 'Anonymous' : end($middleware_class_name);
|
||||
}, $route->middleware);
|
||||
} catch (\TypeError $e) {
|
||||
$middlewares[] = 'Bad Middleware';
|
||||
} finally {
|
||||
if (is_string($route->middleware) === true) {
|
||||
$middlewares[] = $route->middleware;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$arrayOfRoutes[] = [
|
||||
'Pattern' => $route->pattern,
|
||||
'Methods' => implode(', ', $route->methods),
|
||||
'Alias' => $route->alias ?? '',
|
||||
'Streamed' => $route->is_streamed ? 'Yes' : 'No',
|
||||
'Middleware' => !empty($middlewares) ? implode(",", $middlewares) : '-'
|
||||
];
|
||||
}
|
||||
}
|
||||
$io->table($arrayOfRoutes, [
|
||||
'head' => 'boldGreen'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not to add the route based on the request
|
||||
*
|
||||
* @param Route $route Flight Route object
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function shouldAddRoute(Route $route)
|
||||
{
|
||||
$boolval = false;
|
||||
|
||||
$showAll = !$this->get && !$this->post && !$this->put && !$this->delete && !$this->patch;
|
||||
if ($showAll === true) {
|
||||
$boolval = true;
|
||||
} else {
|
||||
$methods = [ 'GET', 'POST', 'PUT', 'DELETE', 'PATCH' ];
|
||||
foreach ($methods as $method) {
|
||||
$lowercaseMethod = strtolower($method);
|
||||
if (
|
||||
$this->{$lowercaseMethod} === true &&
|
||||
(
|
||||
$route->methods[0] === '*' ||
|
||||
in_array($method, $route->methods, true) === true
|
||||
)
|
||||
) {
|
||||
$boolval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $boolval;
|
||||
}
|
||||
}
|
504
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/core/Dispatcher.php
Normal file
504
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/core/Dispatcher.php
Normal file
File diff suppressed because it is too large
Load Diff
241
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/core/Loader.php
Normal file
241
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/core/Loader.php
Normal file
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\core;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* The Loader class is responsible for loading objects. It maintains
|
||||
* a list of reusable class instances and can generate a new class
|
||||
* instances with custom initialization parameters. It also performs
|
||||
* class autoloading.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Loader
|
||||
{
|
||||
/**
|
||||
* Registered classes.
|
||||
*
|
||||
* @var array<string, array{class-string|Closure(): object, array<int, mixed>, ?callable}> $classes
|
||||
*/
|
||||
protected array $classes = [];
|
||||
|
||||
/**
|
||||
* If this is disabled, classes can load with underscores
|
||||
*/
|
||||
protected static bool $v2ClassLoading = true;
|
||||
|
||||
/**
|
||||
* Class instances.
|
||||
*
|
||||
* @var array<string, object>
|
||||
*/
|
||||
protected array $instances = [];
|
||||
|
||||
/**
|
||||
* Autoload directories.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected static array $dirs = [];
|
||||
|
||||
/**
|
||||
* Registers a class.
|
||||
*
|
||||
* @param string $name Registry name
|
||||
* @param class-string<T>|Closure(): T $class Class name or function to instantiate class
|
||||
* @param array<int, mixed> $params Class initialization parameters
|
||||
* @param ?Closure(T $instance): void $callback $callback Function to call after object instantiation
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
public function register(string $name, $class, array $params = [], ?callable $callback = null): void
|
||||
{
|
||||
unset($this->instances[$name]);
|
||||
|
||||
$this->classes[$name] = [$class, $params, $callback];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a class.
|
||||
*
|
||||
* @param string $name Registry name
|
||||
*/
|
||||
public function unregister(string $name): void
|
||||
{
|
||||
unset($this->classes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a registered class.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param bool $shared Shared instance
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return ?object Class instance
|
||||
*/
|
||||
public function load(string $name, bool $shared = true): ?object
|
||||
{
|
||||
$obj = null;
|
||||
|
||||
if (isset($this->classes[$name])) {
|
||||
[0 => $class, 1 => $params, 2 => $callback] = $this->classes[$name];
|
||||
|
||||
$exists = isset($this->instances[$name]);
|
||||
|
||||
if ($shared) {
|
||||
$obj = ($exists) ?
|
||||
$this->getInstance($name) :
|
||||
$this->newInstance($class, $params);
|
||||
|
||||
if (!$exists) {
|
||||
$this->instances[$name] = $obj;
|
||||
}
|
||||
} else {
|
||||
$obj = $this->newInstance($class, $params);
|
||||
}
|
||||
|
||||
if ($callback && (!$shared || !$exists)) {
|
||||
$ref = [&$obj];
|
||||
\call_user_func_array($callback, $ref);
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single instance of a class.
|
||||
*
|
||||
* @param string $name Instance name
|
||||
*
|
||||
* @return ?object Class instance
|
||||
*/
|
||||
public function getInstance(string $name): ?object
|
||||
{
|
||||
return $this->instances[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new instance of a class.
|
||||
*
|
||||
* @param class-string<T>|Closure(): class-string<T> $class Class name or callback function to instantiate class
|
||||
* @param array<int, string> $params Class initialization parameters
|
||||
*
|
||||
* @template T of object
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return T Class instance
|
||||
*/
|
||||
public function newInstance($class, array $params = [])
|
||||
{
|
||||
if (\is_callable($class)) {
|
||||
return \call_user_func_array($class, $params);
|
||||
}
|
||||
|
||||
return new $class(...$params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a registered callable
|
||||
*
|
||||
* @param string $name Registry name
|
||||
*
|
||||
* @return mixed Class information or null if not registered
|
||||
*/
|
||||
public function get(string $name)
|
||||
{
|
||||
return $this->classes[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the object to the initial state.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->classes = [];
|
||||
$this->instances = [];
|
||||
}
|
||||
|
||||
// Autoloading Functions
|
||||
|
||||
/**
|
||||
* Starts/stops autoloader.
|
||||
*
|
||||
* @param bool $enabled Enable/disable autoloading
|
||||
* @param string|iterable<int, string> $dirs Autoload directories
|
||||
*/
|
||||
public static function autoload(bool $enabled = true, $dirs = []): void
|
||||
{
|
||||
if ($enabled) {
|
||||
spl_autoload_register([__CLASS__, 'loadClass']);
|
||||
} else {
|
||||
spl_autoload_unregister([__CLASS__, 'loadClass']); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if (!empty($dirs)) {
|
||||
self::addDirectory($dirs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Autoloads classes.
|
||||
*
|
||||
* Classes are not allowed to have underscores in their names.
|
||||
*
|
||||
* @param string $class Class name
|
||||
*/
|
||||
public static function loadClass(string $class): void
|
||||
{
|
||||
$replace_chars = self::$v2ClassLoading === true ? ['\\', '_'] : ['\\'];
|
||||
$classFile = str_replace($replace_chars, '/', $class) . '.php';
|
||||
|
||||
foreach (self::$dirs as $dir) {
|
||||
$filePath = "$dir/$classFile";
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
require_once $filePath;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a directory for autoloading classes.
|
||||
*
|
||||
* @param string|iterable<int, string> $dir Directory path
|
||||
*/
|
||||
public static function addDirectory($dir): void
|
||||
{
|
||||
if (\is_array($dir) || \is_object($dir)) {
|
||||
foreach ($dir as $value) {
|
||||
self::addDirectory($value);
|
||||
}
|
||||
} elseif (\is_string($dir)) {
|
||||
if (!\in_array($dir, self::$dirs, true)) {
|
||||
self::$dirs[] = $dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value for V2 class loading.
|
||||
*
|
||||
* @param bool $value The value to set for V2 class loading.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setV2ClassLoading(bool $value): void
|
||||
{
|
||||
self::$v2ClassLoading = $value;
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\database;
|
||||
|
||||
use flight\util\Collection;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
|
||||
class PdoWrapper extends PDO
|
||||
{
|
||||
/**
|
||||
* Use this for INSERTS, UPDATES, or if you plan on using a SELECT in a while loop
|
||||
*
|
||||
* Ex: $statement = $db->runQuery("SELECT * FROM table WHERE something = ?", [ $something ]);
|
||||
* while($row = $statement->fetch()) {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* $db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]);
|
||||
* $db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]);
|
||||
*
|
||||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?"
|
||||
* @param array<int|string,mixed> $params - Ex: [ $something ]
|
||||
*
|
||||
* @return PDOStatement
|
||||
*/
|
||||
public function runQuery(string $sql, array $params = []): PDOStatement
|
||||
{
|
||||
$processed_sql_data = $this->processInStatementSql($sql, $params);
|
||||
$sql = $processed_sql_data['sql'];
|
||||
$params = $processed_sql_data['params'];
|
||||
$statement = $this->prepare($sql);
|
||||
$statement->execute($params);
|
||||
return $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls one field from the query
|
||||
*
|
||||
* Ex: $id = $db->fetchField("SELECT id FROM table WHERE something = ?", [ $something ]);
|
||||
*
|
||||
* @param string $sql - Ex: "SELECT id FROM table WHERE something = ?"
|
||||
* @param array<int|string,mixed> $params - Ex: [ $something ]
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchField(string $sql, array $params = [])
|
||||
{
|
||||
$result = $this->fetchRow($sql, $params);
|
||||
$data = $result->getData();
|
||||
return reset($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls one row from the query
|
||||
*
|
||||
* Ex: $row = $db->fetchRow("SELECT * FROM table WHERE something = ?", [ $something ]);
|
||||
*
|
||||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?"
|
||||
* @param array<int|string,mixed> $params - Ex: [ $something ]
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function fetchRow(string $sql, array $params = []): Collection
|
||||
{
|
||||
$sql .= stripos($sql, 'LIMIT') === false ? ' LIMIT 1' : '';
|
||||
$result = $this->fetchAll($sql, $params);
|
||||
return count($result) > 0 ? $result[0] : new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls all rows from the query
|
||||
*
|
||||
* Ex: $rows = $db->fetchAll("SELECT * FROM table WHERE something = ?", [ $something ]);
|
||||
* foreach($rows as $row) {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* @param string $sql - Ex: "SELECT * FROM table WHERE something = ?"
|
||||
* @param array<int|string,mixed> $params - Ex: [ $something ]
|
||||
*
|
||||
* @return array<int,Collection>
|
||||
*/
|
||||
public function fetchAll(string $sql, array $params = [])
|
||||
{
|
||||
$processed_sql_data = $this->processInStatementSql($sql, $params);
|
||||
$sql = $processed_sql_data['sql'];
|
||||
$params = $processed_sql_data['params'];
|
||||
$statement = $this->prepare($sql);
|
||||
$statement->execute($params);
|
||||
$results = $statement->fetchAll();
|
||||
if (is_array($results) === true && count($results) > 0) {
|
||||
foreach ($results as &$result) {
|
||||
$result = new Collection($result);
|
||||
}
|
||||
} else {
|
||||
$results = [];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't worry about this guy. Converts stuff for IN statements
|
||||
*
|
||||
* Ex: $row = $db->fetchAll("SELECT * FROM table WHERE id = ? AND something IN(?), [ $id, [1,2,3] ]);
|
||||
* Converts this to "SELECT * FROM table WHERE id = ? AND something IN(?,?,?)"
|
||||
*
|
||||
* @param string $sql the sql statement
|
||||
* @param array<int|string,mixed> $params the params for the sql statement
|
||||
*
|
||||
* @return array<string,string|array<int|string,mixed>>
|
||||
*/
|
||||
protected function processInStatementSql(string $sql, array $params = []): array
|
||||
{
|
||||
// Replace "IN(?)" with "IN(?,?,?)"
|
||||
$sql = preg_replace('/IN\s*\(\s*\?\s*\)/i', 'IN(?)', $sql);
|
||||
|
||||
$current_index = 0;
|
||||
while (($current_index = strpos($sql, 'IN(?)', $current_index)) !== false) {
|
||||
$preceeding_count = substr_count($sql, '?', 0, $current_index - 1);
|
||||
|
||||
$param = $params[$preceeding_count];
|
||||
$question_marks = '?';
|
||||
|
||||
if (is_string($param) || is_array($param)) {
|
||||
$params_to_use = $param;
|
||||
if (is_string($param)) {
|
||||
$params_to_use = explode(',', $param);
|
||||
}
|
||||
|
||||
foreach ($params_to_use as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$params_to_use[$key] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
$question_marks = join(',', array_fill(0, count($params_to_use), '?'));
|
||||
$sql = substr_replace($sql, $question_marks, $current_index + 3, 1);
|
||||
|
||||
array_splice($params, $preceeding_count, 1, $params_to_use);
|
||||
}
|
||||
|
||||
$current_index += strlen($question_marks) + 4;
|
||||
}
|
||||
|
||||
return ['sql' => $sql, 'params' => $params];
|
||||
}
|
||||
}
|
417
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/net/Request.php
Normal file
417
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/net/Request.php
Normal file
@ -0,0 +1,417 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use flight\util\Collection;
|
||||
|
||||
/**
|
||||
* The Request class represents an HTTP request. Data from
|
||||
* all the super globals $_GET, $_POST, $_COOKIE, and $_FILES
|
||||
* are stored and accessible via the Request object.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*
|
||||
* The default request properties are:
|
||||
*
|
||||
* - **url** - The URL being requested
|
||||
* - **base** - The parent subdirectory of the URL
|
||||
* - **method** - The request method (GET, POST, PUT, DELETE)
|
||||
* - **referrer** - The referrer URL
|
||||
* - **ip** - IP address of the client
|
||||
* - **ajax** - Whether the request is an AJAX request
|
||||
* - **scheme** - The server protocol (http, https)
|
||||
* - **user_agent** - Browser information
|
||||
* - **type** - The content type
|
||||
* - **length** - The content length
|
||||
* - **query** - Query string parameters
|
||||
* - **data** - Post parameters
|
||||
* - **cookies** - Cookie parameters
|
||||
* - **files** - Uploaded files
|
||||
* - **secure** - Connection is secure
|
||||
* - **accept** - HTTP accept parameters
|
||||
* - **proxy_ip** - Proxy IP address of the client
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* URL being requested
|
||||
*/
|
||||
public string $url;
|
||||
|
||||
/**
|
||||
* Parent subdirectory of the URL
|
||||
*/
|
||||
public string $base;
|
||||
|
||||
/**
|
||||
* Request method (GET, POST, PUT, DELETE)
|
||||
*/
|
||||
public string $method;
|
||||
|
||||
/**
|
||||
* Referrer URL
|
||||
*/
|
||||
public string $referrer;
|
||||
|
||||
/**
|
||||
* IP address of the client
|
||||
*/
|
||||
public string $ip;
|
||||
|
||||
/**
|
||||
* Whether the request is an AJAX request
|
||||
*/
|
||||
public bool $ajax;
|
||||
|
||||
/**
|
||||
* Server protocol (http, https)
|
||||
*/
|
||||
public string $scheme;
|
||||
|
||||
/**
|
||||
* Browser information
|
||||
*/
|
||||
public string $user_agent;
|
||||
|
||||
/**
|
||||
* Content type
|
||||
*/
|
||||
public string $type;
|
||||
|
||||
/**
|
||||
* Content length
|
||||
*/
|
||||
public int $length;
|
||||
|
||||
/**
|
||||
* Query string parameters
|
||||
*/
|
||||
public Collection $query;
|
||||
|
||||
/**
|
||||
* Post parameters
|
||||
*/
|
||||
public Collection $data;
|
||||
|
||||
/**
|
||||
* Cookie parameters
|
||||
*/
|
||||
public Collection $cookies;
|
||||
|
||||
/**
|
||||
* Uploaded files
|
||||
*/
|
||||
public Collection $files;
|
||||
|
||||
/**
|
||||
* Whether the connection is secure
|
||||
*/
|
||||
public bool $secure;
|
||||
|
||||
/**
|
||||
* HTTP accept parameters
|
||||
*/
|
||||
public string $accept;
|
||||
|
||||
/**
|
||||
* Proxy IP address of the client
|
||||
*/
|
||||
public string $proxy_ip;
|
||||
|
||||
/**
|
||||
* HTTP host name
|
||||
*/
|
||||
public string $host;
|
||||
|
||||
/**
|
||||
* Stream path for where to pull the request body from
|
||||
*/
|
||||
private string $stream_path = 'php://input';
|
||||
|
||||
/**
|
||||
* Raw HTTP request body
|
||||
*/
|
||||
public string $body = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array<string, mixed> $config Request configuration
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
// Default properties
|
||||
if (empty($config)) {
|
||||
$config = [
|
||||
'url' => str_replace('@', '%40', self::getVar('REQUEST_URI', '/')),
|
||||
'base' => str_replace(['\\', ' '], ['/', '%20'], \dirname(self::getVar('SCRIPT_NAME'))),
|
||||
'method' => self::getMethod(),
|
||||
'referrer' => self::getVar('HTTP_REFERER'),
|
||||
'ip' => self::getVar('REMOTE_ADDR'),
|
||||
'ajax' => self::getVar('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest',
|
||||
'scheme' => self::getScheme(),
|
||||
'user_agent' => self::getVar('HTTP_USER_AGENT'),
|
||||
'type' => self::getVar('CONTENT_TYPE'),
|
||||
'length' => intval(self::getVar('CONTENT_LENGTH', 0)),
|
||||
'query' => new Collection($_GET),
|
||||
'data' => new Collection($_POST),
|
||||
'cookies' => new Collection($_COOKIE),
|
||||
'files' => new Collection($_FILES),
|
||||
'secure' => self::getScheme() === 'https',
|
||||
'accept' => self::getVar('HTTP_ACCEPT'),
|
||||
'proxy_ip' => self::getProxyIpAddress(),
|
||||
'host' => self::getVar('HTTP_HOST'),
|
||||
];
|
||||
}
|
||||
|
||||
$this->init($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize request properties.
|
||||
*
|
||||
* @param array<string, mixed> $properties Array of request properties
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function init(array $properties = []): self
|
||||
{
|
||||
// Set all the defined properties
|
||||
foreach ($properties as $name => $value) {
|
||||
$this->{$name} = $value;
|
||||
}
|
||||
|
||||
// Get the requested URL without the base directory
|
||||
// This rewrites the url in case the public url and base directories match
|
||||
// (such as installing on a subdirectory in a web server)
|
||||
// @see testInitUrlSameAsBaseDirectory
|
||||
if ($this->base !== '/' && $this->base !== '' && strpos($this->url, $this->base) === 0) {
|
||||
$this->url = substr($this->url, \strlen($this->base));
|
||||
}
|
||||
|
||||
// Default url
|
||||
if (empty($this->url) === true) {
|
||||
$this->url = '/';
|
||||
} else {
|
||||
// Merge URL query parameters with $_GET
|
||||
$_GET = array_merge($_GET, self::parseQuery($this->url));
|
||||
|
||||
$this->query->setData($_GET);
|
||||
}
|
||||
|
||||
// Check for JSON input
|
||||
if (strpos($this->type, 'application/json') === 0) {
|
||||
$body = $this->getBody();
|
||||
if ($body !== '') {
|
||||
$data = json_decode($body, true);
|
||||
if (is_array($data) === true) {
|
||||
$this->data->setData($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the body of the request.
|
||||
*
|
||||
* @return string Raw HTTP request body
|
||||
*/
|
||||
public function getBody(): string
|
||||
{
|
||||
$body = $this->body;
|
||||
|
||||
if ($body !== '') {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$method = $this->method ?? self::getMethod();
|
||||
|
||||
if ($method === 'POST' || $method === 'PUT' || $method === 'DELETE' || $method === 'PATCH') {
|
||||
$body = file_get_contents($this->stream_path);
|
||||
}
|
||||
|
||||
$this->body = $body;
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request method.
|
||||
*/
|
||||
public static function getMethod(): string
|
||||
{
|
||||
$method = self::getVar('REQUEST_METHOD', 'GET');
|
||||
|
||||
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) === true) {
|
||||
$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
|
||||
} elseif (isset($_REQUEST['_method']) === true) {
|
||||
$method = $_REQUEST['_method'];
|
||||
}
|
||||
|
||||
return strtoupper($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real remote IP address.
|
||||
*
|
||||
* @return string IP address
|
||||
*/
|
||||
public static function getProxyIpAddress(): string
|
||||
{
|
||||
$forwarded = [
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_FORWARDED',
|
||||
'HTTP_X_CLUSTER_CLIENT_IP',
|
||||
'HTTP_FORWARDED_FOR',
|
||||
'HTTP_FORWARDED',
|
||||
];
|
||||
|
||||
$flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
|
||||
|
||||
foreach ($forwarded as $key) {
|
||||
if (\array_key_exists($key, $_SERVER) === true) {
|
||||
sscanf($_SERVER[$key], '%[^,]', $ip);
|
||||
if (filter_var($ip, \FILTER_VALIDATE_IP, $flags) !== false) {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a variable from $_SERVER using $default if not provided.
|
||||
*
|
||||
* @param string $var Variable name
|
||||
* @param mixed $default Default value to substitute
|
||||
*
|
||||
* @return mixed Server variable value
|
||||
*/
|
||||
public static function getVar(string $var, $default = '')
|
||||
{
|
||||
return $_SERVER[$var] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will pull a header from the request.
|
||||
*
|
||||
* @param string $header Header name. Can be caps, lowercase, or mixed.
|
||||
* @param string $default Default value if the header does not exist
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getHeader(string $header, $default = ''): string
|
||||
{
|
||||
$header = 'HTTP_' . strtoupper(str_replace('-', '_', $header));
|
||||
return self::getVar($header, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the request headers
|
||||
*
|
||||
* @return array<string, string|int>
|
||||
*/
|
||||
public static function getHeaders(): array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (strpos($key, 'HTTP_') === 0) {
|
||||
// converts headers like HTTP_CUSTOM_HEADER to Custom-Header
|
||||
$key = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of Request->getHeader(). Gets a single header.
|
||||
*
|
||||
* @param string $header Header name. Can be caps, lowercase, or mixed.
|
||||
* @param string $default Default value if the header does not exist
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function header(string $header, $default = '')
|
||||
{
|
||||
return self::getHeader($header, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of Request->getHeaders(). Gets all the request headers
|
||||
*
|
||||
* @return array<string, string|int>
|
||||
*/
|
||||
public static function headers(): array
|
||||
{
|
||||
return self::getHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full request URL.
|
||||
*
|
||||
* @return string URL
|
||||
*/
|
||||
public function getFullUrl(): string
|
||||
{
|
||||
return $this->scheme . '://' . $this->host . $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs the scheme and host. Does not end with a /
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseUrl(): string
|
||||
{
|
||||
return $this->scheme . '://' . $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query parameters from a URL.
|
||||
*
|
||||
* @param string $url URL string
|
||||
*
|
||||
* @return array<string, int|string|array<int|string, int|string>>
|
||||
*/
|
||||
public static function parseQuery(string $url): array
|
||||
{
|
||||
$params = [];
|
||||
|
||||
$args = parse_url($url);
|
||||
if (isset($args['query']) === true) {
|
||||
parse_str($args['query'], $params);
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL Scheme
|
||||
*
|
||||
* @return string 'http'|'https'
|
||||
*/
|
||||
public static function getScheme(): string
|
||||
{
|
||||
if (
|
||||
(isset($_SERVER['HTTPS']) === true && strtolower($_SERVER['HTTPS']) === 'on')
|
||||
||
|
||||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
|
||||
||
|
||||
(isset($_SERVER['HTTP_FRONT_END_HTTPS']) === true && $_SERVER['HTTP_FRONT_END_HTTPS'] === 'on')
|
||||
||
|
||||
(isset($_SERVER['REQUEST_SCHEME']) === true && $_SERVER['REQUEST_SCHEME'] === 'https')
|
||||
) {
|
||||
return 'https';
|
||||
}
|
||||
|
||||
return 'http';
|
||||
}
|
||||
}
|
473
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/net/Response.php
Normal file
473
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/net/Response.php
Normal file
@ -0,0 +1,473 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* The Response class represents an HTTP response. The object
|
||||
* contains the response headers, HTTP status code, and response
|
||||
* body.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* Content-Length header.
|
||||
*/
|
||||
public bool $content_length = true;
|
||||
|
||||
/**
|
||||
* This is to maintain legacy handling of output buffering
|
||||
* which causes a lot of problems. This will be removed
|
||||
* in v4
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public bool $v2_output_buffering = false;
|
||||
|
||||
/**
|
||||
* HTTP status codes
|
||||
*
|
||||
* @var array<int, ?string> $codes
|
||||
*/
|
||||
public static array $codes = [
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
102 => 'Processing',
|
||||
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
207 => 'Multi-Status',
|
||||
208 => 'Already Reported',
|
||||
|
||||
226 => 'IM Used',
|
||||
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
306 => '(Unused)',
|
||||
307 => 'Temporary Redirect',
|
||||
308 => 'Permanent Redirect',
|
||||
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Payload Too Large',
|
||||
414 => 'URI Too Long',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
424 => 'Failed Dependency',
|
||||
|
||||
426 => 'Upgrade Required',
|
||||
|
||||
428 => 'Precondition Required',
|
||||
429 => 'Too Many Requests',
|
||||
|
||||
431 => 'Request Header Fields Too Large',
|
||||
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported',
|
||||
506 => 'Variant Also Negotiates',
|
||||
507 => 'Insufficient Storage',
|
||||
508 => 'Loop Detected',
|
||||
|
||||
510 => 'Not Extended',
|
||||
511 => 'Network Authentication Required',
|
||||
];
|
||||
|
||||
/**
|
||||
* HTTP status
|
||||
*/
|
||||
protected int $status = 200;
|
||||
|
||||
/**
|
||||
* HTTP response headers
|
||||
*
|
||||
* @var array<string,int|string|array<int,string>> $headers
|
||||
*/
|
||||
protected array $headers = [];
|
||||
|
||||
/**
|
||||
* HTTP response body
|
||||
*/
|
||||
protected string $body = '';
|
||||
|
||||
/**
|
||||
* HTTP response sent
|
||||
*/
|
||||
protected bool $sent = false;
|
||||
|
||||
/**
|
||||
* These are callbacks that can process the response body before it's sent
|
||||
*
|
||||
* @var array<int, callable> $responseBodyCallbacks
|
||||
*/
|
||||
protected array $responseBodyCallbacks = [];
|
||||
|
||||
/**
|
||||
* Sets the HTTP status of the response.
|
||||
*
|
||||
* @param ?int $code HTTP status code.
|
||||
*
|
||||
* @throws Exception If invalid status code
|
||||
*
|
||||
* @return int|$this Self reference
|
||||
*/
|
||||
public function status(?int $code = null)
|
||||
{
|
||||
if ($code === null) {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
if (\array_key_exists($code, self::$codes)) {
|
||||
$this->status = $code;
|
||||
} else {
|
||||
throw new Exception('Invalid status code.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header to the response.
|
||||
*
|
||||
* @param array<string, int|string>|string $name Header name or array of names and values
|
||||
* @param ?string $value Header value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function header($name, ?string $value = null): self
|
||||
{
|
||||
if (\is_array($name)) {
|
||||
foreach ($name as $k => $v) {
|
||||
$this->headers[$k] = $v;
|
||||
}
|
||||
} else {
|
||||
$this->headers[$name] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single header from the response.
|
||||
*
|
||||
* @param string $name the name of the header
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHeader(string $name): ?string
|
||||
{
|
||||
$headers = $this->headers;
|
||||
// lowercase all the header keys
|
||||
$headers = array_change_key_case($headers, CASE_LOWER);
|
||||
return $headers[strtolower($name)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of Response->header(). Adds a header to the response.
|
||||
*
|
||||
* @param array<string, int|string>|string $name Header name or array of names and values
|
||||
* @param ?string $value Header value
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeader($name, ?string $value): self
|
||||
{
|
||||
return $this->header($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers from the response.
|
||||
*
|
||||
* @return array<string, int|string|array<int, string>>
|
||||
*/
|
||||
public function headers(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for Response->headers(). Returns the headers from the response.
|
||||
*
|
||||
* @return array<string, int|string|array<int, string>>
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes content to the response body.
|
||||
*
|
||||
* @param string $str Response content
|
||||
* @param bool $overwrite Overwrite the response body
|
||||
*
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function write(string $str, bool $overwrite = false): self
|
||||
{
|
||||
if ($overwrite === true) {
|
||||
$this->clearBody();
|
||||
}
|
||||
|
||||
$this->body .= $str;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the response body.
|
||||
*
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function clearBody(): self
|
||||
{
|
||||
$this->body = '';
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the response.
|
||||
*
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function clear(): self
|
||||
{
|
||||
$this->status = 200;
|
||||
$this->headers = [];
|
||||
$this->clearBody();
|
||||
|
||||
// This needs to clear the output buffer if it's on
|
||||
if ($this->v2_output_buffering === false && ob_get_length() > 0) {
|
||||
ob_clean();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets caching headers for the response.
|
||||
*
|
||||
* @param int|string|false $expires Expiration time as time() or as strtotime() string value
|
||||
*
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function cache($expires): self
|
||||
{
|
||||
if ($expires === false) {
|
||||
$this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
|
||||
|
||||
$this->headers['Cache-Control'] = [
|
||||
'no-store, no-cache, must-revalidate',
|
||||
'post-check=0, pre-check=0',
|
||||
'max-age=0',
|
||||
];
|
||||
|
||||
$this->headers['Pragma'] = 'no-cache';
|
||||
} else {
|
||||
$expires = \is_int($expires) ? $expires : strtotime($expires);
|
||||
$this->headers['Expires'] = gmdate('D, d M Y H:i:s', $expires) . ' GMT';
|
||||
$this->headers['Cache-Control'] = 'max-age=' . ($expires - time());
|
||||
|
||||
if (isset($this->headers['Pragma']) && $this->headers['Pragma'] === 'no-cache') {
|
||||
unset($this->headers['Pragma']);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends HTTP headers.
|
||||
*
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function sendHeaders(): self
|
||||
{
|
||||
// Send status code header
|
||||
if (strpos(\PHP_SAPI, 'cgi') !== false) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->setRealHeader(
|
||||
sprintf(
|
||||
'Status: %d %s',
|
||||
$this->status,
|
||||
self::$codes[$this->status]
|
||||
),
|
||||
true
|
||||
);
|
||||
// @codeCoverageIgnoreEnd
|
||||
} else {
|
||||
$this->setRealHeader(
|
||||
sprintf(
|
||||
'%s %d %s',
|
||||
$_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1',
|
||||
$this->status,
|
||||
self::$codes[$this->status]
|
||||
),
|
||||
true,
|
||||
$this->status
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->content_length === true) {
|
||||
// Send content length
|
||||
$length = $this->getContentLength();
|
||||
|
||||
if ($length > 0) {
|
||||
$this->setHeader('Content-Length', (string) $length);
|
||||
}
|
||||
}
|
||||
|
||||
// Send other headers
|
||||
foreach ($this->headers as $field => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $v) {
|
||||
$this->setRealHeader($field . ': ' . $v, false);
|
||||
}
|
||||
} else {
|
||||
$this->setRealHeader($field . ': ' . $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a real header. Mostly used for test mocking.
|
||||
*
|
||||
* @param string $header_string The header string you would pass to header()
|
||||
* @param bool $replace The optional replace parameter indicates whether the
|
||||
* header should replace a previous similar header, or add a second header of
|
||||
* the same type. By default it will replace, but if you pass in false as the
|
||||
* second argument you can force multiple headers of the same type.
|
||||
* @param int $response_code The response code to send
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function setRealHeader(string $header_string, bool $replace = true, int $response_code = 0): self
|
||||
{
|
||||
header($header_string, $replace, $response_code);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content length.
|
||||
*/
|
||||
public function getContentLength(): int
|
||||
{
|
||||
return \extension_loaded('mbstring') ?
|
||||
mb_strlen($this->body, 'latin1') :
|
||||
\strlen($this->body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response body
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether response body was sent.
|
||||
*/
|
||||
public function sent(): bool
|
||||
{
|
||||
return $this->sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the response as sent.
|
||||
*/
|
||||
public function markAsSent(): void
|
||||
{
|
||||
$this->sent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a HTTP response.
|
||||
*/
|
||||
public function send(): void
|
||||
{
|
||||
// legacy way of handling this
|
||||
if ($this->v2_output_buffering === true) {
|
||||
if (ob_get_length() > 0) {
|
||||
ob_end_clean(); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
|
||||
// Only for the v3 output buffering.
|
||||
if ($this->v2_output_buffering === false) {
|
||||
$this->processResponseCallbacks();
|
||||
}
|
||||
|
||||
if (headers_sent() === false) {
|
||||
$this->sendHeaders(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
echo $this->body;
|
||||
|
||||
$this->sent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to process the response body before it's sent. These are processed in the order
|
||||
* they are added
|
||||
*
|
||||
* @param callable $callback The callback to process the response body
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addResponseBodyCallback(callable $callback): void
|
||||
{
|
||||
$this->responseBodyCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycles through the response body callbacks and processes them in order
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processResponseCallbacks(): void
|
||||
{
|
||||
foreach ($this->responseBodyCallbacks as $callback) {
|
||||
$this->body = $callback($this->body);
|
||||
}
|
||||
}
|
||||
}
|
266
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/net/Route.php
Normal file
266
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/net/Route.php
Normal file
@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
/**
|
||||
* The Route class is responsible for routing an HTTP request to
|
||||
* an assigned callback function. The Router tries to match the
|
||||
* requested URL against a series of URL patterns.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Route
|
||||
{
|
||||
/**
|
||||
* URL pattern
|
||||
*/
|
||||
public string $pattern;
|
||||
|
||||
/**
|
||||
* Callback function
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $callback;
|
||||
|
||||
/**
|
||||
* HTTP methods
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public array $methods = [];
|
||||
|
||||
/**
|
||||
* Route parameters
|
||||
*
|
||||
* @var array<int, ?string>
|
||||
*/
|
||||
public array $params = [];
|
||||
|
||||
/**
|
||||
* Matching regular expression
|
||||
*/
|
||||
public ?string $regex = null;
|
||||
|
||||
/**
|
||||
* URL splat content
|
||||
*/
|
||||
public string $splat = '';
|
||||
|
||||
/**
|
||||
* Pass self in callback parameters
|
||||
*/
|
||||
public bool $pass = false;
|
||||
|
||||
/**
|
||||
* The alias is a way to identify the route using a simple name ex: 'login' instead of /admin/login
|
||||
*/
|
||||
public string $alias = '';
|
||||
|
||||
/**
|
||||
* The middleware to be applied to the route
|
||||
*
|
||||
* @var array<int, callable|object|string>
|
||||
*/
|
||||
public array $middleware = [];
|
||||
|
||||
/** Whether the response for this route should be streamed. */
|
||||
public bool $is_streamed = false;
|
||||
|
||||
/**
|
||||
* If this route is streamed, the headers to be sent before the response.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public array $streamed_headers = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pattern URL pattern
|
||||
* @param callable|string $callback Callback function
|
||||
* @param array<int, string> $methods HTTP methods
|
||||
* @param bool $pass Pass self in callback parameters
|
||||
*/
|
||||
public function __construct(string $pattern, $callback, array $methods, bool $pass, string $alias = '')
|
||||
{
|
||||
$this->pattern = $pattern;
|
||||
$this->callback = $callback;
|
||||
$this->methods = $methods;
|
||||
$this->pass = $pass;
|
||||
$this->alias = $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a URL matches the route pattern. Also parses named parameters in the URL.
|
||||
*
|
||||
* @param string $url Requested URL (original format, not URL decoded)
|
||||
* @param bool $case_sensitive Case sensitive matching
|
||||
*
|
||||
* @return bool Match status
|
||||
*/
|
||||
public function matchUrl(string $url, bool $case_sensitive = false): bool
|
||||
{
|
||||
// Wildcard or exact match
|
||||
if ($this->pattern === '*' || $this->pattern === $url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
$last_char = substr($this->pattern, -1);
|
||||
|
||||
// Get splat
|
||||
if ($last_char === '*') {
|
||||
$n = 0;
|
||||
$len = \strlen($url);
|
||||
$count = substr_count($this->pattern, '/');
|
||||
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if ($url[$i] === '/') {
|
||||
++$n;
|
||||
}
|
||||
|
||||
if ($n === $count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->splat = urldecode(strval(substr($url, $i + 1)));
|
||||
}
|
||||
|
||||
// Build the regex for matching
|
||||
$pattern_utf_chars_encoded = preg_replace_callback(
|
||||
'#(\\p{L}+)#u',
|
||||
static function ($matches) {
|
||||
return urlencode($matches[0]);
|
||||
},
|
||||
$this->pattern
|
||||
);
|
||||
$regex = str_replace([')', '/*'], [')?', '(/?|/.*?)'], $pattern_utf_chars_encoded);
|
||||
|
||||
$regex = preg_replace_callback(
|
||||
'#@([\w]+)(:([^/\(\)]*))?#',
|
||||
static function ($matches) use (&$ids) {
|
||||
$ids[$matches[1]] = null;
|
||||
if (isset($matches[3])) {
|
||||
return '(?P<' . $matches[1] . '>' . $matches[3] . ')';
|
||||
}
|
||||
|
||||
return '(?P<' . $matches[1] . '>[^/\?]+)';
|
||||
},
|
||||
$regex
|
||||
);
|
||||
|
||||
$regex .= $last_char === '/' ? '?' : '/?';
|
||||
|
||||
// Attempt to match route and named parameters
|
||||
if (!preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (array_keys($ids) as $k) {
|
||||
$this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
|
||||
}
|
||||
|
||||
$this->regex = $regex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an HTTP method matches the route methods.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
*
|
||||
* @return bool Match status
|
||||
*/
|
||||
public function matchMethod(string $method): bool
|
||||
{
|
||||
return \count(array_intersect([$method, '*'], $this->methods)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an alias matches the route alias.
|
||||
*/
|
||||
public function matchAlias(string $alias): bool
|
||||
{
|
||||
return $this->alias === $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrates the route url with the given parameters
|
||||
*
|
||||
* @param array<string, mixed> $params the parameters to pass to the route
|
||||
*/
|
||||
public function hydrateUrl(array $params = []): string
|
||||
{
|
||||
$url = preg_replace_callback("/(?:@([\w]+)(?:\:([^\/]+))?\)*)/i", function ($match) use ($params) {
|
||||
if (isset($match[1]) && isset($params[$match[1]])) {
|
||||
return $params[$match[1]];
|
||||
}
|
||||
}, $this->pattern);
|
||||
|
||||
// catches potential optional parameter
|
||||
$url = str_replace('(/', '/', $url);
|
||||
// trim any trailing slashes
|
||||
if ($url !== '/') {
|
||||
$url = rtrim($url, '/');
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the route alias
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlias(string $alias): self
|
||||
{
|
||||
$this->alias = $alias;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the route middleware
|
||||
*
|
||||
* @param array<int, callable|string>|callable|string $middleware
|
||||
*/
|
||||
public function addMiddleware($middleware): self
|
||||
{
|
||||
if (is_array($middleware) === true) {
|
||||
$this->middleware = array_merge($this->middleware, $middleware);
|
||||
} else {
|
||||
$this->middleware[] = $middleware;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the response should be streamed
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function stream(): self
|
||||
{
|
||||
$this->is_streamed = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will allow the response for this route to be streamed.
|
||||
*
|
||||
* @param array<string, mixed> $headers a key value of headers to set before the stream starts.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function streamWithHeaders(array $headers): self
|
||||
{
|
||||
$this->is_streamed = true;
|
||||
$this->streamed_headers = $headers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
330
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/net/Router.php
Normal file
330
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/net/Router.php
Normal file
@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use Exception;
|
||||
use flight\net\Route;
|
||||
|
||||
/**
|
||||
* The Router class is responsible for routing an HTTP request to
|
||||
* an assigned callback function. The Router tries to match the
|
||||
* requested URL against a series of URL patterns.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
/**
|
||||
* Case sensitive matching.
|
||||
*/
|
||||
public bool $case_sensitive = false;
|
||||
|
||||
/**
|
||||
* Mapped routes.
|
||||
*
|
||||
* @var array<int,Route> $routes
|
||||
*/
|
||||
protected array $routes = [];
|
||||
|
||||
/**
|
||||
* The current route that is has been found and executed.
|
||||
*/
|
||||
public ?Route $executedRoute = null;
|
||||
|
||||
/**
|
||||
* Pointer to current route.
|
||||
*/
|
||||
protected int $index = 0;
|
||||
|
||||
/**
|
||||
* When groups are used, this is mapped against all the routes
|
||||
*/
|
||||
protected string $groupPrefix = '';
|
||||
|
||||
/**
|
||||
* Group Middleware
|
||||
*
|
||||
* @var array<int,mixed>
|
||||
*/
|
||||
protected array $groupMiddlewares = [];
|
||||
|
||||
/**
|
||||
* Allowed HTTP methods
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
|
||||
|
||||
/**
|
||||
* Gets mapped routes.
|
||||
*
|
||||
* @return array<int,Route> Array of routes
|
||||
*/
|
||||
public function getRoutes(): array
|
||||
{
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all routes in the router.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->routes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a URL pattern to a callback function.
|
||||
*
|
||||
* @param string $pattern URL pattern to match.
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback.
|
||||
* @param string $route_alias Alias for the route.
|
||||
*/
|
||||
public function map(string $pattern, $callback, bool $pass_route = false, string $route_alias = ''): Route
|
||||
{
|
||||
|
||||
// This means that the route ies defined in a group, but the defined route is the base
|
||||
// url path. Note the '' in route()
|
||||
// Ex: Flight::group('/api', function() {
|
||||
// Flight::route('', function() {});
|
||||
// }
|
||||
// Keep the space so that it can execute the below code normally
|
||||
if ($this->groupPrefix !== '') {
|
||||
$url = ltrim($pattern);
|
||||
} else {
|
||||
$url = trim($pattern);
|
||||
}
|
||||
|
||||
$methods = ['*'];
|
||||
|
||||
if (strpos($url, ' ') !== false) {
|
||||
[$method, $url] = explode(' ', $url, 2);
|
||||
$url = trim($url);
|
||||
$methods = explode('|', $method);
|
||||
|
||||
// Add head requests to get methods, should they come in as a get request
|
||||
if (in_array('GET', $methods, true) === true && in_array('HEAD', $methods, true) === false) {
|
||||
$methods[] = 'HEAD';
|
||||
}
|
||||
}
|
||||
|
||||
// And this finishes it off.
|
||||
if ($this->groupPrefix !== '') {
|
||||
$url = rtrim($this->groupPrefix . $url);
|
||||
}
|
||||
|
||||
$route = new Route($url, $callback, $methods, $pass_route, $route_alias);
|
||||
|
||||
// to handle group middleware
|
||||
foreach ($this->groupMiddlewares as $gm) {
|
||||
$route->addMiddleware($gm);
|
||||
}
|
||||
|
||||
$this->routes[] = $route;
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GET based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function get(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('GET ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a POST based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function post(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('POST ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PUT based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function put(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('PUT ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PATCH based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function patch(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('PATCH ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DELETE based route
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callable|string $callback Callback function or string class->method
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
* @param string $alias Alias for the route
|
||||
*/
|
||||
public function delete(string $pattern, $callback, bool $pass_route = false, string $alias = ''): Route
|
||||
{
|
||||
return $this->map('DELETE ' . $pattern, $callback, $pass_route, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group together a set of routes
|
||||
*
|
||||
* @param string $groupPrefix group URL prefix (such as /api/v1)
|
||||
* @param callable $callback The necessary calling that holds the Router class
|
||||
* @param array<int, callable|object> $groupMiddlewares
|
||||
* The middlewares to be applied to the group. Example: `[$middleware1, $middleware2]`
|
||||
*/
|
||||
public function group(string $groupPrefix, callable $callback, array $groupMiddlewares = []): void
|
||||
{
|
||||
$oldGroupPrefix = $this->groupPrefix;
|
||||
$oldGroupMiddlewares = $this->groupMiddlewares;
|
||||
$this->groupPrefix .= $groupPrefix;
|
||||
$this->groupMiddlewares = array_merge($this->groupMiddlewares, $groupMiddlewares);
|
||||
$callback($this);
|
||||
$this->groupPrefix = $oldGroupPrefix;
|
||||
$this->groupMiddlewares = $oldGroupMiddlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes the current request.
|
||||
*
|
||||
* @return false|Route Matching route or false if no match
|
||||
*/
|
||||
public function route(Request $request)
|
||||
{
|
||||
while ($route = $this->current()) {
|
||||
$urlMatches = $route->matchUrl($request->url, $this->case_sensitive);
|
||||
$methodMatches = $route->matchMethod($request->method);
|
||||
if ($urlMatches === true && $methodMatches === true) {
|
||||
$this->executedRoute = $route;
|
||||
return $route;
|
||||
// capture the route but don't execute it. We'll use this in Engine->start() to throw a 405
|
||||
} elseif ($urlMatches === true && $methodMatches === false) {
|
||||
$this->executedRoute = $route;
|
||||
}
|
||||
$this->next();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL for a given route alias
|
||||
*
|
||||
* @param string $alias the alias to match
|
||||
* @param array<string,mixed> $params the parameters to pass to the route
|
||||
*/
|
||||
public function getUrlByAlias(string $alias, array $params = []): string
|
||||
{
|
||||
$potential_aliases = [];
|
||||
foreach ($this->routes as $route) {
|
||||
$potential_aliases[] = $route->alias;
|
||||
if ($route->matchAlias($alias)) {
|
||||
// This will make it so the params that already
|
||||
// exist in the url will be passed in.
|
||||
if (!empty($this->executedRoute->params)) {
|
||||
$params = $params + $this->executedRoute->params;
|
||||
}
|
||||
return $route->hydrateUrl($params);
|
||||
}
|
||||
}
|
||||
|
||||
// use a levenshtein to find the closest match and make a recommendation
|
||||
$closest_match = '';
|
||||
$closest_match_distance = 0;
|
||||
foreach ($potential_aliases as $potential_alias) {
|
||||
$levenshtein_distance = levenshtein($alias, $potential_alias);
|
||||
if ($levenshtein_distance > $closest_match_distance) {
|
||||
$closest_match = $potential_alias;
|
||||
$closest_match_distance = $levenshtein_distance;
|
||||
}
|
||||
}
|
||||
|
||||
$exception_message = 'No route found with alias: \'' . $alias . '\'.';
|
||||
if ($closest_match !== '') {
|
||||
$exception_message .= ' Did you mean \'' . $closest_match . '\'?';
|
||||
}
|
||||
|
||||
throw new Exception($exception_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds the current route index.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->index = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if more routes can be iterated.
|
||||
*
|
||||
* @return bool More routes
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->routes[$this->index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current route.
|
||||
*
|
||||
* @return false|Route
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->routes[$this->index] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous route.
|
||||
*/
|
||||
public function previous(): void
|
||||
{
|
||||
--$this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next route.
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
++$this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to the first route.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->rewind();
|
||||
}
|
||||
}
|
197
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/template/View.php
Normal file
197
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/template/View.php
Normal file
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\template;
|
||||
|
||||
/**
|
||||
* The View class represents output to be displayed. It provides
|
||||
* methods for managing view data and inserts the data into
|
||||
* view templates upon rendering.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
*/
|
||||
class View
|
||||
{
|
||||
/** Location of view templates. */
|
||||
public string $path;
|
||||
|
||||
/** File extension. */
|
||||
public string $extension = '.php';
|
||||
|
||||
/**
|
||||
* View variables.
|
||||
*
|
||||
* @var array<string, mixed> $vars
|
||||
*/
|
||||
protected array $vars = [];
|
||||
|
||||
/** Template file. */
|
||||
private string $template;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $path Path to templates directory
|
||||
*/
|
||||
public function __construct(string $path = '.')
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a template variable.
|
||||
*
|
||||
* @return mixed Variable value or `null` if doesn't exists
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
return $this->vars[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a template variable.
|
||||
*
|
||||
* @param string|iterable<string, mixed> $key
|
||||
* @param mixed $value Value
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function set($key, $value = null): self
|
||||
{
|
||||
if (\is_iterable($key)) {
|
||||
foreach ($key as $k => $v) {
|
||||
$this->vars[$k] = $v;
|
||||
}
|
||||
} else {
|
||||
$this->vars[$key] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a template variable is set.
|
||||
*
|
||||
* @return bool If key exists
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->vars[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets a template variable. If no key is passed in, clear all variables.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clear(?string $key = null): self
|
||||
{
|
||||
if ($key === null) {
|
||||
$this->vars = [];
|
||||
} else {
|
||||
unset($this->vars[$key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a template.
|
||||
*
|
||||
* @param string $file Template file
|
||||
* @param ?array<string, mixed> $data Template data
|
||||
*
|
||||
* @throws \Exception If template not found
|
||||
*/
|
||||
public function render(string $file, ?array $data = null): void
|
||||
{
|
||||
$this->template = $this->getTemplate($file);
|
||||
|
||||
if (!\file_exists($this->template)) {
|
||||
$normalized_path = self::normalizePath($this->template);
|
||||
throw new \Exception("Template file not found: {$normalized_path}.");
|
||||
}
|
||||
|
||||
if (\is_array($data)) {
|
||||
$this->vars = \array_merge($this->vars, $data);
|
||||
}
|
||||
|
||||
\extract($this->vars);
|
||||
|
||||
include $this->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output of a template.
|
||||
*
|
||||
* @param string $file Template file
|
||||
* @param ?array<string, mixed> $data Template data
|
||||
*
|
||||
* @return string Output of template
|
||||
*/
|
||||
public function fetch(string $file, ?array $data = null): string
|
||||
{
|
||||
\ob_start();
|
||||
|
||||
$this->render($file, $data);
|
||||
|
||||
return \ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a template file exists.
|
||||
*
|
||||
* @param string $file Template file
|
||||
*
|
||||
* @return bool Template file exists
|
||||
*/
|
||||
public function exists(string $file): bool
|
||||
{
|
||||
return \file_exists($this->getTemplate($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path to a template file.
|
||||
*
|
||||
* @param string $file Template file
|
||||
*
|
||||
* @return string Template file location
|
||||
*/
|
||||
public function getTemplate(string $file): string
|
||||
{
|
||||
$ext = $this->extension;
|
||||
|
||||
if (!empty($ext) && (\substr($file, -1 * \strlen($ext)) != $ext)) {
|
||||
$file .= $ext;
|
||||
}
|
||||
|
||||
$is_windows = \strtoupper(\substr(PHP_OS, 0, 3)) === 'WIN';
|
||||
|
||||
if ((\substr($file, 0, 1) === '/') || ($is_windows && \substr($file, 1, 1) === ':')) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $this->path . DIRECTORY_SEPARATOR . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays escaped output.
|
||||
*
|
||||
* @param string $str String to escape
|
||||
*
|
||||
* @return string Escaped string
|
||||
*/
|
||||
public function e(string $str): string
|
||||
{
|
||||
$value = \htmlentities($str);
|
||||
echo $value;
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected static function normalizePath(string $path, string $separator = DIRECTORY_SEPARATOR): string
|
||||
{
|
||||
return \str_replace(['\\', '/'], $separator, $path);
|
||||
}
|
||||
}
|
223
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/util/Collection.php
Normal file
223
R4.01_R4.A.10/td_tp/tp5/src/api_php/flight/util/Collection.php
Normal file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace flight\util;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* The Collection class allows you to access a set of data
|
||||
* using both array and object notation.
|
||||
*
|
||||
* @license MIT, http://flightphp.com/license
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @implements ArrayAccess<string, mixed>
|
||||
* @implements Iterator<string, mixed>
|
||||
*/
|
||||
class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Collection data.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $data;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array<string, mixed> $data Initial data
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an item.
|
||||
*
|
||||
* @return mixed Value if `$key` exists in collection data, otherwise returns `NULL`
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->data[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an item.
|
||||
*
|
||||
* @param mixed $value Value
|
||||
*/
|
||||
public function __set(string $key, $value): void
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item exists.
|
||||
*/
|
||||
public function __isset(string $key): bool
|
||||
{
|
||||
return isset($this->data[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item.
|
||||
*/
|
||||
public function __unset(string $key): void
|
||||
{
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an item at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->data[$offset] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an item at the offset.
|
||||
*
|
||||
* @param ?string $offset Offset
|
||||
* @param mixed $value Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
if ($offset === null) {
|
||||
$this->data[] = $value;
|
||||
} else {
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item exists at the offset.
|
||||
*
|
||||
* @param string $offset
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->data[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item at the offset.
|
||||
*
|
||||
* @param string $offset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the collection.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
reset($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current collection item.
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return current($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current collection key.
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return key($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next collection value.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next(): void
|
||||
{
|
||||
next($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current collection key is valid.
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return key($this->data) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the collection.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item keys.
|
||||
*
|
||||
* @return array<int, string> Collection keys
|
||||
*/
|
||||
public function keys(): array
|
||||
{
|
||||
return array_keys($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the collection data.
|
||||
*
|
||||
* @return array<string, mixed> Collection data
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection data.
|
||||
*
|
||||
* @param array<string, mixed> $data New collection data
|
||||
*/
|
||||
public function setData(array $data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the collection.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// This file is only here so that the PHP8 attribute for doesn't throw an error in files
|
||||
class ReturnTypeWillChange
|
||||
{
|
||||
}
|
5
R4.01_R4.A.10/td_tp/tp5/src/api_php/htaccess
Normal file
5
R4.01_R4.A.10/td_tp/tp5/src/api_php/htaccess
Normal file
@ -0,0 +1,5 @@
|
||||
Require method GET POST PUT DELETE OPTIONS
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php [QSA,L]
|
68
R4.01_R4.A.10/td_tp/tp5/src/api_php/index.php
Normal file
68
R4.01_R4.A.10/td_tp/tp5/src/api_php/index.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
require 'flight/Flight.php';
|
||||
require 'model/model.php';
|
||||
|
||||
Todo::init();
|
||||
|
||||
Flight::route('GET /todo(/@id)','getTodos');
|
||||
Flight::route('POST /todo','addTodo');
|
||||
Flight::route('DELETE /todo/@id','deleteTodo');
|
||||
Flight::route('PUT /todo/@id','updateTodo');
|
||||
|
||||
function addTodo()
|
||||
{
|
||||
$todo = [
|
||||
"title" => Flight::request()->data->title ,
|
||||
"done" => Flight::request()->data->done
|
||||
];
|
||||
|
||||
$id = Todo::create($todo);
|
||||
Flight::response()->header("Location",Flight::request()->url.$id);
|
||||
$todo['id'] = $id;
|
||||
Flight::json($todo,201);
|
||||
|
||||
}
|
||||
|
||||
function deleteTodo($id)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
function updateTodo($id)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
function getTodos($id = null)
|
||||
{
|
||||
$filter = Flight::request()->query->filter ?? "all";
|
||||
|
||||
if ($id === null){
|
||||
switch($filter){
|
||||
case "done":
|
||||
$todos = Todo::findCompleted();
|
||||
break;
|
||||
case "active":
|
||||
$todos = Todo::findUnCompleted();
|
||||
break;
|
||||
default:
|
||||
$todos = Todo::findAll();
|
||||
}
|
||||
Flight::json(
|
||||
[
|
||||
"results" => $todos
|
||||
]
|
||||
);
|
||||
|
||||
} else {
|
||||
$todo = Todo::find($id);
|
||||
if ($todo)
|
||||
Flight::json($todo);
|
||||
else
|
||||
Flight::halt(404);
|
||||
}
|
||||
}
|
||||
|
||||
Flight::start();
|
114
R4.01_R4.A.10/td_tp/tp5/src/api_php/model/model.php
Normal file
114
R4.01_R4.A.10/td_tp/tp5/src/api_php/model/model.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
class Database {
|
||||
private $host;
|
||||
private $user;
|
||||
private $pass;
|
||||
private $dbname;
|
||||
private $pdo;
|
||||
|
||||
public function __construct($host, $user, $pass, $dbname) {
|
||||
$this->host = $host;
|
||||
$this->user = $user;
|
||||
$this->pass = $pass;
|
||||
$this->dbname = $dbname;
|
||||
}
|
||||
public function connect() {
|
||||
$driver_options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
];
|
||||
$dsn = "mysql:host=$this->host;dbname=$this->dbname";
|
||||
$this->pdo = new PDO($dsn, $this->user, $this->pass,$driver_options);
|
||||
}
|
||||
|
||||
public function query($sql, $params = []) {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
if ($stmt->execute($params) === true)
|
||||
return $stmt;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function lastInsertId(){
|
||||
return $this->pdo->lastInsertId();
|
||||
}
|
||||
}
|
||||
|
||||
class res {
|
||||
public $id;
|
||||
public $title;
|
||||
public $done;
|
||||
public function __construct(){
|
||||
$this->done = (bool)$this->done;;
|
||||
}
|
||||
}
|
||||
|
||||
class Todo {
|
||||
static private $db = NULL;
|
||||
|
||||
public static function todo($id,$title,$done){
|
||||
return ["id"=>$id,"title"=>$title,"done"=>(bool)$done];
|
||||
|
||||
}
|
||||
public static function init(){
|
||||
//TODO
|
||||
self::$db = new Database("localhost","test","test","test"); // vos parametres !!!!
|
||||
self::$db->connect();
|
||||
}
|
||||
|
||||
public static function find($id) {
|
||||
$sql = "SELECT * FROM todo WHERE id = ?";
|
||||
$stmt = self::$db->query($sql,[$id]);
|
||||
|
||||
$stmt->setFetchMode(PDO::FETCH_CLASS, 'res');
|
||||
$result = $stmt->fetch();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function findCompleted(){
|
||||
$sql = "SELECT * FROM todo WHERE done = '1'";
|
||||
$stmt = self::$db->query($sql);
|
||||
|
||||
$stmt->setFetchMode(PDO::FETCH_CLASS, 'res');
|
||||
$result = $stmt->fetchAll();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function findUnCompleted(){
|
||||
$sql = "SELECT * FROM todo WHERE done = '0'";
|
||||
$stmt = self::$db->query($sql);
|
||||
|
||||
$stmt->setFetchMode(PDO::FETCH_CLASS, 'res');
|
||||
$result = $stmt->fetchAll();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function findAll() {
|
||||
$sql = "SELECT id,title, done FROM todo";
|
||||
$stmt = self::$db->query($sql);
|
||||
// $result = $stmt->fetchAll(PDO::FETCH_FUNC, "Todo::todo");
|
||||
$stmt->setFetchMode(PDO::FETCH_CLASS, 'res');
|
||||
|
||||
$result = $stmt->fetchAll();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function update($todo){
|
||||
|
||||
$sql = "UPDATE todo SET title = ?, done = ? WHERE id = ?";
|
||||
$stmt = self::$db->query($sql,[$todo['title'],(int)$todo['done'],$todo['id']]);
|
||||
}
|
||||
|
||||
public static function create($todo){
|
||||
|
||||
$sql = "INSERT INTO todo (title, done) VALUES (?, ?)";
|
||||
$stmt = self::$db->query($sql,[$todo['title'],(int)$todo['done']]);
|
||||
return self::$db->lastInsertId();
|
||||
}
|
||||
|
||||
public static function delete($id){
|
||||
$sql = "DELETE FROM todo WHERE id=?";
|
||||
$stmt = self::$db->query($sql,[$id]);
|
||||
}
|
||||
}
|
||||
|
9
R4.01_R4.A.10/td_tp/tp5/src/todo-riot/css/todo.css
Normal file
9
R4.01_R4.A.10/td_tp/tp5/src/todo-riot/css/todo.css
Normal file
@ -0,0 +1,9 @@
|
||||
p.todo {
|
||||
display : flex;
|
||||
justify-content : space-between;
|
||||
}
|
||||
|
||||
.done {
|
||||
text-decoration: line-through;
|
||||
color: #ccc;
|
||||
}
|
43
R4.01_R4.A.10/td_tp/tp5/src/todo-riot/index.html
Normal file
43
R4.01_R4.A.10/td_tp/tp5/src/todo-riot/index.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Riot todo</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<script src="todo.riot" type="riot"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/riot/9.4.4/riot+compiler.min.js" integrity="sha512-sMPQdAnmCCQmymthALQzoFXGFmG1hhiazFuC/8Y5hLentlAWNqs+eQQJJ/yeGC4DLk/JwLL3jGk6yoEEDNb+7w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://unpkg.com/@riotjs/route@9.2/index.umd.js"></script>
|
||||
<link rel="stylesheet" href="css/todo.css">
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
||||
>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="container">
|
||||
<todo></todo>
|
||||
</main>
|
||||
<script type="module">
|
||||
import makeDataService from "./js/api.js";
|
||||
|
||||
riot.register('router', route.Router);
|
||||
riot.register('route', route.Route);
|
||||
|
||||
riot.compile().then(async () => {
|
||||
let sa = makeDataService();
|
||||
riot.install(function(component){
|
||||
component.serviceData = sa;
|
||||
})
|
||||
riot.mount('todo', {
|
||||
title: 'I want to behave!',
|
||||
todos: []
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
9
R4.01_R4.A.10/td_tp/tp5/src/todo-riot/js/api.js
Normal file
9
R4.01_R4.A.10/td_tp/tp5/src/todo-riot/js/api.js
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
export default function makeDataService(){
|
||||
let url = 'votre url';
|
||||
let service = {
|
||||
// TODO
|
||||
};
|
||||
|
||||
return service;
|
||||
}
|
113
R4.01_R4.A.10/td_tp/tp5/src/todo-riot/todo.riot
Normal file
113
R4.01_R4.A.10/td_tp/tp5/src/todo-riot/todo.riot
Normal file
@ -0,0 +1,113 @@
|
||||
<todo>
|
||||
<router base = {base}>
|
||||
<route path="(#)?/:filter?"
|
||||
on-before-mount = { changeFilter }
|
||||
on-before-update = { changeFilter }
|
||||
>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li>{this.state.todos.filter(todo => !todo.done).length} todos left</li>
|
||||
</ul>
|
||||
<ul>
|
||||
|
||||
<li each={filter in ['all','active','done']}>
|
||||
<!--template if = {state.filter !== filter}-->
|
||||
<a class={state.filter=== filter ? 'contrast':''} href="#/{filter}" > {filter.toUpperCase()}</a>
|
||||
<!--template>
|
||||
<template if = {state.filter === filter}>{filter.toUpperCase()}</template-->
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<b>{props.title}</b>
|
||||
</header>
|
||||
<template each={ todo in filterTodos() } key = {todo.id}>
|
||||
<p class="todo">
|
||||
<label class={ todo.done ? 'completed' : null }>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={ todo.done }
|
||||
onclick={ () => toggle(todo) } />
|
||||
<span class = {todo.done ? 'done':''}> { todo.title } </span>
|
||||
</label>
|
||||
<a href="#" onclick={(e)=>remove(e,todo)}><i class="fa-solid fa-trash"></i></a>
|
||||
</p>
|
||||
<hr>
|
||||
</template>
|
||||
</article>
|
||||
<form onsubmit={ add }>
|
||||
<fieldset role="group">
|
||||
<input type="text" oninput={ edit } />
|
||||
<input type="button" disabled={ !state.text } value="Add #{ state.todos.length + 1 }">
|
||||
<input type="button" disabled = { !state.todos.find ( e => e.done) } onclick={ clear } value="Clear done">
|
||||
</fieldset>
|
||||
</form>
|
||||
</route>
|
||||
</router>
|
||||
<script>
|
||||
export default {
|
||||
base : 'url', // Votre URL
|
||||
changeFilter(r){
|
||||
this.state.filter = r.params.filter || 'all'
|
||||
},
|
||||
async onBeforeMount(props, state) {
|
||||
// initial state
|
||||
this.state = {
|
||||
todos: props.todos,
|
||||
text: '',
|
||||
filter:'all',
|
||||
}
|
||||
let todos = await this.serviceData.getTodos()
|
||||
this.state.todos = todos;
|
||||
this.update()
|
||||
},
|
||||
filterTodos(){
|
||||
if (this.state.filter === 'all')
|
||||
return this.state.todos
|
||||
|
||||
if (this.state.filter === 'active')
|
||||
return this.state.todos.filter(e=> !e.done)
|
||||
|
||||
if (this.state.filter === 'done')
|
||||
return this.state.todos.filter(e=> e.done)
|
||||
},
|
||||
async remove(e,todo){
|
||||
e.preventDefault()
|
||||
let res = await this.serviceData.removeTodo(todo)
|
||||
let todos = await this.serviceData.getTodos()
|
||||
this.state.todos = todos
|
||||
this.update()
|
||||
},
|
||||
|
||||
edit(e) {
|
||||
// update only the text state
|
||||
this.update({
|
||||
text: e.target.value
|
||||
})
|
||||
},
|
||||
async clear(e) {
|
||||
e.preventDefault()
|
||||
let res = await this.serviceData.clearDone()
|
||||
this.state.todos = this.state.todos.filter(todo => !todo.done)
|
||||
|
||||
},
|
||||
async add(e) {
|
||||
e.preventDefault()
|
||||
let text = this.state.text;
|
||||
let res = await this.serviceData.addTodo({title : text , done : false})
|
||||
let todos = await this.serviceData.getTodos()
|
||||
this.state.todos = todos
|
||||
this.update()
|
||||
},
|
||||
async toggle(todo) {
|
||||
let res = await this.serviceData.toggleTodo(todo);
|
||||
let todos = await this.serviceData.getTodos()
|
||||
this.state.todos = todos
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</todo>
|
Loading…
x
Reference in New Issue
Block a user