From 609dcf9f9ca4319d8546a571d1d5dbf92f3e2637 Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 06:37:37 -0600 Subject: [PATCH 1/9] Added docs folder, broke up existing README into individual files, and put a link list into the README. Next task is to add new documentation. --- README.md | 394 ++---------------------------------------------------- 1 file changed, 11 insertions(+), 383 deletions(-) diff --git a/README.md b/README.md index 061b5a4c..6626eebd 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ 2. Require Klein with `php composer.phar require klein/klein v2.0.x` 3. Install dependencies with `php composer.phar install` -## Example +## Quick Start *Hello World* - Obligatory hello world example @@ -38,389 +38,17 @@ $klein->respond('GET', '/hello-world', function () { $klein->dispatch(); ``` -*Example 1* - Respond to all requests +## Documentation -```php -$klein->respond(function () { - return 'All the things'; -}); -``` - -*Example 2* - Named parameters - -```php -$klein->respond('/[:name]', function ($request) { - return 'Hello ' . $request->name; -}); -``` - -*Example 3* - [So RESTful](http://bit.ly/g93B1s) - -```php -$klein->respond('GET', '/posts', $callback); -$klein->respond('POST', '/posts', $callback); -$klein->respond('PUT', '/posts/[i:id]', $callback); -$klein->respond('DELETE', '/posts/[i:id]', $callback); -$klein->respond('OPTIONS', null, $callback); - -// To match multiple request methods: -$klein->respond(array('POST','GET'), $route, $callback); - -// Or you might want to handle the requests in the same place -$klein->respond('/posts/[create|edit:action]?/[i:id]?', function ($request, $response) { - switch ($request->action) { - // - } -}); -``` - -*Example 4* - Sending objects / files - -```php -$klein->respond(function ($request, $response, $service) { - $service->xml = function ($object) { - // Custom xml output function - } - $service->csv = function ($object) { - // Custom csv output function - } -}); - -$klein->respond('/report.[xml|csv|json:format]?', function ($request, $response, $service) { - // Get the format or fallback to JSON as the default - $send = $request->param('format', 'json'); - $service->$send($report); -}); - -$klein->respond('/report/latest', function ($request, $response, $service) { - $service->file('/tmp/cached_report.zip'); -}); -``` - -*Example 5* - All together - -```php -$klein->respond(function ($request, $response, $service, $app) use ($klein) { - // Handle exceptions => flash the message and redirect to the referrer - $klein->onError(function ($klein, $err_msg) { - $klein->service()->flash($err_msg); - $klein->service()->back(); - }); - - // The third parameter can be used to share scope and global objects - $app->db = new PDO(...); - - // $app also can store lazy services, e.g. if you don't want to - // instantiate a database connection on every response - $app->register('db', function() { - return new PDO(...); - }); -}); - -$klein->respond('POST', '/users/[i:id]/edit', function ($request, $response, $service, $app) { - // Quickly validate input parameters - $service->validateParam('username', 'Please enter a valid username')->isLen(5, 64)->isChars('a-zA-Z0-9-'); - $service->validateParam('password')->notNull(); - - $app->db->query(...); // etc. - - // Add view properties and helper methods - $service->title = 'foo'; - $service->escape = function ($str) { - return htmlentities($str); // Assign view helpers - }; - - $service->render('myview.phtml'); -}); - -// myview.phtml: -<?php echo $this->escape($this->title) ?> -``` - -## Route namespaces - -```php -$klein->with('/users', function () use ($klein) { - - $klein->respond('GET', '/?', function ($request, $response) { - // Show all users - }); - - $klein->respond('GET', '/[:id]', function ($request, $response) { - // Show a single user - }); - -}); - -foreach(array('projects', 'posts') as $controller) { - // Include all routes defined in a file under a given namespace - $klein->with("/$controller", "controllers/$controller.php"); -} -``` - -Included files are run in the scope of Klein (`$klein`) so all Klein -methods/properties can be accessed with `$this` - -_Example file for: "controllers/projects.php"_ -```php -// Routes to "/projects/?" -$this->respond('GET', '/?', function ($request, $response) { - // Show all projects -}); -``` - -## Lazy services - -Services can be stored **lazily**, meaning that they are only instantiated on -first use. - -``` php -respond(function ($request, $response, $service, $app) { - $app->register('lazyDb', function() { - $db = new stdClass(); - $db->name = 'foo'; - return $db; - }); -}); - -//Later - -$klein->respond('GET', '/posts', function ($request, $response, $service, $app) { - // $db is initialised on first request - // all subsequent calls will use the same instance - return $app->lazyDb->name; -}); -``` - -## Validators - -To add a custom validator use `addValidator($method, $callback)` - -```php -$service->addValidator('hex', function ($str) { - return preg_match('/^[0-9a-f]++$/i', $str); -}); -``` - -You can validate parameters using `is<$method>()` or `not<$method>()`, e.g. - -```php -$service->validateParam('key')->isHex(); -``` - -Or you can validate any string using the same flow.. - -```php -$service->validate($username)->isLen(4,16); -``` - -Validation methods are chainable, and a custom exception message can be specified for if/when validation fails - -```php -$service->validateParam('key', 'The key was invalid')->isHex()->isLen(32); -``` - -## Routing - -**[** *match_type* **:** *param_name* **]** - -Some examples - - * // Match all request URIs - [i] // Match an integer - [i:id] // Match an integer as 'id' - [a:action] // Match alphanumeric characters as 'action' - [h:key] // Match hexadecimal characters as 'key' - [:action] // Match anything up to the next / or end of the URI as 'action' - [create|edit:action] // Match either 'create' or 'edit' as 'action' - [*] // Catch all (lazy) - [*:trailing] // Catch all as 'trailing' (lazy) - [**:trailing] // Catch all (possessive - will match the rest of the URI) - .[:format]? // Match an optional parameter 'format' - a / or . before the block is also optional - -Some more complicated examples - - /posts/[*:title][i:id] // Matches "/posts/this-is-a-title-123" - /output.[xml|json:format]? // Matches "/output", "output.xml", "output.json" - /[:controller]?/[:action]? // Matches the typical /controller/action format - -**Note** - *all* routes that match the request URI are called - this -allows you to incorporate complex conditional logic such as user -authentication or view layouts. e.g. as a basic example, the following -code will wrap other routes with a header and footer - -```php -$klein->respond('*', function ($request, $response, $service) { $service->render('header.phtml'); }); -//other routes -$klein->respond('*', function ($request, $response, $service) { $service->render('footer.phtml'); }); -``` - -Routes automatically match the entire request URI. If you need to match -only a part of the request URI or use a custom regular expression, use the `@` operator. If you need to -negate a route, use the `!` operator - -```php -// Match all requests that end with '.json' or '.csv' -$klein->respond('@\.(json|csv)$', ... - -// Match all requests that _don't_ start with /admin -$klein->respond('!@^/admin/', ... -``` - -## Views - -You can send properties or helpers to the view by assigning them -to the `$service` object, or by using the second arg of `$service->render()` - -```php -$service->escape = function ($str) { - return htmlentities($str); -}; - -$service->render('myview.phtml', array('title' => 'My View')); - -// Or just: $service->title = 'My View'; -``` - -*myview.phtml* - -```html -<?php echo $this->escape($this->title) ?> -``` - -Views are compiled and run in the scope of `$service` so all service methods can be accessed with `$this` - -```php -$this->render('partial.html') // Render partials -$this->sharedData()->get('myvar') // Access stored service variables -echo $this->query(array('page' => 2)) // Modify the current query string -``` - -## API - -Below is a list of the public methods in the common classes you will most likely use. For a more formal source -of class/method documentation, please see the [PHPdoc generated documentation](http://chriso.github.io/klein.php/docs/). - -```php -$request-> - id($hash = true) // Get a unique ID for the request - paramsGet() // Return the GET parameter collection - paramsPost() // Return the POST parameter collection - paramsNamed() // Return the named parameter collection - cookies() // Return the cookies collection - server() // Return the server collection - headers() // Return the headers collection - files() // Return the files collection - body() // Get the request body - params() // Return all parameters - params($mask = null) // Return all parameters that match the mask array - extract() friendly - param($key, $default = null) // Get a request parameter (get, post, named) - isSecure() // Was the request sent via HTTPS? - ip() // Get the request IP - userAgent() // Get the request user agent - uri() // Get the request URI - pathname() // Get the request pathname - method() // Get the request method - method($method) // Check if the request method is $method, i.e. method('post') => true - query($key, $value = null) // Get, add to, or modify the current query string - // Get / Set (if assigned a value) a request parameter - -$response-> - protocolVersion($protocol_version = null) // Get the protocol version, or set it to the passed value - body($body = null) // Get the response body's content, or set it to the passed value - status() // Get the response's status object - headers() // Return the headers collection - cookies() // Return the cookies collection - code($code = null) // Return the HTTP response code, or set it to the passed value - prepend($content) // Prepend a string to the response body - append($content) // Append a string to the response body - isLocked() // Check if the response is locked - requireUnlocked() // Require that a response is unlocked - lock() // Lock the response from further modification - unlock() // Unlock the response - sendHeaders($override = false) // Send the HTTP response headers - sendCookies($override = false) // Send the HTTP response cookies - sendBody() // Send the response body's content - send() // Send the response and lock it - isSent() // Check if the response has been sent - chunk($str = null) // Enable response chunking (see the wiki) - header($key, $value = null) // Set a response header - cookie($key, $value = null, $expiry = null) // Set a cookie - cookie($key, null) // Remove a cookie - noCache() // Tell the browser not to cache the response - redirect($url, $code = 302) // Redirect to the specified URL - dump($obj) // Dump an object - file($path, $filename = null) // Send a file - json($object, $jsonp_prefix = null) // Send an object as JSON or JSONP by providing padding prefix - -$service-> - sharedData() // Return the shared data collection - startSession() // Start a session and return its ID - flash($msg, $type = 'info', $params = array() // Set a flash message - flashes($type = null) // Retrieve and clears all flashes of $type - markdown($str, $args, ...) // Return a string formatted with markdown - escape($str) // Escape a string - refresh() // Redirect to the current URL - back() // Redirect to the referer - query($key, $value = null) // Modify the current query string - query($arr) - layout($layout) // Set the view layout - yieldView() // Call inside the layout to render the view content - render($view, $data = array()) // Render a view or partial (in the scope of $response) - partial($view, $data = array()) // Render a partial without a layout (in the scope of $response) - addValidator($method, $callback) // Add a custom validator method - validate($string, $err = null) // Validate a string (with a custom error message) - validateParam($param, $err = null) // Validate a param - ($arg1, ...) // Call a user-defined helper - // Get a user-defined property - -$app-> - ($arg1, ...) //Call a user-defined helper - -$validator-> - notNull() // The string must not be null - isLen($length) // The string must be the exact length - isLen($min, $max) // The string must be between $min and $max length (inclusive) - isInt() // Check for a valid integer - isFloat() // Check for a valid float/decimal - isEmail() // Check for a valid email - isUrl() // Check for a valid URL - isIp() // Check for a valid IP - isAlpha() // Check for a-z (case insensitive) - isAlnum() // Check for alphanumeric characters - contains($needle) // Check if the string contains $needle - isChars($chars) // Validate against a character list - isRegex($pattern, $modifiers = '') // Validate against a regular expression - notRegex($pattern, $modifiers ='') - is() // Validate against a custom validator - not() // The validator can't match - () // Alias for is() -``` - -## Unit Testing - -Unit tests are a crucial part of developing a routing engine such as Klein. -Added features or bug-fixes can have adverse effects that are hard to find -without a lot of testing, hence the importance of unit testing. - -This project uses [PHPUnit](https://github.com/sebastianbergmann/phpunit/) as -its unit testing framework. - -The tests all live in `/tests` and each test extends an abstract class -`AbstractKleinTest` - -To test the project, simply run `php composer.phar install --dev` to download -a common version of PHPUnit with composer and run the tests from the main -directory with `./vendor/bin/phpunit` - -## Contributing - -See the [contributing guide](CONTRIBUTING.md) for more info - -## More information - -See the [wiki](https://github.com/chriso/klein.php/wiki) for more information ++ [Examples](docs/Examples.md) ++ [Route Namespaces](docs/Route_Namespaces.md) ++ [Lazy Services](docs/Routing.md) ++ [Validators](docs/Validators.md) ++ [Routing](docs/Routing.md) ++ [Views](docs/Views.md) ++ [API](docs/API.md) ++ [Unit Testing](docs/Unit_Testing.md) ++ [Contributing](CONTRIBUTING.md) ## Contributors From 5c41cce3426d2f1c1cae25f8a7c916d632555d33 Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 06:41:28 -0600 Subject: [PATCH 2/9] Oops... Looks like docs/ is where autogenerated files from phpDocumenter live, and is thus gitignore'd. I guess if the phpDocs stuff is the documentation, this stuff is the manual. --- README.md | 20 +++---- manual/API.md | 100 ++++++++++++++++++++++++++++++++ manual/Examples.md | 115 +++++++++++++++++++++++++++++++++++++ manual/Lazy_Services.md | 23 ++++++++ manual/Route_Namespaces.md | 31 ++++++++++ manual/Routing.md | 46 +++++++++++++++ manual/Unit_Testing.md | 15 +++++ manual/Validators.md | 27 +++++++++ manual/Views.md | 28 +++++++++ 9 files changed, 395 insertions(+), 10 deletions(-) create mode 100644 manual/API.md create mode 100644 manual/Examples.md create mode 100644 manual/Lazy_Services.md create mode 100644 manual/Route_Namespaces.md create mode 100644 manual/Routing.md create mode 100644 manual/Unit_Testing.md create mode 100644 manual/Validators.md create mode 100644 manual/Views.md diff --git a/README.md b/README.md index 6626eebd..f74dd8a6 100644 --- a/README.md +++ b/README.md @@ -38,16 +38,16 @@ $klein->respond('GET', '/hello-world', function () { $klein->dispatch(); ``` -## Documentation - -+ [Examples](docs/Examples.md) -+ [Route Namespaces](docs/Route_Namespaces.md) -+ [Lazy Services](docs/Routing.md) -+ [Validators](docs/Validators.md) -+ [Routing](docs/Routing.md) -+ [Views](docs/Views.md) -+ [API](docs/API.md) -+ [Unit Testing](docs/Unit_Testing.md) +## Manual + ++ [Examples](manual/Examples.md) ++ [Route Namespaces](manual/Route_Namespaces.md) ++ [Lazy Services](manual/Routing.md) ++ [Validators](manual/Validators.md) ++ [Routing](manual/Routing.md) ++ [Views](manual/Views.md) ++ [API](manual/API.md) ++ [Unit Testing](manual/Unit_Testing.md) + [Contributing](CONTRIBUTING.md) ## Contributors diff --git a/manual/API.md b/manual/API.md new file mode 100644 index 00000000..e80eea4d --- /dev/null +++ b/manual/API.md @@ -0,0 +1,100 @@ +# API + +Below is a list of the public methods in the common classes you will most likely use. For a more formal source +of class/method documentation, please see the [PHPdoc generated documentation](http://chriso.github.io/klein.php/docs/). + +```php +$request-> + id($hash = true) // Get a unique ID for the request + paramsGet() // Return the GET parameter collection + paramsPost() // Return the POST parameter collection + paramsNamed() // Return the named parameter collection + cookies() // Return the cookies collection + server() // Return the server collection + headers() // Return the headers collection + files() // Return the files collection + body() // Get the request body + params() // Return all parameters + params($mask = null) // Return all parameters that match the mask array - extract() friendly + param($key, $default = null) // Get a request parameter (get, post, named) + isSecure() // Was the request sent via HTTPS? + ip() // Get the request IP + userAgent() // Get the request user agent + uri() // Get the request URI + pathname() // Get the request pathname + method() // Get the request method + method($method) // Check if the request method is $method, i.e. method('post') => true + query($key, $value = null) // Get, add to, or modify the current query string + // Get / Set (if assigned a value) a request parameter + +$response-> + protocolVersion($protocol_version = null) // Get the protocol version, or set it to the passed value + body($body = null) // Get the response body's content, or set it to the passed value + status() // Get the response's status object + headers() // Return the headers collection + cookies() // Return the cookies collection + code($code = null) // Return the HTTP response code, or set it to the passed value + prepend($content) // Prepend a string to the response body + append($content) // Append a string to the response body + isLocked() // Check if the response is locked + requireUnlocked() // Require that a response is unlocked + lock() // Lock the response from further modification + unlock() // Unlock the response + sendHeaders($override = false) // Send the HTTP response headers + sendCookies($override = false) // Send the HTTP response cookies + sendBody() // Send the response body's content + send() // Send the response and lock it + isSent() // Check if the response has been sent + chunk($str = null) // Enable response chunking (see the wiki) + header($key, $value = null) // Set a response header + cookie($key, $value = null, $expiry = null) // Set a cookie + cookie($key, null) // Remove a cookie + noCache() // Tell the browser not to cache the response + redirect($url, $code = 302) // Redirect to the specified URL + dump($obj) // Dump an object + file($path, $filename = null) // Send a file + json($object, $jsonp_prefix = null) // Send an object as JSON or JSONP by providing padding prefix + +$service-> + sharedData() // Return the shared data collection + startSession() // Start a session and return its ID + flash($msg, $type = 'info', $params = array() // Set a flash message + flashes($type = null) // Retrieve and clears all flashes of $type + markdown($str, $args, ...) // Return a string formatted with markdown + escape($str) // Escape a string + refresh() // Redirect to the current URL + back() // Redirect to the referer + query($key, $value = null) // Modify the current query string + query($arr) + layout($layout) // Set the view layout + yieldView() // Call inside the layout to render the view content + render($view, $data = array()) // Render a view or partial (in the scope of $response) + partial($view, $data = array()) // Render a partial without a layout (in the scope of $response) + addValidator($method, $callback) // Add a custom validator method + validate($string, $err = null) // Validate a string (with a custom error message) + validateParam($param, $err = null) // Validate a param + ($arg1, ...) // Call a user-defined helper + // Get a user-defined property + +$app-> + ($arg1, ...) //Call a user-defined helper + +$validator-> + notNull() // The string must not be null + isLen($length) // The string must be the exact length + isLen($min, $max) // The string must be between $min and $max length (inclusive) + isInt() // Check for a valid integer + isFloat() // Check for a valid float/decimal + isEmail() // Check for a valid email + isUrl() // Check for a valid URL + isIp() // Check for a valid IP + isAlpha() // Check for a-z (case insensitive) + isAlnum() // Check for alphanumeric characters + contains($needle) // Check if the string contains $needle + isChars($chars) // Validate against a character list + isRegex($pattern, $modifiers = '') // Validate against a regular expression + notRegex($pattern, $modifiers ='') + is() // Validate against a custom validator + not() // The validator can't match + () // Alias for is() +``` \ No newline at end of file diff --git a/manual/Examples.md b/manual/Examples.md new file mode 100644 index 00000000..7ee92979 --- /dev/null +++ b/manual/Examples.md @@ -0,0 +1,115 @@ +# Examples + +*Hello World* - Obligatory hello world example + +```php +respond('GET', '/hello-world', function () { + return 'Hello World!'; +}); + +$klein->dispatch(); +``` + +*Example 1* - Respond to all requests + +```php +$klein->respond(function () { + return 'All the things'; +}); +``` + +*Example 2* - Named parameters + +```php +$klein->respond('/[:name]', function ($request) { + return 'Hello ' . $request->name; +}); +``` + +*Example 3* - [So RESTful](http://bit.ly/g93B1s) + +```php +$klein->respond('GET', '/posts', $callback); +$klein->respond('POST', '/posts', $callback); +$klein->respond('PUT', '/posts/[i:id]', $callback); +$klein->respond('DELETE', '/posts/[i:id]', $callback); +$klein->respond('OPTIONS', null, $callback); + +// To match multiple request methods: +$klein->respond(array('POST','GET'), $route, $callback); + +// Or you might want to handle the requests in the same place +$klein->respond('/posts/[create|edit:action]?/[i:id]?', function ($request, $response) { + switch ($request->action) { + // + } +}); +``` + +*Example 4* - Sending objects / files + +```php +$klein->respond(function ($request, $response, $service) { + $service->xml = function ($object) { + // Custom xml output function + } + $service->csv = function ($object) { + // Custom csv output function + } +}); + +$klein->respond('/report.[xml|csv|json:format]?', function ($request, $response, $service) { + // Get the format or fallback to JSON as the default + $send = $request->param('format', 'json'); + $service->$send($report); +}); + +$klein->respond('/report/latest', function ($request, $response, $service) { + $service->file('/tmp/cached_report.zip'); +}); +``` + +*Example 5* - All together + +```php +$klein->respond(function ($request, $response, $service, $app) use ($klein) { + // Handle exceptions => flash the message and redirect to the referrer + $klein->onError(function ($klein, $err_msg) { + $klein->service()->flash($err_msg); + $klein->service()->back(); + }); + + // The third parameter can be used to share scope and global objects + $app->db = new PDO(...); + + // $app also can store lazy services, e.g. if you don't want to + // instantiate a database connection on every response + $app->register('db', function() { + return new PDO(...); + }); +}); + +$klein->respond('POST', '/users/[i:id]/edit', function ($request, $response, $service, $app) { + // Quickly validate input parameters + $service->validateParam('username', 'Please enter a valid username')->isLen(5, 64)->isChars('a-zA-Z0-9-'); + $service->validateParam('password')->notNull(); + + $app->db->query(...); // etc. + + // Add view properties and helper methods + $service->title = 'foo'; + $service->escape = function ($str) { + return htmlentities($str); // Assign view helpers + }; + + $service->render('myview.phtml'); +}); + +// myview.phtml: +<?php echo $this->escape($this->title) ?> +``` \ No newline at end of file diff --git a/manual/Lazy_Services.md b/manual/Lazy_Services.md new file mode 100644 index 00000000..83b5b904 --- /dev/null +++ b/manual/Lazy_Services.md @@ -0,0 +1,23 @@ +# Lazy services + +Services can be stored **lazily**, meaning that they are only instantiated on +first use. + +``` php +respond(function ($request, $response, $service, $app) { + $app->register('lazyDb', function() { + $db = new stdClass(); + $db->name = 'foo'; + return $db; + }); +}); + +//Later + +$klein->respond('GET', '/posts', function ($request, $response, $service, $app) { + // $db is initialised on first request + // all subsequent calls will use the same instance + return $app->lazyDb->name; +}); +``` \ No newline at end of file diff --git a/manual/Route_Namespaces.md b/manual/Route_Namespaces.md new file mode 100644 index 00000000..7f132afd --- /dev/null +++ b/manual/Route_Namespaces.md @@ -0,0 +1,31 @@ +# Route Namespaces + +```php +$klein->with('/users', function () use ($klein) { + + $klein->respond('GET', '/?', function ($request, $response) { + // Show all users + }); + + $klein->respond('GET', '/[:id]', function ($request, $response) { + // Show a single user + }); + +}); + +foreach(array('projects', 'posts') as $controller) { + // Include all routes defined in a file under a given namespace + $klein->with("/$controller", "controllers/$controller.php"); +} +``` + +Included files are run in the scope of Klein (`$klein`) so all Klein +methods/properties can be accessed with `$this` + +_Example file for: "controllers/projects.php"_ +```php +// Routes to "/projects/?" +$this->respond('GET', '/?', function ($request, $response) { + // Show all projects +}); +``` \ No newline at end of file diff --git a/manual/Routing.md b/manual/Routing.md new file mode 100644 index 00000000..0129cdca --- /dev/null +++ b/manual/Routing.md @@ -0,0 +1,46 @@ +# Routing + +**[** *match_type* **:** *param_name* **]** + +Some examples + + * // Match all request URIs + [i] // Match an integer + [i:id] // Match an integer as 'id' + [a:action] // Match alphanumeric characters as 'action' + [h:key] // Match hexadecimal characters as 'key' + [:action] // Match anything up to the next / or end of the URI as 'action' + [create|edit:action] // Match either 'create' or 'edit' as 'action' + [*] // Catch all (lazy) + [*:trailing] // Catch all as 'trailing' (lazy) + [**:trailing] // Catch all (possessive - will match the rest of the URI) + .[:format]? // Match an optional parameter 'format' - a / or . before the block is also optional + +Some more complicated examples + + /posts/[*:title][i:id] // Matches "/posts/this-is-a-title-123" + /output.[xml|json:format]? // Matches "/output", "output.xml", "output.json" + /[:controller]?/[:action]? // Matches the typical /controller/action format + +**Note** - *all* routes that match the request URI are called - this +allows you to incorporate complex conditional logic such as user +authentication or view layouts. e.g. as a basic example, the following +code will wrap other routes with a header and footer + +```php +$klein->respond('*', function ($request, $response, $service) { $service->render('header.phtml'); }); +//other routes +$klein->respond('*', function ($request, $response, $service) { $service->render('footer.phtml'); }); +``` + +Routes automatically match the entire request URI. If you need to match +only a part of the request URI or use a custom regular expression, use the `@` operator. If you need to +negate a route, use the `!` operator + +```php +// Match all requests that end with '.json' or '.csv' +$klein->respond('@\.(json|csv)$', ... + +// Match all requests that _don't_ start with /admin +$klein->respond('!@^/admin/', ... +``` \ No newline at end of file diff --git a/manual/Unit_Testing.md b/manual/Unit_Testing.md new file mode 100644 index 00000000..1593c6f2 --- /dev/null +++ b/manual/Unit_Testing.md @@ -0,0 +1,15 @@ +# Unit Testing + +Unit tests are a crucial part of developing a routing engine such as Klein. +Added features or bug-fixes can have adverse effects that are hard to find +without a lot of testing, hence the importance of unit testing. + +This project uses [PHPUnit](https://github.com/sebastianbergmann/phpunit/) as +its unit testing framework. + +The tests all live in `/tests` and each test extends an abstract class +`AbstractKleinTest` + +To test the project, simply run `php composer.phar install --dev` to download +a common version of PHPUnit with composer and run the tests from the main +directory with `./vendor/bin/phpunit` \ No newline at end of file diff --git a/manual/Validators.md b/manual/Validators.md new file mode 100644 index 00000000..945aed10 --- /dev/null +++ b/manual/Validators.md @@ -0,0 +1,27 @@ +# Validators + +To add a custom validator use `addValidator($method, $callback)` + +```php +$service->addValidator('hex', function ($str) { + return preg_match('/^[0-9a-f]++$/i', $str); +}); +``` + +You can validate parameters using `is<$method>()` or `not<$method>()`, e.g. + +```php +$service->validateParam('key')->isHex(); +``` + +Or you can validate any string using the same flow.. + +```php +$service->validate($username)->isLen(4,16); +``` + +Validation methods are chainable, and a custom exception message can be specified for if/when validation fails + +```php +$service->validateParam('key', 'The key was invalid')->isHex()->isLen(32); +``` \ No newline at end of file diff --git a/manual/Views.md b/manual/Views.md new file mode 100644 index 00000000..806f05ef --- /dev/null +++ b/manual/Views.md @@ -0,0 +1,28 @@ +# Views + +You can send properties or helpers to the view by assigning them +to the `$service` object, or by using the second arg of `$service->render()` + +```php +$service->escape = function ($str) { + return htmlentities($str); +}; + +$service->render('myview.phtml', array('title' => 'My View')); + +// Or just: $service->title = 'My View'; +``` + +*myview.phtml* + +```html +<?php echo $this->escape($this->title) ?> +``` + +Views are compiled and run in the scope of `$service` so all service methods can be accessed with `$this` + +```php +$this->render('partial.html') // Render partials +$this->sharedData()->get('myvar') // Access stored service variables +echo $this->query(array('page' => 2)) // Modify the current query string +``` \ No newline at end of file From 180841a9f7ce8d00ed408de64be416f8c6a79c32 Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 06:47:38 -0600 Subject: [PATCH 3/9] Added some files from the Wiki. --- README.md | 2 + manual/HTTP_Errors.md | 69 ++++++++++++++++++++++++ manual/Response_Chunking.md | 23 ++++++++ manual/Sub-Directory_Installation.md | 81 ++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 manual/HTTP_Errors.md create mode 100644 manual/Response_Chunking.md create mode 100644 manual/Sub-Directory_Installation.md diff --git a/README.md b/README.md index f74dd8a6..539c1f8a 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ $klein->dispatch(); + [Validators](manual/Validators.md) + [Routing](manual/Routing.md) + [Views](manual/Views.md) ++ [HTTP Errors](manual/HTTP_Errors.md) ++ [Sub-Directory Installation](manual/Sub-Directory_Installation.md) + [API](manual/API.md) + [Unit Testing](manual/Unit_Testing.md) + [Contributing](CONTRIBUTING.md) diff --git a/manual/HTTP_Errors.md b/manual/HTTP_Errors.md new file mode 100644 index 00000000..d334313c --- /dev/null +++ b/manual/HTTP_Errors.md @@ -0,0 +1,69 @@ +# HTTP Errors + +To handle 404 errors, or any HTTP error, you can add a special handler alongside your routes. You simply pass the handler method a callback, much like you would a route. The callback receives the following parameters: + +* int `$code` The HTTP error code +* Klein `$router` The router instance +* RouteCollection `$matched` The collection of routes that were matched in dispatch +* array `$methods_matched` The HTTP methods that were matched in dispatch +* HttpExceptionInterface `$http_exception` The exception that occurred + +Following are a couple examples: + +```php +onHttpError(function ($code, $router) { + switch ($code) { + case 404: + $router->response()->body( + 'Y U so lost?!' + ); + break; + case 405: + $router->response()->body( + 'You can\'t do that!' + ); + break; + default: + $router->response()->body( + 'Oh no, a bad error happened that caused a '. $code + ); + } +}); + +// Using range behaviors via if/else +$klein->onHttpError(function ($code, $router) { + if ($code >= 400 && $code < 500) { + $router->response()->body( + 'Oh no, a bad error happened that caused a '. $code + ); + } elseif ($code >= 500 && $code <= 599) { + error_log('uhhh, something bad happened'); + } +}); +``` + +The instructions above represent the current recommended technique for handling HTTP errors. Below is the older method, which should still work, but **may be deprecated in future.** + +Add a route for `404` as your *last* route. If no other routes are matched, the specified callback will be called. + +```php +respond('404', function ($request) { + $page = $request->uri(); + echo "Oops, it looks like $page doesn't exist..\n"; +}); +``` + +**But I need some other route(s) for setting up layouts, etc.** + +If you don't want a certain `respond()` call to be counted as a match, just call it without a route: + +```php +respond(function ($request, $response, $app) { + $response->layout('layout.phtml'); + //etc. +}); +``` \ No newline at end of file diff --git a/manual/Response_Chunking.md b/manual/Response_Chunking.md new file mode 100644 index 00000000..78d67eb2 --- /dev/null +++ b/manual/Response_Chunking.md @@ -0,0 +1,23 @@ +# Response Chunking + +Read [this article](http://weblog.rubyonrails.org/2011/4/18/why-http-streaming) to understand how response chunking works and how it might benefit your app. + +To send a string as a chunk + +```php +$response->chunk($str); +``` + +To flush the contents of the output buffer as a response chunk + +```php +$response->chunk(); +``` + +After calling `chunk()`, views will be chunked too + +```php +$response->render('mytemplate.phtml'); +``` + +*Note: calling `$response->chunk()` for the first time sets the appropriate header (`Transfer-Encoding: chunked`).* \ No newline at end of file diff --git a/manual/Sub-Directory_Installation.md b/manual/Sub-Directory_Installation.md new file mode 100644 index 00000000..efcf50de --- /dev/null +++ b/manual/Sub-Directory_Installation.md @@ -0,0 +1,81 @@ +# Sub-Directory Installation + +Klein.php is possible to be deployed in many types of environments that at least meet the PHP 5.3 version minimum requirement. Deploying Klein through a "sub-directory" and allowing Klein to still route in a relative path sense requires some configuration. Here's how. + +All examples are based off of the following example paths: + +* **Web Root**: "/srv/www/" +* **Klein installed**: "/srv/www/my-site/app/" + +## In Server Configuration + +### Apache (.htaccess) + +For Apache, you'll need the rewrite module to change the `RewriteBase`. So if you're web application is served from a path relative to your webroot, you'll have change your `RewriteBase` to match the relative path from the web root. For example: + +.htaccess file (or similar rules could be put in the Apache virtual-host config) +``` + + Options -MultiViews + + RewriteEngine On + RewriteBase /my-site/app/ + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + +``` + +### Nginx + +For nginx, you'll have to modify your configuration to simply point to the sub-directory path, like so: + +```nginx +server { + location / { + try_files $uri $uri/ /my-site/app/index.php?$args; + } +} +``` + +## In Code + +Klein.php has no configuration, so to deploy this url rewrite in a subdirectory you must invoke dispatch with a **custom "Request" object** as shown below: + +```php +server()->get('REQUEST_URI'); + +// Set the request URI to a modified one (without the "subdirectory") in it +$request->server()->set('REQUEST_URI', substr($uri, strlen(APP_PATH))); + +// Pass our request to our dispatch method +$klein->dispatch($request); +``` +This might also work for you (doesn't need a custom request object): + +```php +dispatch(); +``` + +References: +- [https://github.com/chriso/klein.php/issues/5](https://github.com/chriso/klein.php/issues/5) +- [https://github.com/chriso/klein.php/issues/95](https://github.com/chriso/klein.php/issues/95) \ No newline at end of file From 674d9c854323558a8a3010b9803cda5c875020e6 Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 07:15:47 -0600 Subject: [PATCH 4/9] Update Routing.md about skipping route matches Included sample code and suggested workarounds. However, I'm not sure which workaround is more appropriate, so the text is a little ambivalent. --- manual/Routing.md | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/manual/Routing.md b/manual/Routing.md index 0129cdca..20b237a0 100644 --- a/manual/Routing.md +++ b/manual/Routing.md @@ -22,7 +22,9 @@ Some more complicated examples /output.[xml|json:format]? // Matches "/output", "output.xml", "output.json" /[:controller]?/[:action]? // Matches the typical /controller/action format -**Note** - *all* routes that match the request URI are called - this +## Matching Multiple Routes + +*All* routes that match the request URI are called - this allows you to incorporate complex conditional logic such as user authentication or view layouts. e.g. as a basic example, the following code will wrap other routes with a header and footer @@ -33,6 +35,30 @@ $klein->respond('*', function ($request, $response, $service) { $service->render $klein->respond('*', function ($request, $response, $service) { $service->render('footer.phtml'); }); ``` +In some cases, you may need to override this matching behavior. For instance, if you have several specific routes, followed by a much broader route. + +```php +// Backend (Admin panel) + +$klein->respond('/admin/?', function ($request, $response, $service) { + if(isAdmin()) { + $service->render('admin.phtml'); + } else { + permissionDenied(); + } +}); + +// Main Views + +$klein->respond('/[:page]/?', function ($request, $response, $service) { + $service->render($request->page.'.phtml'); +}); +``` + +The code above will match both routes, for `/admin` and render both views, making a big mess. You can either call `$klein->skipRemaining()` to skip the other routes, or you could call `$response->send()`. It looks like either would accomplish the goal, but I'm not sure what the difference in effect would be, or the scenarios where one is more appropriate than the other. + +## Matching Partial URIs + Routes automatically match the entire request URI. If you need to match only a part of the request URI or use a custom regular expression, use the `@` operator. If you need to negate a route, use the `!` operator @@ -43,4 +69,4 @@ $klein->respond('@\.(json|csv)$', ... // Match all requests that _don't_ start with /admin $klein->respond('!@^/admin/', ... -``` \ No newline at end of file +``` From 6259d8e64926d99826700b733f358b7f029642a1 Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 07:37:56 -0600 Subject: [PATCH 5/9] Added a man page about URL rewrite config in Apache and nginx. Not my work, but a Gist I found. Attribution listed at the bottom. --- README.md | 1 + manual/URL_Rewrite.md | 47 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 manual/URL_Rewrite.md diff --git a/README.md b/README.md index 539c1f8a..7ad1f590 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ $klein->dispatch(); + [Views](manual/Views.md) + [HTTP Errors](manual/HTTP_Errors.md) + [Sub-Directory Installation](manual/Sub-Directory_Installation.md) ++ [URL Rewrite Config](manual/URL_rewrite.md) + [API](manual/API.md) + [Unit Testing](manual/Unit_Testing.md) + [Contributing](CONTRIBUTING.md) diff --git a/manual/URL_Rewrite.md b/manual/URL_Rewrite.md new file mode 100644 index 00000000..21a5e3be --- /dev/null +++ b/manual/URL_Rewrite.md @@ -0,0 +1,47 @@ +# URL Rewrite Config + +Why rewrite URLs? Check [Wikipedia](http://en.wikipedia.org/wiki/Rewrite_engine) + +## Apache + +Make sure [AllowOverride](http://httpd.apache.org/docs/2.0/mod/core.html#allowoverride) is on for your directory, or put in `httpd.conf` + + # Apache (.htaccess or httpd.conf) + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule . /index.php [L] + +## nginx + + # basics + try_files $uri $uri/ /index.php?$args; + +If you're trying to route requests for an app that is *not* in the document root, invoke klein's dispatch line like this: + + + +Then in your `nginx.conf` file, use: + + location /your/webapp/ { + try_files $uri $uri/ /your/webapp/index.php?$args; + } + +**Don't** do this. + + # nginx + if (!-e $request_filename) { + rewrite . /index.php last; + } + +See [nginx pitfalls](http://wiki.nginx.org/Pitfalls). + +## More Reading + * http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html + * http://wiki.nginx.org/HttpRewriteModule + * http://wiki.nginx.org/Pitfalls + * [klein.php](https://github.com/chriso/klein.php) - simple, fast router for PHP + +Source: https://gist.github.com/jamesvl/910325 From ebbaeb3dbf6238707613608bbfcc105c8a312931 Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 07:40:01 -0600 Subject: [PATCH 6/9] Updated URL rewrite man page to reflected OOP-style Klein syntax. --- manual/URL_Rewrite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/URL_Rewrite.md b/manual/URL_Rewrite.md index 21a5e3be..b496ff69 100644 --- a/manual/URL_Rewrite.md +++ b/manual/URL_Rewrite.md @@ -20,7 +20,7 @@ If you're trying to route requests for an app that is *not* in the document root dispatch(substr($_SERVER['REQUEST_URI'], strlen(APP_PATH))); ?> Then in your `nginx.conf` file, use: From 729cceb279a243491210292c1dfbd13b629fdc84 Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 08:28:53 -0600 Subject: [PATCH 7/9] Added a page about flash messages. --- README.md | 1 + manual/Flash_Messages.md | 49 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 manual/Flash_Messages.md diff --git a/README.md b/README.md index 7ad1f590..91a0962b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ $klein->dispatch(); + [Validators](manual/Validators.md) + [Routing](manual/Routing.md) + [Views](manual/Views.md) ++ [Flash Messages](manual/Views.md) + [HTTP Errors](manual/HTTP_Errors.md) + [Sub-Directory Installation](manual/Sub-Directory_Installation.md) + [URL Rewrite Config](manual/URL_rewrite.md) diff --git a/manual/Flash_Messages.md b/manual/Flash_Messages.md new file mode 100644 index 00000000..efb4a3f9 --- /dev/null +++ b/manual/Flash_Messages.md @@ -0,0 +1,49 @@ +# Flash Messages + +Klein provides a Flash Message system, which allows you to store messages for the user in the session array, to be presented to the user at a later time, in a future request. + +They usually look something like this: http://getbootstrap.com/components/#alerts + +## Create + +To store such a message, from inside your route callback, you would call something like: + +```php +flash('Do *NOT* go in there!','warning'); +?> +``` + +The first parameter is a string containing the message you want sent to the user. You can use basic markdown syntax (basically just links, bold, and italics), which will be converted to HTML during rendering. + +The second parameter is a message type. This is an arbitrary string. You can make up whatever types make sense for your app. This parameter is optional. If you leave it blank, the default value 'info' will be used. + +## Retrieve + +The flash messages are stored in $_SESSION['__flashes']. However, you should not access them directly. To retrive them, you use `Klein\ServiceProvider::flashes()`. This method retrieves and clears all the flash messages, or all the flash messages of a given type. + +If not type parameter is passed to the method, it returns an array of flashes, grouped by type, so you can foreach and echo them. If you're using the Klein templating system, then you can call the ServiceProvider from the template as `$this`. + +So in your template, you would have something like: + +```php +flashes() as $type=>$messages): ?> + +
+ + +``` + +Note that we first loop through the types, and then for each type, we loop through the messages. The code above will format the flash messages appropriately to work with [Bootstrap](http://getbootstrap.com), assuming your types correspond to theirs (success, info, warning, or danger). + +## Caution + +The two methods involved in handling flash messages are very similar, but not interchangeable. The singular method `Klein\ServiceProvider::flash()` creates a flash message, while the plural method `Klein\ServiceProvider::flashes()` retrieves them. + +## More Info + ++ http://chriso.github.io/klein.php/docs/classes/Klein.ServiceProvider.html#method_flashes ++ http://chriso.github.io/klein.php/docs/classes/Klein.ServiceProvider.html#method_flash ++ http://chriso.github.io/klein.php/docs/classes/Klein.ServiceProvider.html#merkdown + +Source: http://stackoverflow.com/a/21195011/1004008 \ No newline at end of file From 58f64c19086cfd2dd00323fcbf75d5978b52d606 Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 08:34:42 -0600 Subject: [PATCH 8/9] Organizing the manual page links on the README. --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 91a0962b..a47cc429 100644 --- a/README.md +++ b/README.md @@ -40,16 +40,25 @@ $klein->dispatch(); ## Manual +### Routes + [Examples](manual/Examples.md) + [Route Namespaces](manual/Route_Namespaces.md) ++ [Routing](manual/Routing.md) ++ [HTTP Errors](manual/HTTP_Errors.md) + +### Services + [Lazy Services](manual/Routing.md) + [Validators](manual/Validators.md) -+ [Routing](manual/Routing.md) + +### Views + [Views](manual/Views.md) -+ [Flash Messages](manual/Views.md) -+ [HTTP Errors](manual/HTTP_Errors.md) ++ [Flash Messages](manual/Flash_Messages.md) + +### Advanced Installation and Configuration + [Sub-Directory Installation](manual/Sub-Directory_Installation.md) + [URL Rewrite Config](manual/URL_rewrite.md) + +### Development + [API](manual/API.md) + [Unit Testing](manual/Unit_Testing.md) + [Contributing](CONTRIBUTING.md) From f8d5a162b74d13cf66489260e2c2ed34cfd943ea Mon Sep 17 00:00:00 2001 From: Jamie Adams Date: Thu, 23 Jan 2014 09:22:24 -0600 Subject: [PATCH 9/9] Changed heading size for clarity --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a47cc429..8715e348 100644 --- a/README.md +++ b/README.md @@ -40,25 +40,25 @@ $klein->dispatch(); ## Manual -### Routes +#### Routes + [Examples](manual/Examples.md) + [Route Namespaces](manual/Route_Namespaces.md) + [Routing](manual/Routing.md) + [HTTP Errors](manual/HTTP_Errors.md) -### Services +#### Services + [Lazy Services](manual/Routing.md) + [Validators](manual/Validators.md) -### Views +#### Views + [Views](manual/Views.md) + [Flash Messages](manual/Flash_Messages.md) -### Advanced Installation and Configuration +#### Advanced Installation and Configuration + [Sub-Directory Installation](manual/Sub-Directory_Installation.md) + [URL Rewrite Config](manual/URL_rewrite.md) -### Development +#### Development + [API](manual/API.md) + [Unit Testing](manual/Unit_Testing.md) + [Contributing](CONTRIBUTING.md)