diff --git a/README.md b/README.md index 061b5a4c..8715e348 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,30 @@ $klein->respond('GET', '/hello-world', function () { $klein->dispatch(); ``` -*Example 1* - Respond to all requests +## Manual -```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` +#### Routes ++ [Examples](manual/Examples.md) ++ [Route Namespaces](manual/Route_Namespaces.md) ++ [Routing](manual/Routing.md) ++ [HTTP Errors](manual/HTTP_Errors.md) -## Contributing +#### Services ++ [Lazy Services](manual/Routing.md) ++ [Validators](manual/Validators.md) -See the [contributing guide](CONTRIBUTING.md) for more info +#### Views ++ [Views](manual/Views.md) ++ [Flash Messages](manual/Flash_Messages.md) -## More information +#### Advanced Installation and Configuration ++ [Sub-Directory Installation](manual/Sub-Directory_Installation.md) ++ [URL Rewrite Config](manual/URL_rewrite.md) -See the [wiki](https://github.com/chriso/klein.php/wiki) for more information +#### Development ++ [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/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 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/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/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/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..20b237a0 --- /dev/null +++ b/manual/Routing.md @@ -0,0 +1,72 @@ +# 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 + +## 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 + +```php +$klein->respond('*', function ($request, $response, $service) { $service->render('header.phtml'); }); +//other routes +$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 + +```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/', ... +``` 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 diff --git a/manual/URL_Rewrite.md b/manual/URL_Rewrite.md new file mode 100644 index 00000000..b496ff69 --- /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: + + dispatch(substr($_SERVER['REQUEST_URI'], strlen(APP_PATH))); + ?> + +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 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