diff --git a/README.md b/README.md index 8c824f7..b9f8019 100644 --- a/README.md +++ b/README.md @@ -25,25 +25,10 @@ Flight::route('/', function() { Flight::start(); ``` -[Learn more](https://docs.flightphp.com/learn) - # Want to setup a Skeleton/Boilerplate project quickly? Head over to the [flightphp/skeleton](https://github.com/flightphp/skeleton) repo to get started! -# Need some inspiration? - -While these are not officially sponsored by the FlightPHP Team, these could give you ideas on how to structure your own projects that are built with Flight! - -- https://github.com/markhughes/flight-skeleton - Basic Skeleton App -- https://github.com/Skayo/FlightWiki - Example Wiki -- https://github.com/itinnovator/myphp-app - The IT-Innovator PHP Framework Application -- https://github.com/casgin/LittleEducationalCMS - LittleEducationalCMS (Spanish) -- https://github.com/chiccomagnus/PGAPI - Italian Yellow Pages API -- https://github.com/recepuncu/cms - Generic Content Management System (with....very little documentation) -- https://github.com/ycrao/tinyme - A tiny php framework based on Flight and medoo. -- https://github.com/paddypei/Flight-MVC - Example MVC Application - # Requirements Flight requires `PHP 7.4` or greater. @@ -183,919 +168,6 @@ Flight::route('/', [$greeting, 'hello']); Routes are matched in the order they are defined. The first route to match a request will be invoked. -## Method Routing - -By default, route patterns are matched against all request methods. You can respond -to specific methods by placing an identifier before the URL. - -```php -Flight::route('GET /', function () { - echo 'I received a GET request.'; -}); - -Flight::route('POST /', function () { - echo 'I received a POST request.'; -}); -``` - -You can also map multiple methods to a single callback by using a `|` delimiter: - -```php -Flight::route('GET|POST /', function () { - echo 'I received either a GET or a POST request.'; -}); -``` - -## Regular Expressions - -You can use regular expressions in your routes: - -```php -Flight::route('/user/[0-9]+', function () { - // This will match /user/1234 -}); -``` - -## Named Parameters - -You can specify named parameters in your routes which will be passed along to -your callback function. - -```php -Flight::route('/@name/@id', function (string $name, string $id) { - echo "hello, $name ($id)!"; -}); -``` - -You can also include regular expressions with your named parameters by using -the `:` delimiter: - -```php -Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) { - // This will match /bob/123 - // But will not match /bob/12345 -}); -``` - -Matching regex groups `()` with named parameters isn't supported. - -## Optional Parameters - -You can specify named parameters that are optional for matching by wrapping -segments in parentheses. - -```php -Flight::route( - '/blog(/@year(/@month(/@day)))', - function(?string $year, ?string $month, ?string $day) { - // This will match the following URLS: - // /blog/2012/12/10 - // /blog/2012/12 - // /blog/2012 - // /blog - } -); -``` - -Any optional parameters that are not matched will be passed in as NULL. - -## Wildcards - -Matching is only done on individual URL segments. If you want to match multiple -segments you can use the `*` wildcard. - -```php -Flight::route('/blog/*', function () { - // This will match /blog/2000/02/01 -}); -``` - -To route all requests to a single callback, you can do: - -```php -Flight::route('*', function () { - // Do something -}); -``` - -## Passing - -You can pass execution on to the next matching route by returning `true` from -your callback function. - -```php -Flight::route('/user/@name', function (string $name) { - // Check some condition - if ($name !== "Bob") { - // Continue to next route - return true; - } -}); - -Flight::route('/user/*', function () { - // This will get called -}); -``` - -## Route Info - -If you want to inspect the matching route information, you can request for the route -object to be passed to your callback by passing in `true` as the third parameter in -the route method. The route object will always be the last parameter passed to your -callback function. - -```php -Flight::route('/', function(\flight\net\Route $route) { - // Array of HTTP methods matched against - $route->methods; - - // Array of named parameters - $route->params; - - // Matching regular expression - $route->regex; - - // Contains the contents of any '*' used in the URL pattern - $route->splat; -}, true); -``` - -## Route Grouping - -There may be times when you want to group related routes together (such as `/api/v1`). -You can do this by using the `group` method: - -```php -Flight::group('/api/v1', function () { - Flight::route('/users', function () { - // Matches /api/v1/users - }); - - Flight::route('/posts', function () { - // Matches /api/v1/posts - }); -}); -``` - -You can even nest groups of groups: - -```php -Flight::group('/api', function () { - Flight::group('/v1', function () { - // Flight::get() gets variables, it doesn't set a route! See object context below - Flight::route('GET /users', function () { - // Matches GET /api/v1/users - }); - - Flight::post('/posts', function () { - // Matches POST /api/v1/posts - }); - - Flight::put('/posts/1', function () { - // Matches PUT /api/v1/posts - }); - }); - Flight::group('/v2', function () { - - // Flight::get() gets variables, it doesn't set a route! See object context below - Flight::route('GET /users', function () { - // Matches GET /api/v2/users - }); - }); -}); -``` - -### Grouping with Object Context - -You can still use route grouping with the `Engine` object in the following way: - -```php -$app = new \flight\Engine(); -$app->group('/api/v1', function (Router $router) { - $router->get('/users', function () { - // Matches GET /api/v1/users - }); - - $router->post('/posts', function () { - // Matches POST /api/v1/posts - }); -}); -``` - -## Route Aliasing - -You can assign an alias to a route, so that the URL can dynamically be generated later in your code (like a template for instance). - -```php -Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); - -// later in code somewhere -Flight::getUrl('user_view', [ 'id' => 5 ]); // will return '/users/5' -``` - -This is especially helpful if your URL happens to change. In the above example, lets say that users was moved to `/admin/users/@id` instead. -With aliasing in place, you don't have to change anywhere you reference the alias because the alias will now return `/admin/users/5` like in the -example above. - -Route aliasing still works in groups as well: - -```php -Flight::group('/users', function() { - Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); -}); - - -// later in code somewhere -Flight::getUrl('user_view', [ 'id' => 5 ]); // will return '/users/5' -``` - -## Route Middleware -Flight supports route and group route middleware. Middleware is a function that is executed before (or after) the route callback. This is a great way to add API authentication checks in your code, or to validate that the user has permission to access the route. - -Here's a basic example: - -```php -// If you only supply an anonymous function, it will be executed before the route callback. -// there are no "after" middleware functions except for classes (see below) -Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() { - echo 'Middleware first!'; -}); - -Flight::start(); - -// This will output "Middleware first! Here I am!" -``` - -There are some very important notes about middleware that you should be aware of before you use them: -- Middleware functions are executed in the order they are added to the route. The execution is similar to how [Slim Framework handles this](https://www.slimframework.com/docs/v4/concepts/middleware.html#how-does-middleware-work). - - Befores are executed in the order added, and Afters are executed in reverse order. -- If your middleware function returns false, all execution is stopped and a 403 Forbidden error is thrown. You'll probably want to handle this more gracefully with a `Flight::redirect()` or something similar. -- If you need parameters from your route, they will be passed in a single array to your middleware function. (`function($params) { ... }` or `public function before($params) {}`). The reason for this is that you can structure your parameters into groups and in some of those groups, your parameters may actually show up in a different order which would break the middleware function by referring to the wrong parameter. This way, you can access them by name instead of position. - -### Middleware Classes - -Middleware can be registered as a class as well. If you need the "after" functionality, you must use a class. - -```php -class MyMiddleware { - public function before($params) { - echo 'Middleware first!'; - } - - public function after($params) { - echo 'Middleware last!'; - } -} - -$MyMiddleware = new MyMiddleware(); -Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware($MyMiddleware); // also ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]); - -Flight::start(); - -// This will display "Middleware first! Here I am! Middleware last!" -``` - -### Middleware Groups - -You can add a route group, and then every route in that group will have the same middleware as well. This is useful if you need to group a bunch of routes by say an Auth middleware to check the API key in the header. - -```php - -// added at the end of the group method -Flight::group('/api', function() { - Flight::route('/users', function() { echo 'users'; }, false, 'users'); - Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); -}, [ new ApiAuthMiddleware() ]); -``` - -# Extending - -Flight is designed to be an extensible framework. The framework comes with a set -of default methods and components, but it allows you to map your own methods, -register your own classes, or even override existing classes and methods. - -## Mapping Methods - -To map your own custom method, you use the `map` function: - -```php -// Map your method -Flight::map('hello', function (string $name) { - echo "hello $name!"; -}); - -// Call your custom method -Flight::hello('Bob'); -``` - -## Registering Classes - -To register your own class, you use the `register` function: - -```php -// Register your class -Flight::register('user', User::class); - -// Get an instance of your class -$user = Flight::user(); -``` - -The register method also allows you to pass along parameters to your class -constructor. So when you load your custom class, it will come pre-initialized. -You can define the constructor parameters by passing in an additional array. -Here's an example of loading a database connection: - -```php -// Register class with constructor parameters -Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']); - -// Get an instance of your class -// This will create an object with the defined parameters -// -// new PDO('mysql:host=localhost;dbname=test','user','pass'); -// -$db = Flight::db(); -``` - -If you pass in an additional callback parameter, it will be executed immediately -after class construction. This allows you to perform any set up procedures for your -new object. The callback function takes one parameter, an instance of the new object. - -```php -// The callback will be passed the object that was constructed -Flight::register( - 'db', - PDO::class, - ['mysql:host=localhost;dbname=test', 'user', 'pass'], - function (PDO $db) { - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - } -); -``` - -By default, every time you load your class you will get a shared instance. -To get a new instance of a class, simply pass in `false` as a parameter: - -```php -// Shared instance of the class -$shared = Flight::db(); - -// New instance of the class -$new = Flight::db(false); -``` - -Keep in mind that mapped methods have precedence over registered classes. If you -declare both using the same name, only the mapped method will be invoked. - -## PDO Helper Class - -Flight comes with a helper class for PDO. It allows you to easily query your database -with all the prepared/execute/fetchAll() wackiness. It greatly simplifies how you can -query your database. - -```php -// Register the PDO helper class -Flight::register('db', \flight\database\PdoWrapper::class, ['mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [ - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'', - PDO::ATTR_EMULATE_PREPARES => false, - PDO::ATTR_STRINGIFY_FETCHES => false, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC - ] -]); - -Flight::route('/users', function () { - // Get all users - $users = Flight::db()->fetchAll('SELECT * FROM users'); - - // Stream all users - $statement = Flight::db()->runQuery('SELECT * FROM users'); - while ($user = $statement->fetch()) { - echo $user['name']; - } - - // Get a single user - $user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]); - - // Get a single value - $count = Flight::db()->fetchField('SELECT COUNT(*) FROM users'); - - // Special IN() syntax to help out (make sure IN is in caps) - $users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]); - // you could also do this - $users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [ '1,2,3,4,5']); - - // Insert a new user - Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']); - $insert_id = Flight::db()->lastInsertId(); - - // Update a user - Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]); - - // Delete a user - Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]); - - // Get the number of affected rows - $statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']); - $affected_rows = $statement->rowCount(); - -}); -``` - -# Overriding - -Flight allows you to override its default functionality to suit your own needs, -without having to modify any code. - -For example, when Flight cannot match a URL to a route, it invokes the `notFound` -method which sends a generic `HTTP 404` response. You can override this behavior -by using the `map` method: - -```php -Flight::map('notFound', function() { - // Display custom 404 page - include 'errors/404.html'; -}); -``` - -Flight also allows you to replace core components of the framework. -For example you can replace the default Router class with your own custom class: - -```php -// Register your custom class -Flight::register('router', MyRouter::class); - -// When Flight loads the Router instance, it will load your class -$myrouter = Flight::router(); -``` - -Framework methods like `map` and `register` however cannot be overridden. You will -get an error if you try to do so. - -# Filtering - -Flight allows you to filter methods before and after they are called. There are no -predefined hooks you need to memorize. You can filter any of the default framework -methods as well as any custom methods that you've mapped. - -A filter function looks like this: - -```php -function (array &$params, string &$output): bool { - // Filter code -} -``` - -Using the passed in variables you can manipulate the input parameters and/or the output. - -You can have a filter run before a method by doing: - -```php -Flight::before('start', function (array &$params, string &$output): bool { - // Do something -}); -``` - -You can have a filter run after a method by doing: - -```php -Flight::after('start', function (array &$params, string &$output): bool { - // Do something -}); -``` - -You can add as many filters as you want to any method. They will be called in the -order that they are declared. - -Here's an example of the filtering process: - -```php -// Map a custom method -Flight::map('hello', function (string $name) { - return "Hello, $name!"; -}); - -// Add a before filter -Flight::before('hello', function (array &$params, string &$output): bool { - // Manipulate the parameter - $params[0] = 'Fred'; - return true; -}); - -// Add an after filter -Flight::after('hello', function (array &$params, string &$output): bool { - // Manipulate the output - $output .= " Have a nice day!"; - return true; -}); - -// Invoke the custom method -echo Flight::hello('Bob'); -``` - -This should display: - -``` -Hello Fred! Have a nice day! -``` - -If you have defined multiple filters, you can break the chain by returning `false` -in any of your filter functions: - -```php -Flight::before('start', function (array &$params, string &$output): bool { - echo 'one'; - return true; -}); - -Flight::before('start', function (array &$params, string &$output): bool { - echo 'two'; - - // This will end the chain - return false; -}); - -// This will not get called -Flight::before('start', function (array &$params, string &$output): bool { - echo 'three'; - return true; -}); -``` - -Note, core methods such as `map` and `register` cannot be filtered because they -are called directly and not invoked dynamically. - -# Variables - -Flight allows you to save variables so that they can be used anywhere in your application. - -```php -// Save your variable -Flight::set('id', 123); - -// Elsewhere in your application -$id = Flight::get('id'); -``` -To see if a variable has been set you can do: - -```php -if (Flight::has('id')) { - // Do something -} -``` - -You can clear a variable by doing: - -```php -// Clears the id variable -Flight::clear('id'); - -// Clears all variables -Flight::clear(); -``` - -Flight also uses variables for configuration purposes. - -```php -Flight::set('flight.log_errors', true); -``` - -# Views - -Flight provides some basic templating functionality by default. To display a view -template call the `render` method with the name of the template file and optional -template data: - -```php -Flight::render('hello.php', ['name' => 'Bob']); -``` - -The template data you pass in is automatically injected into the template and can -be reference like a local variable. Template files are simply PHP files. If the -content of the `hello.php` template file is: - -```php -Hello, = $name ?>! -``` - -The output would be: - -``` -Hello, Bob! -``` - -You can also manually set view variables by using the set method: - -```php -Flight::view()->set('name', 'Bob'); -``` - -The variable `name` is now available across all your views. So you can simply do: - -```php -Flight::render('hello'); -``` - -Note that when specifying the name of the template in the render method, you can -leave out the `.php` extension. - -By default Flight will look for a `views` directory for template files. You can -set an alternate path for your templates by setting the following config: - -```php -Flight::set('flight.views.path', '/path/to/views'); -``` - -## Layouts - -It is common for websites to have a single layout template file with interchanging -content. To render content to be used in a layout, you can pass in an optional -parameter to the `render` method. - -```php -Flight::render('header', ['heading' => 'Hello'], 'headerContent'); -Flight::render('body', ['body' => 'World'], 'bodyContent'); -``` - -Your view will then have saved variables called `headerContent` and `bodyContent`. -You can then render your layout by doing: - -```php -Flight::render('layout', ['title' => 'Home Page']); -``` - -If the template files looks like this: - -`header.php`: - -```php -