Added Kohana v3.0.9

This commit is contained in:
Deon George
2011-01-14 01:49:56 +11:00
parent fe11dd5f51
commit b6e9961846
520 changed files with 54728 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# Loading Classes
Kohana takes advantage of PHP [autoloading](http://php.net/manual/language.oop5.autoload.php). This removes the need to call [include](http://php.net/include) or [require](http://php.net/require) before using a class. When you use a class Kohana will find and include the class file for you. For instance, when you want to use the [Cookie::set] method, you simply call:
Cookie::set('mycookie', 'any string value');
Or to load an [Encrypt] instance, just call [Encrypt::instance]:
$encrypt = Encrypt::instance();
Classes are loaded via the [Kohana::auto_load] method, which makes a simple conversion from class name to file name:
1. Classes are placed in the `classes/` directory of the [filesystem](files)
2. Any underscore characters in the class name are converted to slashes
2. The filename is lowercase
When calling a class that has not been loaded (eg: `Session_Cookie`), Kohana will search the filesystem using [Kohana::find_file] for a file named `classes/session/cookie.php`.
If your classes do not follow this convention, they cannot be autoloaded by Kohana. You will have to manually included your files, or add your own [autoload function.](http://us3.php.net/manual/en/function.spl-autoload-register.php)
## Custom Autoloaders
Kohana's default autoloader is enabled in `application/bootstrap.php` using [spl_autoload_register](http://php.net/spl_autoload_register):
spl_autoload_register(array('Kohana', 'auto_load'));
This allows [Kohana::auto_load] to attempt to find and include any class that does not yet exist when the class is first used.
### Example: Zend
You can easily gain access to other libraries if they include an autoloader. For example, here is how to enable Zend's autoloader so you can use Zend libraries in your Kohana application.
#### Download and install the Zend Framework files
- [Download the latest Zend Framework files](http://framework.zend.com/download/latest).
- Create a `vendor` directory at `application/vendor`. This keeps third party software separate from your application classes.
- Move the decompressed Zend folder containing Zend Framework to `application/vendor/Zend`.
#### Include Zend's Autoloader in your bootstrap
Somewhere in `application/bootstrap.php`, copy the following code:
/**
* Enable Zend Framework autoloading
*/
if ($path = Kohana::find_file('vendor', 'Zend/Loader'))
{
ini_set('include_path',
ini_get('include_path').PATH_SEPARATOR.dirname(dirname($path)));
require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();
}
#### Usage example
You can now autoload any Zend Framework classes from inside your Kohana application.
if ($validate($_POST))
{
$mailer = new Zend_Mail;
$mailer->setBodyHtml($view)
->setFrom(Kohana::config('site')->email_from)
->addTo($email)
->setSubject($message)
->send();
}

View File

@@ -0,0 +1,164 @@
# Bootstrap
The bootstrap is located at `application/bootstrap.php`. It is responsible for setting up the Kohana environment and executing the main response. It is included by `index.php` (see [Request flow](flow))
[!!] The bootstrap is responsible for the flow of your application. In previous versions of Kohana the bootstrap was in `system` and was somewhat of an unseen, uneditible force. In Kohana 3 the bootstrap takes on a much more integral and versatile role. Do not be afraid to edit and change your bootstrap however you see fit.
## Environment setup
First the bootstrap sets the timezone and the locale, and adds Kohana's autoloader so the [cascading filesystem](files) works. You could add any other settings that all your application needed here.
~~~
// Sample excerpt from bootstrap.php with comments trimmed down
// Set the default time zone.
date_default_timezone_set('America/Chicago');
// Set the default locale.
setlocale(LC_ALL, 'en_US.utf-8');
// Enable the Kohana auto-loader.
spl_autoload_register(array('Kohana', 'auto_load'));
// Enable the Kohana auto-loader for unserialization.
ini_set('unserialize_callback_func', 'spl_autoload_call');
~~~
## Initilization and Configuration
Kohana is then initialized by calling [Kohana::init], and the log and [config](files/config) reader/writers are enabled.
~~~
// Sample excerpt from bootstrap.php with comments trimmed down
Kohana::init(array('
base_url' => '/kohana/',
index_file => false,
));
// Attach the file writer to logging. Multiple writers are supported.
Kohana::$log->attach(new Kohana_Log_File(APPPATH.'logs'));
// Attach a file reader to config. Multiple readers are supported.
Kohana::$config->attach(new Kohana_Config_File);
~~~
You can add conditional statements to make the bootstrap have different values based on certain settings. For example, detect whether we are live by checking `$_SERVER['HTTP_HOST']` and set caching, profiling, etc. accordingly. This is just an example, there are many different ways to accomplish the same thing.
~~~
// Excerpt from http://github.com/isaiahdw/kohanaphp.com/blob/f2afe8e28b/application/bootstrap.php
... [trimmed]
/**
* Set the environment status by the domain.
*/
if (strpos($_SERVER['HTTP_HOST'], 'kohanaphp.com') !== FALSE)
{
// We are live!
Kohana::$environment = Kohana::PRODUCTION;
// Turn off notices and strict errors
error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT);
}
/**
* Initialize Kohana, setting the default options.
... [trimmed]
*/
Kohana::init(array(
'base_url' => Kohana::$environment === Kohana::PRODUCTION ? '/' : '/kohanaphp.com/',
'caching' => Kohana::$environment === Kohana::PRODUCTION,
'profile' => Kohana::$environment !== Kohana::PRODUCTION,
'index_file' => FALSE,
));
... [trimmed]
try
{
$request = Request::instance()->execute();
}
catch (Exception $e)
{
// If we are in development and the error wasn't a 404, show the stack trace.
if ( Kohana::$environment == "development" AND $e->getCode() != 404 )
{
throw $e;
}
...[trimmed]
~~~
[!!] Note: The default bootstrap will set `Kohana::$environment = $_ENV['KOHANA_ENV']` if set. Docs on how to supply this variable are available in your web server's documentation (e.g. [Apache](http://httpd.apache.org/docs/1.3/mod/mod_env.html#setenv), [Lighttpd](http://redmine.lighttpd.net/wiki/1/Docs:ModSetEnv#Options)). This is considered better practice than many alternative methods to set `Kohana::$enviroment`, as you can change the setting per server, without having to rely on config options or hostnames.
## Modules
**Read the [Modules](modules) page for a more detailed description.**
[Modules](modules) are then loaded using [Kohana::modules()]. Including modules is optional.
Each key in the array should be the name of the module, and the value is the path to the module, either relative or absolute.
~~~
// Example excerpt from bootstrap.php
Kohana::modules(array(
'database' => MODPATH.'database',
'orm' => MODPATH.'orm',
'userguide' => MODPATH.'userguide',
));
~~~
## Routes
**Read the [Routing](routing) page for a more detailed description and more examples.**
[Routes](routing) are then defined via [Route::set()].
~~~
// The default route that comes with Kohana 3
Route::set('default', '(<controller>(/<action>(/<id>)))')
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));
~~~
## Execution
Once our environment is initialized and routes defined, it's time to execute our application. This area of the bootstrap is very flexible. Do not be afraid to change this around to whatever suits your needs.
### Basic Example
The most simple way to do this, and what comes default with Kohana 3 is simply:
~~~
// Execute the main request
echo Request::instance()
->execute()
->send_headers()
->response;
~~~
### Catching Exceptions
**See [Error Handling](errors) for a more detailed description and more examples.**
The previous example provides no error catching, which means if an error occurs a stack trace would be shown which could show sensitive info, as well as be unfriendly for the user. One way to solve this is to add a `try catch` block. If we get an exception, we will show the view located at `views/errors/404.php`. **Note: Because we catch the exception, Kohana will not log the error! It is your responsibility to log the error.**
~~~
try
{
// Execute the main request
$request = Request::instance()->execute();
}
catch (Exception $e)
{
// Be sure to log the error
Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e));
// If there was an error, send a 404 response and display an error
$request->status = 404;
$request->response = View::factory('errors/404');
}
// Send the headers and echo the response
$request->send_headers();
echo $request->response;
~~~

View File

@@ -0,0 +1 @@
This will discuss controller basics, like before() and after(), private function, and about extending controllers like the Controller_Template, or using a parent::before() for authentication.

View File

@@ -0,0 +1,306 @@
# Conventions and Coding Style
It is encouraged that you follow Kohana's [coding style](http://dev.kohanaframework.org/wiki/kohana2/CodingStyle). This makes code more readable and allows for easier code sharing and contributing.
## Class Names and File Location
Class names in Kohana follow a strict convention to facilitate [autoloading](autoloading). Class names should have uppercase first letters with underscores to separate words. Underscores are significant as they directly reflect the file location in the filesystem.
The following conventions apply:
1. CamelCased class names should not be used, except when it is undesirable to create a new directory level.
2. All class file names and directory names are lowercase.
3. All classes should be in the `classes` directory. This may be at any level in the [cascading filesystem](files).
[!!] Unlike Kohana v2.x, there is no separation between "controllers", "models", "libraries" and "helpers". All classes are placed in the "classes/" directory, regardless if they are static "helpers" or object "libraries". You can use whatever kind of class design you want: static, singleton, adapter, etc.
### Examples {#class-name-examples}
Remember that in a class, an underscore means a new directory. Consider the following examples:
Class Name | File Path
----------------------|-------------------------------
Controller_Template | classes/controller/template.php
Model_User | classes/model/user.php
Database | classes/database.php
Database_Query | classes/database/query.php
Form | classes/form.php
## Coding Standards
In order to produce highly consistent source code, we ask that everyone follow the coding standards as closely as possible.
### Brackets
Please use [BSD/Allman Style](http://en.wikipedia.org/wiki/Indent_style#BSD.2FAllman_style) bracketing. Brackets are always on their own line. The exception to this rule is the opening bracket for a class, which can be on the same line.
if ($foo == 'bar')
{
$baz->bar();
}
else
{
$baz->default();
}
// The opening bracket for a class can be on the same line
Class Foobar {
### Naming Conventions
Kohana uses under_score naming, not camelCase naming.
#### Classes
// Controller class, uses Controller_ prefix
class Controller_Apple extends Controller {
// Model class, uses Model_ prefix
class Model_Cheese extends Model {
// Regular class
class Peanut {
When creating an instance of a class, don't use parentheses if you're not passing something on to the constructor:
// Correct:
$db = new Database;
// Incorrect:
$db = new Database();
#### Functions and Methods
Functions should be all lowercase, and use under_scores to separate words:
function drink_beverage($beverage)
{
#### Variables
All variables should be lowercase and use under_score, not camelCase:
// Correct:
$foo = 'bar';
$long_example = 'uses underscores';
// Incorrect:
$weDontWantThis = 'understood?';
### Indentation
You must use tabs to indent your code. Using spaces for tabbing is strictly forbidden.
Vertical spacing (for multi-line) is done with spaces. Tabs are not good for vertical alignment because different people have different tab widths.
$text = 'this is a long text block that is wrapped. Normally, we aim for '
.'wrapping at 80 chars. Vertical alignment is very important for '
.'code readability. Remember that all indentation is done with tabs,'
.'but vertical alignment should be completed with spaces, after '
.'indenting with tabs.';
### String concatenation
Do not put spaces around the concatenation operator:
// Correct:
$str = 'one'.$var.'two';
// Incorrect:
$str = 'one'. $var .'two';
$str = 'one' . $var . 'two';
### Single Line Statements
Single-line IF statements should only be used when breaking normal execution (e.g. return or continue):
// Acceptable:
if ($foo == $bar)
return $foo;
if ($foo == $bar)
continue;
if ($foo == $bar)
break;
if ($foo == $bar)
throw new Exception('You screwed up!');
// Not acceptable:
if ($baz == $bun)
$baz = $bar + 2;
### Comparison Operations
Please use OR and AND for comparison:
// Correct:
if (($foo AND $bar) OR ($b AND $c))
// Incorrect:
if (($foo && $bar) || ($b && $c))
Please use elseif, not else if:
// Correct:
elseif ($bar)
// Incorrect:
else if($bar)
### Switch structures
Each case, break and default should be on a separate line. The block inside a case or default must be indented by 1 tab.
switch ($var)
{
case 'bar':
case 'foo':
echo 'hello';
break;
case 1:
echo 'one';
break;
default:
echo 'bye';
break;
}
### Parentheses
There should be one space after statement name, followed by a parenthesis. The ! (bang) character must have a space on either side to ensure maximum readability. Except in the case of a bang or type casting, there should be no whitespace after an opening parenthesis or before a closing parenthesis.
// Correct:
if ($foo == $bar)
if ( ! $foo)
// Incorrect:
if($foo == $bar)
if(!$foo)
if ((int) $foo)
if ( $foo == $bar )
if (! $foo)
### Ternaries
All ternary operations should follow a standard format. Use parentheses around expressions only, not around just variables.
$foo = ($bar == $foo) ? $foo : $bar;
$foo = $bar ? $foo : $bar;
All comparisons and operations must be done inside of a parentheses group:
$foo = ($bar > 5) ? ($bar + $foo) : strlen($bar);
When separating complex ternaries (ternaries where the first part goes beyond ~80 chars) into multiple lines, spaces should be used to line up operators, which should be at the front of the successive lines:
$foo = ($bar == $foo)
? $foo
: $bar;
### Type Casting
Type casting should be done with spaces on each side of the cast:
// Correct:
$foo = (string) $bar;
if ( (string) $bar)
// Incorrect:
$foo = (string)$bar;
When possible, please use type casting instead of ternary operations:
// Correct:
$foo = (bool) $bar;
// Incorrect:
$foo = ($bar == TRUE) ? TRUE : FALSE;
When casting type to integer or boolean, use the short format:
// Correct:
$foo = (int) $bar;
$foo = (bool) $bar;
// Incorrect:
$foo = (integer) $bar;
$foo = (boolean) $bar;
### Constants
Always use uppercase for constants:
// Correct:
define('MY_CONSTANT', 'my_value');
$a = TRUE;
$b = NULL;
// Incorrect:
define('MyConstant', 'my_value');
$a = True;
$b = null;
Place constant comparisons at the end of tests:
// Correct:
if ($foo !== FALSE)
// Incorrect:
if (FALSE !== $foo)
This is a slightly controversial choice, so I will explain the reasoning. If we were to write the previous example in plain English, the correct example would read:
if variable $foo is not exactly FALSE
And the incorrect example would read:
if FALSE is not exactly variable $foo
Since we are reading left to right, it simply doesn't make sense to put the constant first.
### Comments
#### One-line comments
Use //, preferably above the line of code you're commenting on. Leave a space after it and start with a capital. Never use #.
// Correct
//Incorrect
// incorrect
# Incorrect
### Regular expressions
When coding regular expressions please use PCRE rather than the POSIX flavor. PCRE is considered more powerful and faster.
// Correct:
if (preg_match('/abc/i'), $str)
// Incorrect:
if (eregi('abc', $str))
Use single quotes around your regular expressions rather than double quotes. Single-quoted strings are more convenient because of their simplicity. Unlike double-quoted strings they don't support variable interpolation nor integrated backslash sequences like \n or \t, etc.
// Correct:
preg_match('/abc/', $str);
// Incorrect:
preg_match("/abc/", $str);
When performing a regular expression search and replace, please use the $n notation for backreferences. This is preferred over \\n.
// Correct:
preg_replace('/(\d+) dollar/', '$1 euro', $str);
// Incorrect:
preg_replace('/(\d+) dollar/', '\\1 euro', $str);
Finally, please note that the $ character for matching the position at the end of the line allows for a following newline character. Use the D modifier to fix this if needed. [More info](http://blog.php-security.org/archives/76-Holes-in-most-preg_match-filters.html).
$str = "email@example.com\n";
preg_match('/^.+@.+$/', $str); // TRUE
preg_match('/^.+@.+$/D', $str); // FALSE

View File

@@ -0,0 +1,89 @@
# Cookies
Kohana provides classes that make it easy to work with both cookies and sessions. At a high level both sessions and cookies provide the same functionality. They allow the developer to store temporary or persistent information about a specific client for later retrieval, usually to make something persistent between requests.
[Cookies](http://en.wikipedia.org/wiki/HTTP_cookie) should be used for storing non-private data that is persistent for a long period of time. For example storing a user preference or a language setting. Use the [Cookie] class for getting and setting cookies.
[!!] Kohana uses "signed" cookies. Every cookie that is stored is combined with a secure hash to prevent modification of the cookie. If a cookie is modified outside of Kohana the hash will be incorrect and the cookie will be deleted. This hash is generated using [Cookie::salt()], which uses the [Cookie::$salt] property. You should change this setting when your application is live.
Nothing stops you from using `$_COOKIE` like normal, but you can not mix using the Cookie class and the regular `$_COOKIE` global, because the hash that Kohana uses to sign cookies will not be present, and Kohana will delete the cookie.
## Storing, Retrieving, and Deleting Data
[Cookie] and [Session] provide a very similar API for storing data. The main difference between them is that sessions are accessed using an object, and cookies are accessed using a static class.
### Storing Data
Storing session or cookie data is done using the [Cookie::set] method:
// Set cookie data
Cookie::set($key, $value);
// Store a user id
Cookie::set('user_id', 10);
### Retrieving Data
Getting session or cookie data is done using the [Cookie::get] method:
// Get cookie data
$data = Cookie::get($key, $default_value);
// Get the user id
$user = Cookie::get('user_id');
### Deleting Data
Deleting session or cookie data is done using the [Cookie::delete] method:
// Delete cookie data
Cookie::delete($key);
// Delete the user id
Cookie::delete('user_id');
## Cookie Settings
All of the cookie settings are changed using static properties. You can either change these settings in `bootstrap.php` or by using [transparent extension](extension). Always check these settings before making your application live, as many of them will have a direct affect on the security of your application.
The most important setting is [Cookie::$salt], which is used for secure signing. This value should be changed and kept secret:
Cookie::$salt = 'your secret is safe with me';
[!!] Changing this value will render all cookies that have been set before invalid.
By default, cookies are stored until the browser is closed. To use a specific lifetime, change the [Cookie::$expiration] setting:
// Set cookies to expire after 1 week
Cookie::$expiration = 604800;
// Alternative to using raw integers, for better clarity
Cookie::$expiration = Date::WEEK;
The path that the cookie can be accessed from can be restricted using the [Cookie::$path] setting.
// Allow cookies only when going to /public/*
Cookie::$path = '/public/';
The domain that the cookie can be accessed from can also be restricted, using the [Cookie::$domain] setting.
// Allow cookies only on the domain www.example.com
Cookie::$domain = 'www.example.com';
If you want to make the cookie accessible on all subdomains, use a dot at the beginning of the domain.
// Allow cookies to be accessed on example.com and *.example.com
Cookie::$domain = '.example.com';
To only allow the cookie to be accessed over a secure (HTTPS) connection, use the [Cookie::$secure] setting.
// Allow cookies to be accessed only on a secure connection
Cookie::$secure = TRUE;
// Allow cookies to be accessed on any connection
Cookie::$secure = FALSE;
To prevent cookies from being accessed using Javascript, you can change the [Cookie::$httponly] setting.
// Make cookies inaccessible to Javascript
Cookie::$httponly = TRUE;

View File

@@ -0,0 +1,20 @@
# Debugging
Kohana includes several tools to help you debug your application.
The most basic of these is [Kohana::debug]. This simple method will display any number of variables, similar to [var_export](http://php.net/var_export) or [print_r](http://php.net/print_r), but using HTML for extra formatting.
// Display a dump of the $foo and $bar variables
echo Kohana::debug($foo, $bar);
Kohana also provides a method to show the source code of a particular file using [Kohana::debug_source].
// Display this line of source code
echo Kohana::debug_source(__FILE__, __LINE__);
If you want to display information about your application files without exposing the installation directory, you can use [Kohana::debug_path]:
// Displays "APPPATH/cache" rather than the real path
echo Kohana::debug_path(APPPATH.'cache');
If you are having trouble getting something to work correctly, you could check your Kohana logs and your webserver logs, as well as using a debugging tool like [Xdebug](http://www.xdebug.org/).

View File

@@ -0,0 +1,85 @@
# Error/Exception Handling
Kohana provides both an exception handler and an error handler that transforms errors into exceptions using PHP's [ErrorException](http://php.net/errorexception) class. Many details of the error and the internal state of the application is displayed by the handler:
1. Exception class
2. Error level
3. Error message
4. Source of the error, with the error line highlighted
5. A [debug backtrace](http://php.net/debug_backtrace) of the execution flow
6. Included files, loaded extensions, and global variables
## Example
Click any of the links to toggle the display of additional information:
<div>{{userguide/examples/error}}</div>
## Disabling Error/Exception Handling
If you do not want to use the internal error handling, you can disable it when calling [Kohana::init]:
Kohana::init(array('errors' => FALSE));
## Error Reporting
By default, Kohana displays all errors, including strict mode warnings. This is set using [error_reporting](http://php.net/error_reporting):
error_reporting(E_ALL | E_STRICT);
When you application is live and in production, a more conservative setting is recommended, such as ignoring notices:
error_reporting(E_ALL & ~E_NOTICE);
If you get a white screen when an error is triggered, your host probably has disabled displaying errors. You can turn it on again by adding this line just after your `error_reporting` call:
ini_set('display_errors', TRUE);
Errors should **always** be displayed, even in production, because it allows you to use [exception and error handling](debugging.errors) to serve a nice error page rather than a blank white screen when an error happens.
## Last thoughts
In production, **your application should never have any uncaught exceptions**, as this can expose sensitive information (via the stack trace). In the previous example we make the assumption that there is actually a view called 'views/errors/404', which is fairly safe to assume. One solution is to turn 'errors' off in Kohana::init for your production machine, so it displays the normal php errors rather than a stack trace.
~~~
// snippet from bootstrap.php
Kohana::init(array('
...
'errors' => false,
));
~~~
So rather than displaying the Kohana error page with the stack trace, it will display the default php error. Something like:
**Fatal error: Uncaught Kohana_View_Exception [ 0 ]: The requested view errors/404 could not be found ~ SYSPATH/classes/kohana/view.php [ 215 ] thrown in /var/www/kohanut/docs.kohanaphp.com/3.0/system/classes/kohana/view.php on line 215**
Keep in mind what I said earlier though: **your application should never have any uncaught exceptions**, so this should not be necesarry, though it is a good idea, simply because stack traces on a production environment are a *very* bad idea.
Another solution is to always have a `catch` statement that can't fail, something like an `echo` and an `exit` or a `die()`. This should almost never be necesarry, but it makes some people feel better at night. You can either wrap your entire bootstrap in a try catch, or simply wrap the contents of the catch in another try catch. For example:
~~~
try
{
// Execute the main request
$request->execute();
}
catch (Exception $e)
{
try
{
// Be sure to log the error
Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e));
// If there was an error, send a 404 response and display an error
$request->status = 404;
$request->response = View::factory('errors/404');
}
catch
{
// This is completely overkill, but helps some people sleep at night
echo "Something went terribly wrong. Try again in a few minutes.";
exit;
}
}
~~~

View File

@@ -0,0 +1,101 @@
# Transparent Class Extension
The [cascading filesystem](files) allows transparent class extension. For instance, the class [Cookie] is defined in `SYSPATH/classes/cookie.php` as:
class Cookie extends Kohana_Cookie {}
The default Kohana classes, and many extensions, use this definition so that almost all classes can be extended. You extend any class transparently, by defining your own class in `APPPATH/classes/cookie.php` to add your own methods.
[!!] You should **never** modify any of the files that are distributed with Kohana. Always make modifications to classes using transparent extension to prevent upgrade issues.
For instance, if you wanted to create method that sets encrypted cookies using the [Encrypt] class, you would creat a file at `application/classes/cookie.php` that extends Kohana_Cookie, and adds your functions:
<?php defined('SYSPATH') or die('No direct script access.');
class Cookie extends Kohana_Cookie {
/**
* @var mixed default encryption instance
*/
public static $encryption = 'default';
/**
* Sets an encrypted cookie.
*
* @uses Cookie::set
* @uses Encrypt::encode
*/
public static function encrypt($name, $value, $expiration = NULL)
{
$value = Encrypt::instance(Cookie::$encrpytion)->encode((string) $value);
parent::set($name, $value, $expiration);
}
/**
* Gets an encrypted cookie.
*
* @uses Cookie::get
* @uses Encrypt::decode
*/
public static function decrypt($name, $default = NULL)
{
if ($value = parent::get($name, NULL))
{
$value = Encrypt::instance(Cookie::$encryption)->decode($value);
}
return isset($value) ? $value : $default;
}
} // End Cookie
Now calling `Cookie::encrypt('secret', $data)` will create an encrypted cookie which we can decrypt with `$data = Cookie::decrypt('secret')`.
## How it works
To understand how this works, let's look at what happens normally. When you use the Cookie class, [Kohana::autoload] looks for `classes/cookie.php` in the [cascading filesystem](files). It looks in `application`, then each module, then `system`. The file is found in `system` and is included. Of coures, `system/classes/cookie.php` is just an empty class which extends `Kohana_Cookie`. Again, [Kohana::autoload] is called this time looking for `classes/kohana/cookie.php` which it finds in `system`.
When you add your transparently extended cookie class at `application/classes/cookie.php` this file essentially "replaces" the file at `system/classes/cookie.php` without actually touching it. This happens because this time when we use the Cookie class [Kohana::autoload] looks for `classes/cookie.php` and finds the file in `application` and includes that one, instead of the one in system.
## Example: changing [Cookie] settings
If you are using the [Cookie](cookies) class, and want to change a setting, you should do so using transparent extension, rather than editing the file in the system folder. If you edit it directly, and in the future you upgrade your Kohana version by replacing the system folder, your changes will be reverted and your cookies will probably be invalid. Instead, create a cookie.php file either in `application/classes/cookie.php` or a module (`MODPATH/<modulename>/classes/cookie.php`).
class Cookie extends Kohana_Cookie {
// Set a new salt
public $salt = "some new better random salt phrase";
// Don't allow javascript access to cookies
public $httponly = TRUE;
}
## Example: TODO: an example
Just post the code and breif descript of what function it adds, you don't have to do the "How it works" like above.
## Example: TODO: something else
Just post the code and breif descript of what function it adds, you don't have to do the "How it works" like above.
## More examples
TODO: Provide some links to modules on github, etc that have examples of transparent extension in use.
## Multiple Levels of Extension
If you are extending a Kohana class in a module, you should maintain transparent extensions. In other words, do not include any variables or function in the "base" class (eg. Cookie). Instead make your own namespaced class, and have the "base" class extend that one. With our Encrypted cookie example we can create `MODPATH/mymod/encrypted/cookie.php`:
class Encrypted_Cookie extends Kohana_Cookie {
// Use the same encrypt() and decrypt() methods as above
}
And create `MODPATH/mymod/cookie.php`:
class Cookie extends Encrypted_Cookie {}
This will still allow users to add their own extension to [Cookie] while leaving your extensions intact. To do that they would make a cookie class that extends `Encrypted_Cookie` (rather than `Kohana_Cookie`) in their application folder.

View File

@@ -0,0 +1,83 @@
# Cascading Filesystem
The Kohana filesystem is a heirarchy of similar directory structures that cascade. The heirarchy in Kohana (used when a file is loaded by [Kohana::find_file]) is in the following order:
1. **Application Path**
Defined as `APPPATH` in `index.php`. The default value is `application`.
2. **Module Paths**
This is set as an associative array using [Kohana::modules] in `APPPATH/bootstrap.php`. Each of the values of the array will be searched **in the order that the modules are added**.
3. **System Path**
Defined as `SYSPATH` in `index.php`. The default value is `system`. All of the main or "core" files and classes are defined here.
Files that are in directories higher up the include path order take precedence over files of the same name lower down the order, which makes it is possible to overload any file by placing a file with the same name in a "higher" directory:
![Cascading Filesystem Infographic](cascading_filesystem.png)
This image is only shows certain files, but we can use it to illustrate some examples of the cascading filesystem:
* If Kohana catches an error, it would display the `kohana/error.php` view, So it would call `Kohana::find_file('views', 'kohana/error')`. This would return `application/views/kohana/error.php` because it takes precidence over `system/views/kohana/error.php`. By doing this we can change the error view without editing the system folder.
* If we used `View::factory('welcome')` it would call `Kohana::find_file('views','welcome')` which would return `application/views/welcome.php` because it takes precidence over `modules/common/views/welcome.php`. By doing this, you can overwrite things in a module without editing the modules files.
* If use the Cookie class, [Kohana::auto_load] will call `Kohana::find_file('classes', 'cookie')` which will return `application/classes/cookie.php`. Assuming Cookie extends Kohana_Cookie, the autoloader would then call `Kohana::find_file('classes','kohana/cookie')` which will return `system/classes/kohana/cookie.php` because that file does not exist anywhere higher in the cascade. This is an example of [transparent extension](extension).
* If you used `View::factory('user')` it would call `Kohana::find_file('views','user')` which would return `modules/common/views/user.php`.
* If we wanted to change something in `config/database.php` we could copy the file to `application/config/database.php` and make the changes there. Keep in mind that [config files are merged](files/config#merge) rather than overwritten by the cascade.
## Types of Files
The top level directories of the application, module, and system paths have the following default directories:
classes/
: All classes that you want to [autoload](autoloading) should be stored here. This includes [controllers](mvc/controllers), [models](mvc/models), and all other classes. All classes must follow the [class naming conventions](conventions#class-names-and-file-location).
config/
: Configuration files return an associative array of options that can be loaded using [Kohana::config]. Config files are merged rather than overwritten by the cascade. See [config files](files/config) for more information.
i18n/
: Translation files return an associative array of strings. Translation is done using the `__()` method. To translate "Hello, world!" into Spanish, you would call `__('Hello, world!')` with [I18n::$lang] set to "es-es". I18n files are merged rather than overwritten by the cascade. See [I18n files](files/i18n) for more information.
messages/
: Message files return an associative array of strings that can be loaded using [Kohana::message]. Messages and i18n files differ in that messages are not translated, but always written in the default language and referred to by a single key. Message files are merged rather than overwritten by the cascade. See [message files](files/messages) for more information.
views/
: Views are plain PHP files which are used to generate HTML or other output. The view file is loaded into a [View] object and assigned variables, which it then converts into an HTML fragment. Multiple views can be used within each other. See [views](mvc/views) for more information.
*other*
: You can include any other folders in your cascading filesystem. Examples include, but are not limited to, `guide`, `vendor`, `media`, whatever you want. For example, to find `media/logo.png` in the cascading filesystem you would call `Kohana::find_file('media','logo','png')`.
## Finding Files
The path to any file within the filesystem can be found by calling [Kohana::find_file]:
// Find the full path to "classes/cookie.php"
$path = Kohana::find_file('classes', 'cookie');
// Find the full path to "views/user/login.php"
$path = Kohana::find_file('views', 'user/login');
If the file doesn't have a `.php` extension, pass the extension as the third param.
// Find the full path to "guide/menu.md"
$path = Kohana::find_file('guide', 'menu', 'md');
// If $name is "2000-01-01-first-post" this would look for "posts/2000-01-01-first-post.textile"
$path = Kohana::find_file('posts', $name, '.textile');
## Vendor Extensions
We call extensions or external libraries that are not specific to Kohana "vendor" extensions, and they go in the vendor folder, either in application or in a module. Because these libraries do not follow Kohana's file naming conventions, they cannot be autoloaded by Kohana, so you will have to manually included them. Some examples of vendor libraries are [Markdown](http://daringfireball.net/projects/markdown/), [DOMPDF](http://code.google.com/p/dompdf), [Mustache](http://github.com/bobthecow/mustache.php) and [Swiftmailer](http://swiftmailer.org/).
For example, if you wanted to use [DOMPDF](http://code.google.com/p/dompdf), you would copy it to `application/vendor/dompdf` and include the DOMPDF autoloading class. It can be useful to do this in a controller's before method, as part of a module's init.php, or the contstructor of a singleton class.
require Kohana::find_file('vendor', 'dompdf/dompdf/dompdf_config','inc');
Now you can use DOMPDF without loading any more files:
$pdf = new DOMPDF;
[!!] If you want to convert views into PDFs using DOMPDF, try the [PDFView](http://github.com/shadowhand/pdfview) module.

View File

@@ -0,0 +1,41 @@
# Classes
TODO: Brief intro to classes.
[Models](mvc/models) and [Controllers](mvc/controllers) are classes as well, but are treated slightly differently by Kohana. Read their respective pages to learn more.
## Helper or Library?
Kohana 3 does not differentiate between "helper" classes and "library" classes like in previous versions. They are all placed in the `classes/` folder and follow the same conventions. The distinction is that in general, a "helper" class is used statically, (for examples see the [helpers included in Kohana](helpers)), and library classes are typically instanciated and used as objects (like the [Database query builders](../database/query/builder)). The distinction is not black and white, and is irrelevant anyways, since they are treated the same by Kohana.
## Creating a class
To create a new class, simply place a file in the `classes/` directory at any point in the [Cascading Filesystem](files), that follows the [Class naming conventions](conventions#class-names-and-file-location). For example, lets create a `Foobar` class.
// classes/foobar.php
class Foobar {
static function magic() {
// Does something
}
}
We can now call `Foobar::magic()` any where and Kohana will [autoload](autoloading) the file for us.
We can also put classes in subdirectories.
// classes/professor/baxter.php
class Professor_Baxter {
static function teach() {
// Does something
}
}
We could now call `Professor_Baxter::teach()` any where we want.
For examples of how to create and use classes, simply look at the 'classes' folder in `system` or any module.
## Namespacing your classes
TODO: Discuss namespacing to provide transparent extension functionality in your own classes/modules.

View File

@@ -0,0 +1,95 @@
# Config Files
Configuration files are used to store any kind of configuration needed for a module, class, or anything else you want. They are plain PHP files, stored in the `config/` directory, which return an associative array:
<?php defined('SYSPATH') or die('No direct script access.');
return array(
'setting' => 'value',
'options' => array(
'foo' => 'bar',
),
);
If the above configuration file was called `myconf.php`, you could access it using:
$config = Kohana::config('myconf');
$options = $config['options'];
[Kohana::config] also provides a shortcut for accessing individual keys from configuration arrays using "dot paths" similar to [Arr::path].
Get the "options" array:
$options = Kohana::config('myconf.options');
Get the "foo" key from the "options" array:
$foo = Kohana::config('myconf.options.foo');
Configuration arrays can also be accessed as objects, if you prefer that method:
$options = Kohana::config('myconf')->options;
Please note that you can only access the top level of keys as object properties, all child keys must be accessed using standard array syntax:
$foo = Kohana::config('myconf')->options['foo'];
## Merge
Configuration files are slightly different from most other files within the [cascading filesystem](files) in that they are **merged** rather than overloaded. This means that all configuration files with the same file path are combined to produce the final configuration. The end result is that you can overload *individual* settings rather than duplicating an entire file.
For example, if we wanted to change something in some file
[TODO]
TODO exmaple of adding something to inflector
## Creating your own config files
Let's say we want a config file to store and easily change things like the title of a website, or the google analytics code. We would create a config file, let's call it `site.php`:
// config/site.php
<?php defined('SYSPATH') or die('No direct script access.');
return array(
'title' => 'Our Shiny Website',
'analytics' => FALSE, // analytics code goes here, set to FALSE to disable
);
We could now call `Kohana::config('site.title')` to get the site name, and `Kohana::config('site.analytics')` to get the analytics code.
Let's say we want an archive of versions of some software. We could use config files to store each version, and include links to download, documentation, and issue tracking.
// config/versions.php
<?php defined('SYSPATH') or die('No direct script access.');
return array(
'1.0.0' => array(
'codename' => 'Frog',
'download' => 'files/ourapp-1.0.0.tar.gz',
'documentation' => 'docs/1.0.0',
'released' => '06/05/2009',
'issues' => 'link/to/bug/tracker',
),
'1.1.0' => array(
'codename' => 'Lizard',
'download' => 'files/ourapp-1.1.0.tar.gz',
'documentation' => 'docs/1.1.0',
'released' => '10/15/2009',
'issues' => 'link/to/bug/tracker',
),
/// ... etc ...
);
You could then do the following:
// In your controller
$view->versions = Kohana::config('versions');
// In your view:
foreach ($versions as $version)
{
// echo some html to display each version
}

View File

@@ -0,0 +1 @@
Discuss the format of i18n files, and how to use them.

View File

@@ -0,0 +1,5 @@
<http://kohanaframework.org/guide/using.messages>
Add that message files can be in subfolders, and you can use dot notation to retreive an array path: `Kohana::message('folder/subfolder/file','array.subarray.key')`
Also reinforce that messages are merged by the cascade, not overwritten.

View File

@@ -0,0 +1,26 @@
# Request Flow
Every application follows the same flow:
1. Application starts from `index.php`.
1. The application, module, and system paths are set. (`APPPATH`, `MODPATH`, and `SYSPATH`)
2. Error reporting levels are set.
3. Install file is loaded, if it exists.
4. The [Kohana] class is loaded.
5. The bootstrap file, `APPPATH/bootstrap.php`, is included.
2. Once we are in `bootstrap.php`:
7. [Kohana::init] is called, which sets up error handling, caching, and logging.
8. [Kohana_Config] readers and [Kohana_Log] writers are attached.
9. [Kohana::modules] is called to enable additional modules.
* Module paths are added to the [cascading filesystem](files).
* Includes each module's `init.php` file, if it exists.
* The `init.php` file can perform additional environment setup, including adding routes.
10. [Route::set] is called multiple times to define the [application routes](routing).
11. [Request::instance] is called to start processing the request.
1. Checks each route that has been set until a match is found.
2. Creates the controller instance and passes the request to it.
3. Calls the [Controller::before] method.
4. Calls the controller action, which generates the request response.
5. Calls the [Controller::after] method.
* The above 5 steps can be repeated multiple times when using [HMVC sub-requests](requests).
12. The main [Request] response is displayed

View File

@@ -0,0 +1,135 @@
# Fragments
Fragments are a quick and simple way to cache HTML or other output. Fragments are not useful for caching objects or raw database results, in which case you should use a more robust caching method, which can be achieved with the [Cache module](../cache). Fragments use [Kohana::cache()] and will be placed in the cache directory (`application/cache` by default).
You should use Fragment (or any caching solution) when reading the cache is faster than reprocessing the result. Reading and parsing a remote file, parsing a complicated template, calculating something, etc.
Fragments are typically used in view files.
## Usage
Fragments are used by calling [Fragment::load()] in an `if` statement at the beginning of what you want cached, and [Fragment::save()] at the end. They use [output buffering](http://www.php.net/manual/en/function.ob-start.php) to capture the output between the two function calls.
You can specify the lifetime (in seconds) of the Fragment using the second parameter of [Fragment::load()]. The default lifetime is 30 seconds. You can use the [Date] helper to make more readable times.
Fragments will store a different cache for each language (using [I18n]) if you pass `true` as the third parameter to [Fragment::load()];
You can force the deletion of a Fragment using [Fragment::delete()], or specify a lifetime of 0.
~~~
// Cache for 5 minutes, and cache each language
if ( ! Fragment::load('foobar', Date::MINUTE * 5, true))
{
// Anything that is echo'ed here will be saved
Fragment::save();
}
~~~
## Example: Calculating Pi
In this example we will calculate pi to 1000 places, and cache the result using a fragment. The first time you run this it will probably take a few seconds, but subsequent loads will be much faster, until the fragment lifetime runs out.
~~~
if ( ! Fragment::load('pi1000', Date::HOUR * 4))
{
// Change function nesting limit
ini_set('xdebug.max_nesting_level',1000);
// Source: http://mgccl.com/2007/01/22/php-calculate-pi-revisited
function bcfact($n)
{
return ($n == 0 || $n== 1) ? 1 : bcmul($n,bcfact($n-1));
}
function bcpi($precision)
{
$num = 0;$k = 0;
bcscale($precision+3);
$limit = ($precision+3)/14;
while($k < $limit)
{
$num = bcadd($num, bcdiv(bcmul(bcadd('13591409',bcmul('545140134', $k)),bcmul(bcpow(-1, $k), bcfact(6*$k))),bcmul(bcmul(bcpow('640320',3*$k+1),bcsqrt('640320')), bcmul(bcfact(3*$k), bcpow(bcfact($k),3)))));
++$k;
}
return bcdiv(1,(bcmul(12,($num))),$precision);
}
echo bcpi(1000);
Fragment::save();
}
echo View::factory('profiler/stats');
?>
~~~
## Example: Recent Wikipedia edits
In this example we will use the [Feed] class to retrieve and parse an RSS feed of recent edits to [http://en.wikipedia.org](http://en.wikipedia.org), then use Fragment to cache the results.
~~~
$feed = "http://en.wikipedia.org/w/index.php?title=Special:RecentChanges&feed=rss";
$limit = 50;
// Displayed feeds are cached for 30 seconds (default)
if ( ! Fragment::load('rss:'.$feed)):
// Parse the feed
$items = Feed::parse($feed, $limit);
foreach ($items as $item):
// Convert $item to object
$item = (object) $item;
echo HTML::anchor($item->link,$item->title);
?>
<blockquote>
<p>author: <?php echo $item->creator ?></p>
<p>date: <?php echo $item->pubDate ?></p>
</blockquote>
<?php
endforeach;
Fragment::save();
endif;
echo View::factory('profiler/stats');
~~~
## Example: Nested Fragments
You can nest fragments with different lifetimes to provide more specific control. For example, let's say your page has lots of dynamic content so we want to cache it with a lifetime of five minutes, but one of the pieces takes much longer to generate, and only changes every hour anyways. No reason to generate it every 5 minutes, so we will use a nested fragment.
[!!] If a nested fragment has a shorter lifetime than the parent, it will only get processed when the parent has expired.
~~~
// Cache homepage for five minutes
if ( ! Fragment::load('homepage', Date::MINUTE * 5)):
echo "<p>Home page stuff</p>";
// Pretend like we are actually doing something :)
sleep(2);
// Cache this every hour since it doesn't change as often
if ( ! Fragment::load('homepage-subfragment', Date::HOUR)):
echo "<p>Home page special thingy</p>";
// Pretend like this takes a long time
sleep(5);
Fragment::save(); endif;
echo "<p>More home page stuff</p>";
Fragment::save();
endif;
echo View::factory('profiler/stats');
~~~

View File

@@ -0,0 +1,53 @@
# Helpers
Kohana comes with many static helper functions to make certain tasks easier.
You can make your own helpers by simply making a class and putting it in the `classes` directory, and you can also extend any helper to modify or add new functions using transparent extension.
- **[Arr]** - Array functions. Get an array key or default to a set value, get an array key by path, etc.
- **[CLI]** - Parse command line options.
- **[Cookie]** - Covered in more detail on the [Cookies](cookies) page.
- **[Date]** - Useful date functions and constants. Time between two dates, convert between am/pm and military, date offset, etc.
- **[Encrypt]** - Covered in more detail on the [Security](security) page.
- **[Feed]** - Parse and create RSS feeds.
- **[File]** - Get file type by mime, split and merge a file into small pieces.
- **[Form]** - Create HTML form elements.
- **[Fragment]** - Simple file based caching. Covered in more detail on the [Fragments](fragments) page.
- **[HTML]** - Useful HTML functions. Encode, obfuscate, create script, anchor, and image tags, etc.
- **[I18n]** - Internationalization helper for creating multilanguage sites.
- **[Inflector]** - Change a word into plural or singular form, camelize or humanize a phrase, etc.
- **[Kohana]** - The Kohana class is also a helper. Debug variables (like print_r but better), file loading, etc.
- **[Num]** - Provides locale aware formating and english ordinals (th, st, nd, etc).
- **[Profiler]** - Covered in more detail on the [Profiling](profiling) page.
- **[Remote]** - Remote server access helper using [CURL](http://php.net/curl).
- **[Request]** - Get the current request url, create expire tags, send a file, get the user agent, etc.
- **[Route]** - Create routes, create an internal link using a route.
- **[Security]** - Covered in more detail on the [Security](security) page.
- **[Session]** - Covered in more detail on the [Sessions](sessions) page.
- **[Text]** - Autolink, prevent window words, convert a number to text, etc.
- **[URL]** - Create a relative or absolute URL, make a URL-safe title, etc.
- **[UTF8]** - Provides multi-byte aware string functions like strlen, strpos, substr, etc.
- **[Upload]** - Helper for uploading files from a form.

View File

@@ -0,0 +1,19 @@
# What is Kohana?
Kohana is an open source, [object oriented](http://wikipedia.org/wiki/Object-Oriented_Programming) [MVC](http://wikipedia.org/wiki/ModelViewController "Model View Controller") [web framework](http://wikipedia.org/wiki/Web_Framework) built using [PHP5](http://php.net/manual/intro-whatis "PHP Hypertext Preprocessor") by a team of volunteers that aims to be swift, secure, and small.
[!!] Kohana is licensed under a [BSD license](http://kohanaframework.org/license), so you can legally use it for any kind of open source, commercial, or personal project.
## What makes Kohana great?
Anything can be extended using the unique [filesystem](about.filesystem) design, little or no [configuration](about.configuration) is necessary, [error handling](debugging.errors) helps locate the source of errors quickly, and [debugging](debugging) and [profiling](debugging.profiling) provide insight into the application.
To help secure your applications, tools for [XSS removal](security.xss), [input validation](security.validation), [signed cookies](security.cookies), [form](security.forms) and [HTML](security.html) generators are all included. The [database](security.database) layer provides protection against [SQL injection](http://wikipedia.org/wiki/SQL_Injection). Of course, all official code is carefully written and reviewed for security.
## Contribute to the Documentation
We are working very hard to provide complete documentation. To help improve the guide, please [fork the userguide](http://github.com/kohana/userguide), make your changes, and send a pull request. If you are not familiar with git, you can also submit a [feature request](http://dev.kohanaframework.org/projects/kohana3/issues) (requires registration).
## Unofficial Documentation
If you are having trouble finding an answer here, have a look through the [unofficial wiki](http://kerkness.ca/wiki/doku.php). Your answer may also be found by searching the [forum](http://forum.kohanaphp.com/) or [stackoverflow](http://stackoverflow.com/questions/tagged/kohana) followed by asking your question on either. Additionally, you can chat with the community of developers on the freenode [#kohana](irc://irc.freenode.net/kohana) IRC channel.

View File

@@ -0,0 +1,33 @@
# Installation
1. Download the latest **stable** release from the [Kohana website](http://kohanaframework.org/).
2. Unzip the downloaded package to create a `kohana` directory.
3. Upload the contents of this folder to your webserver.
4. Open `application/bootstrap.php` and make the following changes:
- Set the default [timezone](http://php.net/timezones) for your application.
- Set the `base_url` in the [Kohana::init] call to reflect the location of the kohana folder on your server relative to the document root.
6. Make sure the `application/cache` and `application/logs` directories are writable by the web server.
7. Test your installation by opening the URL you set as the `base_url` in your favorite browser.
[!!] Depending on your platform, the installation's subdirs may have lost their permissions thanks to zip extraction. Chmod them all to 755 by running `find . -type d -exec chmod 0755 {} \;` from the root of your Kohana installation.
You should see the installation page. If it reports any errors, you will need to correct them before continuing.
![Install Page](install.png "Example of install page")
Once your install page reports that your environment is set up correctly you need to either rename or delete `install.php` in the root directory. Kohana is now installed and you should see the output of the welcome controller:
![Welcome Page](welcome.png "Example of welcome page")
## Installing Kohana 3.0 From GitHub
The [source code](http://github.com/kohana/kohana) for Kohana 3.0 is hosted with [GitHub](http://github.com). To install Kohana using the github source code first you need to install git. Visit [http://help.github.com](http://help.github.com) for details on how to install git on your platform.
To install the last stable release of Kohana using git run these commands:
git clone git://github.com/kohana/kohana.git
cd kohana/
git submodule init
git submodule update
[!!] For more information on using Git and Kohana see the [Working with Git](tutorials/git) tutorial.

View File

@@ -0,0 +1,48 @@
## [Kohana]()
- Getting Started
- [Installation](install)
- [Conventions and Style](conventions)
- [Model View Controller](mvc)
- [Controllers](mvc/controllers)
- [Models](mvc/models)
- [Views](mvc/views)
- [Cascading Filesystem](files)
- [Class Files](files/classes)
- [Config Files](files/config)
- [Translation Files](files/i18n)
- [Message Files](files/messages)
- [Request Flow](flow)
- [Bootstrap](bootstrap)
- [Modules](modules)
- [Routing](routing)
- [Error Handling](errors)
- [Tips & Common Mistakes](tips)
- [Upgrading from v2.x](upgrading)
- Basic Usage
- [Debugging](debugging)
- [Loading Classes](autoloading)
- [Transparent Extension](extension)
- [Helpers](helpers)
- [Requests](requests)
- [Sessions](sessions)
- [Cookies](cookies)
- [Fragments](fragments)
- [Profiling](profiling)
- [Security](security)
- [XSS](security/xss)
- [Validation](security/validation)
- [Cookies](security/cookies)
- [Database](security/database)
- [Encryption](security/encryption)
- [Deploying](security/deploying)
- [Tutorials](tutorials)
- [Hello World](tutorials/hello-world)
- [Simple MVC](tutorials/simple-mvc)
- [Routes & Links](tutorials/routes-and-links)
- [Custom Error Pages](tutorials/error-pages)
- [Content Translation](tutorials/translation)
- [Clean URLs](tutorials/clean-urls)
- [Sharing Kohana](tutorials/sharing-kohana)
- [Template Driven Site](tutorials/templates)
- [Working with Git](tutorials/git)

View File

@@ -0,0 +1 @@
This will discuss models, explain what should be in a model, and give *breif* examples of extending models, like using modeling systems.

View File

@@ -0,0 +1,38 @@
# Modules
Modules are simply an addition to the [Cascading Filesystem](files). A module can add any kind of file (controllers, views, classes, config files, etc.) to the filesystem available to Kohana (via [Kohana::find_file]). This is useful to make any part of your application more transportable or shareable between different apps. For example, creating a new modeling system, a search engine, a css/js manager, etc.
## Where to find modules
Kolanos has created [kohana-universe](http://github.com/kolanos/kohana-universe/tree/master/modules/), a fairly comprehensive list of modules that are available on Github. To get your module listed there, send him a message via Github.
Mon Geslani created a [very nice site](http://kohana.mongeslani.com/) that allows you to sort Github modules by activity, watchers, forks, etc. It seems to not be as comprehensive as kohana-universe.
## Enabling modules
Modules are enabled by calling [Kohana::modules] and passing an array of `'name' => 'path'`. The name isn't important, but the path obviously is. A module's path does not have to be in `MODPATH`, but usually is. You can only call [Kohana::modules] once.
Kohana::modules(array(
'auth' => MODPATH.'auth', // Basic authentication
'cache' => MODPATH.'cache', // Caching with multiple backends
'codebench' => MODPATH.'codebench', // Benchmarking tool
'database' => MODPATH.'database', // Database access
'image' => MODPATH.'image', // Image manipulation
'orm' => MODPATH.'orm', // Object Relationship Mapping
'oauth' => MODPATH.'oauth', // OAuth authentication
'pagination' => MODPATH.'pagination', // Paging of results
'unittest' => MODPATH.'unittest', // Unit testing
'userguide' => MODPATH.'userguide', // User guide and API documentation
));
## Init.php
When a module is activated, if an `init.php` file exists in that module's directory, it is included. This is the ideal place to have a module include routes or other initialization necessary for the module to function. The Userguide and Codebench modules have init.php files you can look at.
## How modules work
A file in an enabled module is virtually the same as having that exact file in the same place in the application folder. The main difference being that it can be overwritten by a file of the same name in a higher location (a module enabled after it, or the application folder) via the [Cascading Filesystem](files). It also provides an easy way to organize and share your code.
## Creating your own module
To create a module simply create a folder (usually in `DOCROOT/modules`) and place the files you want to be in the module there, and activate that module in your bootstrap. To share your module, you can upload it to [Github](http://github.com). You can look at examples of modules made by [Kohana](http://github.com/kohana) or [other users](#where-to-find-modules).

View File

@@ -0,0 +1,3 @@
<http://kohanaframework.org/guide/about.mvc>
Discus the MVC pattern, as it pertains to Kohana. Perhaps have an image, etc.

View File

@@ -0,0 +1,189 @@
# Controllers
A Controller is a class file that stands in between the models and the views in an application. It passes information on to the model when data needs to be changed and it requests information from the model when data needs to be loaded. Controllers then pass on the information of the model to the views where the final output can be rendered for the users. Controllers essentially control the flow of the application.
Controllers are called by the [Request::execute()] function based on the [Route] that the url matched. Be sure to read the [routing](routing) page to understand how to use routes to map urls to your controllers.
## Creating Controllers
In order to function, a controller must do the following:
* Reside in `classes/controller` (or a sub-directory)
* Filename must be lowercase, e.g. `articles.php`
* The class name must map to the filename (with `/` replaced with `_`) and each word is capitalized
* Must have the Controller class as a (grand)parent
Some examples of controller names and file locations:
// classes/controller/foobar.php
class Controller_Foobar extends Controller {
// classes/controller/admin.php
class Controller_Admin extends Controller {
Controllers can be in sub-folders:
// classes/controller/baz/bar.php
class Controller_Baz_Bar extends Controller {
// classes/controller/product/category.php
class Controller_Product_Category extends Controller {
[!!] Note that controllers in sub-folders can not be called by the default route, you will need to define a route that has a [directory](routing#directory) param or sets a default value for directory.
Controllers can extend other controllers.
// classes/controller/users.php
class Controller_Users extends Controller_Template
// classes/controller/api.php
class Controller_Api extends Controller_REST
[!!] [Controller_Template] and [Controller_REST] are some example controllers provided in Kohana.
You can also have a controller extend another controller to share common things, such as requiring you to be logged in to use all of those controllers.
// classes/controller/admin.php
class Controller_Admin extends Controller {
// This controller would have a before() that checks if the user is logged in
// classes/controller/admin/plugins.php
class Controller_Admin_Plugins extends Controller_Admin {
// Because this controller extends Controller_Admin, it would have the same logged in check
## $this->request
Every controller has the `$this->request` property which is the [Request] object that called the controller. You can use this to get information about the current request, as well as set the response via `$this->request->response`.
Here is a partial list of the properties and methods available to `$this->request`. These can also be accessed via `Request::instance()`, but `$this->request` is provided as a shortcut. See the [Request] class for more information on any of these.
Property/method | What it does
--- | ---
[$this->request->route](../api/Request#property:route) | The [Route] that matched the current request url
[$this->request->directory](../api/Request#property:directory), <br /> [$this->request->controller](../api/Request#property:controller), <br /> [$this->request->action](../api/Request#property:action) | The directory, controller and action that matched for the current route
[$this->request->param()](../api/Request#param) | Any other params defined in your route
[$this->request->response](../api/Request#property:response) | The content to return for this request
[$this->request->status](../api/Request#property:status) | The HTTP status for the request (200, 404, 500, etc.)
[$this->request->headers](../api/Request#property:headers) | The HTTP headers to return with the response
[$this->request->redirect()](../api/Request#redirect) | Redirect the request to a different url
## Actions
You create actions for your controller by defining a public function with an `action_` prefix. Any method that is not declared as `public` and prefixed with `action_` can NOT be called via routing.
An action method will decide what should be done based on the current request, it *controls* the application. Did the user want to save a blog post? Did they provide the necesarry fields? Do they have permission to da that? The controller will call other classes, including models, to accomplish this. Every action should set `$this->request->response` to the [view file](mvc/views) to be sent to the browser, unless it [redirected](../api/Request#redirect) or otherwise ended the script earlier.
A very basic action method that simply loads a [view](mvc/views) file.
public function action_hello()
{
$this->request->response = View::factory('hello/world'); // This will load views/hello/world.php
}
### Parameters
Parameters can be accessed in two ways. The first is by calling `$this->request->param('name')` where `name` is the name defined in the route.
// Assuming Route::set('example','<controller>(/<action>(/<id>(/<new>)))');
public function action_foobar()
{
$id = $this->request->param('id');
$new = $this->request->param('new');
If that parameter is not set it will be returned as NULL. You can provide a second parameter to set a default value if that param is not set.
public function action_foobar()
{
// $id will be false if it was not supplied in the url
$id = $this->request->param('user',FALSE);
The second way you can access route parameters is from the actions function definition. Any extra keys in your route (keys besides `<directory>`, `<controller>`, and `<action>`) are passed as parameters to your action *in the order they appear in the route*.
// Assuming Route::set('example','<controller>(/<action>(/<id>(/<new>)))');
public function action_foobar($id, $new)
{
Note that the names do not actually matter, *only the order*. You could name the parameters anything you want in both the route and the function definition, they don't even need to match. The following code is identical in function to the previous example.
// Assuming Route::set('example','<controller>(/<action>(/<num>(/<word>)))');
public function action_foobar($foo, $bar)
{
You can provide default values in the same way you do for any php function.
public function action_foobar($id = 0, $new = NULL)
{
You can use whichever method you prefer. Using function params is quick and easy and saves on `$this->request->param()` calls, but keep in mind that if your routes ever change it could change the paramater order and break things. Therefore, it is recommended you use `$this->request->param()`. For example, assuming the following route
Route::set('example','<controller>(/<action>(/<id>(/<new>)))');
If you called "example/foobar/4/bobcat" you could access the parameters by either:
public function action_foobar($id, $new)
{
// OR
public function action_foobar()
{
$id = $this->request->param('id');
$new = $this->request->param('new');
Then, let's say sometime in the future you change your url schemes and your routes. The new route is:
// Note that id and new are switched
Route::set('example','<controller>(/<action>(/<new>(/<id>)))');
Because the `<new>` and `<id>` keys are in a different order, you will need to fix your function definition to be `action_foobar($new, $id)` whereas the function that used `$this->request->param()` calls would continue to function as desired.
### Examples
TODO: some examples of actions
## Before and after
You can use the `before()` and `after()` functions to have code executed before or after the action is executed. For example, you could check if the user is logged in, set a template view, loading a required file, etc.
For example, if you look in `Controller_Template` you can see that in the be
You can check what action has been requested (via `$this->request->action`) and do something based on that, such as requiring the user to be logged in to use a controller, unless they are using the login action.
// Checking auth/login in before, and redirecting if necessary:
Controller_Admin extends Controller {
public function before()
{
// If this user doesn't have the admin role, and is not trying to login, redirect to login
if ( ! Auth::instance()->logged_in('admin') AND $this->request->action !== 'login')
{
$this->request->redirect('admin/login');
}
}
public function action_login() {
...
### Custom __construct() function
In general, you should not have to change the `__construct()` function, as anything you need for all actions can be done in `before()`. If you need to change the controller constructor, you must preserve the parameters or PHP will complain. This is so the Request object that called the controller is available. *Again, in most cases you should probably be using `before()`, and not changing the constructor*, but if you really, *really* need to it should look like this:
// You should almost never need to do this, use before() instead!
// Be sure Kohana_Request is in the params
public function __construct(Kohana_Request $request)
{
// You must call parent::__construct at some point in your function
parent::__construct($request);
// Do whatever else you want
}
## Extending other controllers
TODO: More description and examples of extending other controllers, multiple extension, etc.

View File

@@ -0,0 +1 @@
Discuss models. What should go in a model, what shouldn't be in a model. Provide **very simple** examples using prepared statements, the query builder, as well as mention modeling libraries.

View File

@@ -0,0 +1,161 @@
# Views
Views are files that contain the display information for your application. This is most commonly HTML, CSS and Javascript but can be anything you require such as XML or JSON for AJAX output. The purpose of views is to keep this information separate from your application logic for easy reusability and cleaner code.
Views themselves can contain code used for displaying the data you pass into them. For example, looping through an array of product information and display each one on a new table row. Views are still PHP files so you can use any code you normally would. However, you should try to keep your views as "dumb" as possible and retreive all data you need in your controllers, then pass it to the view.
# Creating View Files
View files are stored in the `views` directory of the [filesystem](files). You can also create sub-directories within the `views` directory to organize your files. All of the following examples are reasonable view files:
APPPATH/views/home.php
APPPATH/views/pages/about.php
APPPATH/views/products/details.php
MODPATH/error/views/errors/404.php
MODPATH/common/views/template.php
## Loading Views
[View] objects will typically be created inside a [Controller](mvc/controllers) using the [View::factory] method. Typically the view is then assigned as the [Request::$response] property or to another view.
public function action_about()
{
$this->request->response = View::factory('pages/about');
}
When a view is assigned as the [Request::$response], as in the example above, it will automatically be rendered when necessary. To get the rendered result of a view you can call the [View::render] method or just type cast it to a string. When a view is rendered, the view file is loaded and HTML is generated.
public function action_index()
{
$view = View::factory('pages/about');
// Render the view
$about_page = $view->render();
// Or just type cast it to a string
$about_page = (string) $view;
$this->request->response = $about_page;
}
## Variables in Views
Once view has been loaded, variables can be assigned to it using the [View::set] and [View::bind] methods.
public function action_roadtrip()
{
$view = View::factory('user/roadtrip')
->set('places', array('Rome', 'Paris', 'London', 'New York', 'Tokyo'));
->bind('user', $this->user);
// The view will have $places and $user variables
$this->request->response = $view;
}
[!!] The only difference between `set()` and `bind()` is that `bind()` assigns the variable by reference. If you `bind()` a variable before it has been defined, the variable will be created with a value of `NULL`.
You can also assign variables directly to the View object. This is identical to calling `set()`;
public function action_roadtrip()
{
$view = View::factory('user/roadtrip');
$view->places = array('Rome', 'Paris', 'London', 'New York', 'Tokyo');
$view->user = $this->user;
// The view will have $places and $user variables
$this->request->response = $view;
}
### Global Variables
An application may have several view files that need access to the same variables. For example, to display a page title in both the header of your template and in the body of the page content. You can create variables that are accessible in any view using the [View::set_global] and [View::bind_global] methods.
// Assign $page_title to all views
View::bind_global('page_title', $page_title);
If the application has three views that are rendered for the home page: `template`, `template/sidebar`, and `pages/home`. First, an abstract controller to create the template will be created:
abstract class Controller_Website extends Controller_Template {
public $page_title;
public function before()
{
parent::before();
// Make $page_title available to all views
View::bind_global('page_title', $this->page_title);
// Load $sidebar into the template as a view
$this->template->sidebar = View::factory('template/sidebar');
}
}
Next, the home controller will extend `Controller_Website`:
class Controller_Home extends Controller_Website {
public function action_index()
{
$this->page_title = 'Home';
$this->template->content = View::factory('pages/home');
}
}
## Views Within Views
If you want to include another view within a view, there are two choices. By calling [View::factory] you can sandbox the included view. This means that you will have to provide all of the variables to the view using [View::set] or [View::bind]:
// In your view file:
// Only the $user variable will be available in "views/user/login.php"
<?php echo View::factory('user/login')->bind('user', $user) ?>
The other option is to include the view directly, which makes all of the current variables available to the included view:
// In your view file:
// Any variable defined in this view will be included in "views/message.php"
<?php include Kohana::find_file('views', 'user/login') ?>
You can also assign a variable of your parent view to be the child view from within your controller. For example:
// In your controller:
public functin action_index()
{
$view = View::factory('common/template);
$view->title = "Some title";
$view->body = View::factory('pages/foobar');
}
// In views/common/template.php:
<html>
<head>
<title><?php echo $title></title>
</head>
<body>
<?php echo $body ?>
</body>
</html>
Of course, you can also load an entire [Request] within a view:
<?php echo Request::factory('user/login')->execute() ?>
This is an example of \[HMVC], which makes it possible to create and read calls to other URLs within your application.
## Differences From v2.x
Unlike version 2.x of Kohana, the view is not loaded within the context of
the [Controller], so you will not be able to access `$this` as the controller
that loaded the view. Passing the controller to the view must be done explictly:
$view->bind('controller', $this);

View File

@@ -0,0 +1,54 @@
# Profiling
Kohana provides a very simple way to display statistics about your application:
1. Common [Kohana] method calls, such as [Kohana::find_file()].
2. Requests. Including the main request, as well as any sub-requests.
3. [Database] queries
4. Average execution times for your application
[!!] In order for profiling to work, the `profile` setting must be `TRUE` in your [Kohana::init()] call in your bootstrap.
## Profiling your code
You can easily add profiling to your own functions and code. This is done using the [Profiler::start()] function. The first parameter is the group, the second parameter is the name of the benchmark.
public function foobar($input)
{
// Be sure to only profile if it's enabled
if (Kohana::$profiling === TRUE)
{
// Start a new benchmark
$benchmark = Profiler::start('Your Category', __FUNCTION__);
}
// Do some stuff
if (isset($benchmark))
{
// Stop the benchmark
Profiler::stop($benchmark);
}
return $something;
}
## How to read the profiling report
The benchmarks are sorted into groups. Each benchmark will show its name, how many times it was run (show in parenthesis after the benchmark name), and then the min, max, average, and total time and memory spent on that benchmark. The total column will have shaded backgrounds to show the relative times between benchmarks in the same group.
At the very end is a group called "Application Execution". This keeps track of how long each execution has taken. The number in parenthesis is how many executions are being compared. It shows the fastest, slowest, and average time and memory usage of the last several requsets. The last box is the time and memory usage of the current request.
((This could use a picture of a profiler with some database queries, etc. with annotations to point out each area as just described.))
## Displaying the profiler
You can display or collect the current [profiler] statistics at any time:
<?php echo View::factory('profiler/stats') ?>
## Preview
(This is the actual profiler stats for this page.)
{{profiler/stats}}

View File

@@ -0,0 +1,15 @@
# Requests
blah
## The main request
request::instance() gets the main request, you set the response in the bootstrap (usually), you use $request->status to send a 404 or other status, $request->headers to send headers, is_ajax, etc.
## Subrequests
TODO: This will talk about subrequests.
<http://kerkness.ca/wiki/doku.php?id=hmvc_in_kohana>
<http://kerkness.ca/wiki/doku.php?id=routing:differ_request_for_internal_and_external>

View File

@@ -0,0 +1,240 @@
# Routing
Kohana provides a very powerful routing system. In essence, routes provide an interface between the urls and your controllers and actions. With the correct routes you could make almost any url scheme correspond to almost any arrangement of controllers, and you could change one without impacting the other.
As mentioned in the [Request Flow](flow) section, a request is handled by the [Request] class, which will look for a matching [Route] and load the appropriate controller to handle that request.
[!!] It is important to understand that **routes are matched in the order they are added**, and as soon as a URL matches a route, routing is essentially "stopped" and *the remaining routes are never tried*. Because the default route matches almost anything, including an empty url, new routes must be place before it.
## Creating routes
If you look in `APPPATH/bootstrap.php` you will see the "default" route as follows:
Route::set('default', '(<controller>(/<action>(/<id>)))')
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));
[!!] The default route is simply provided as a sample, you can remove it and replace it with your own routes.
So this creates a route with the name `default` that will match urls in the format of `(<controller>(/<action>(/<id>)))`.
Let's take a closer look at each of the parameters of [Route::set], which are `name`, `uri`, and an optional array `regex`.
### Name
The name of the route must be a **unique** string. If it is not it will overwrite the older route with the same name. The name is used for creating urls by reverse routing, or checking which route was matched.
### URI
The uri is a string that represents the format of urls that should be matched. The tokens surrounded with `<>` are *keys* and anything surrounded with `()` are *optional* parts of the uri. In Kohana routes, any character is allowed and treated literally aside from `()<>`. The `/` has no meaning besides being a character that must match in the uri. Usually the `/` is used as a static seperator but as long as the regex makes sense, there are no restrictions to how you can format your routes.
Lets look at the default route again, the uri is `(<controller>(/<action>(/<id>)))`. We have three keys or params: controller, action, and id. In this case, the entire uri is optional, so a blank uri would match and the default controller and action (set by defaults(), [covered below](#defaults)) would be assumed resulting in the `Controller_Welcome` class being loaded and the `action_index` method being called to handle the request.
You can use any name you want for your keys, but the following keys have special meaning to the [Request] object, and will influence which controller and action are called:
* **Directory** - The sub-directory of `classes/controller` to look for the controller (\[covered below]\(#directory))
* **Controller** - The controller that the request should execute.
* **Action** - The action method to call.
### Regex
The Kohana route system uses [perl compatible regular expressions](http://perldoc.perl.org/perlre.html) in its matching process. By default each key (surrounded by `<>`) will match `[^/.,;?\n]++` (or in english: anything that is not a slash, period, comma, semicolon, question mark, or newline). You can define your own patterns for each key by passing an associative array of keys and patterns as an additional third argument to Route::set.
In this example, we have controllers in two directories, `admin` and `affiliate`. Because this route will only match urls that begin with `admin` or `affiliate`, the default route would still work for controllers in `classes/controller`.
Route::set('sections', '<directory>(/<controller>(/<action>(/<id>)))',
array(
'directory' => '(admin|affiliate)'
))
->defaults(array(
'controller' => 'home',
'action' => 'index',
));
You can also use a less restrictive regex to match unlimited parameters, or to ignore overflow in a route. In this example, the url `foobar/baz/and-anything/else_that/is-on-the/url` would be routed to `Controller_Foobar::action_baz()` and the `"stuff"` parameter would be `"and-anything/else_that/is-on-the/url"`. If you wanted to use this for unlimited parameters, you could [explode](http://php.net/manual/en/function.explode.php) it, or you just ignore the overflow.
Route::set('default', '(<controller>(/<action>(/<stuff>)))', array('stuff' => '.*'))
->defaults(array(
'controller' => 'welcome',
'action' => 'index',
));
### Default values
If a key in a route is optional (or not present in the route), you can provide a default value for that key by passing an associated array of keys and default values to [Route::defaults], chained after your [Route::set]. This can be useful to provide a default controller or action for your site, among other things.
[!!] The `controller` and `action` key must always have a value, so they either need to be required in your route (not inside of parentheses) or have a default value provided.
In the default route, all the keys are optional, and the controller and action are given a default. If we called an empty url, the defaults would fill in and `Controller_Welcome::action_index()` would be called. If we called `foobar` then only the default for action would be used, so it would call `Controller_Foobar::action_index()` and finally, if we called `foobar/baz` then neither default would be used and `Controller_Foobar::action_baz()` would be called.
TODO: need an example here
You can also use defaults to set a key that isn't in the route at all.
TODO: example of either using directory or controller where it isn't in the route, but set by defaults
### Directory
## Examples
TODO: a million billion examples, you can use the following as a guide for some routes to include:
<http://kerkness.ca/wiki/doku.php?id=routing:routing_basics>
<http://kerkness.ca/wiki/doku.php?id=routing:ignoring_overflow_in_a_route>
<http://kerkness.ca/wiki/doku.php?id=routing:building_routes_with_subdirectories>
There are countless other possibilities for routes. Here are some more examples:
/*
* Authentication shortcuts
*/
Route::set('auth', '<action>',
array(
'action' => '(login|logout)'
))
->defaults(array(
'controller' => 'auth'
));
/*
* Multi-format feeds
* 452346/comments.rss
* 5373.json
*/
Route::set('feeds', '<user_id>(/<action>).<format>',
array(
'user_id' => '\d+',
'format' => '(rss|atom|json)',
))
->defaults(array(
'controller' => 'feeds',
'action' => 'status',
));
/*
* Static pages
*/
Route::set('static', '<path>.html',
array(
'path' => '[a-zA-Z0-9_/]+',
))
->defaults(array(
'controller' => 'static',
'action' => 'index',
));
/*
* You don't like slashes?
* EditGallery:bahamas
* Watch:wakeboarding
*/
Route::set('gallery', '<action>(<controller>):<id>',
array(
'controller' => '[A-Z][a-z]++',
'action' => '[A-Z][a-z]++',
))
->defaults(array(
'controller' => 'Slideshow',
));
/*
* Quick search
*/
Route::set('search', ':<query>', array('query' => '.*'))
->defaults(array(
'controller' => 'search',
'action' => 'index',
));
## Request parameters
The `directory`, `controller` and `action` can be accessed from the [Request] as public properties like so:
// From within a controller:
$this->request->action;
$this->request->controller;
$this->request->directory;
// Can be used anywhere:
Request::instance()->action;
Request::instance()->controller;
Request::instance()->directory;
All other keys specified in a route can be accessed via [Request::param()]:
// From within a controller:
$this->request->param('key_name');
// Can be used anywhere:
Request::instance()->param('key_name');
The [Request::param] method takes an optional second argument to specify a default return value in case the key is not set by the route. If no arguments are given, all keys are returned as an associative array. In addition, `action`, `controller` and `directory` are not accessible via [Request::param()].
For example, with the following route:
Route::set('ads','ad/<ad>(/<affiliate>)')
->defaults(array(
'controller' => 'ads',
'action' => 'index',
));
If a url matches the route, then `Controller_Ads::index()` will be called. You could access the parameters in two ways:
First, any non-special parameters (parameters other than controller, action, and directory) in a route are passed as parameters to the action method in the order they appear in the route. Be sure to define a default value for optional parameters if you don't define them in the route's `->defaults()`.
class Controller_Ads extends Controller {
public function action_index($ad, $affiliate = NULL)
{
}
Secondly, you can access the parameters using the `param()` method of the [Request] class. Again, remember to define a default value (via the second, optional parameter of [Request::param]) if you didn't in `->defaults()`.
class Controller_Ads extends Controller {
public function action_index()
{
$ad = $this->request->param('ad');
$affiliate = $this->request->param('affiliate',NULL);
}
## Where should routes be defined?
The established convention is to either place your custom routes in the `MODPATH/<module>/init.php` file of your module if the routes belong to a module, or simply insert them into the `APPPATH/bootstrap.php` file (be sure to put them **above** the default route) if they are specific to the application. Of course, nothing stops you from including them from an external file, or even generating them dynamically.
## A deeper look at how routes work
TODO: talk about how routes are compiled
## Creating URLs and links using routes
Along with Kohana's powerful routing capabilities are included some methods for generating URLs for your routes' uris. You can always specify your uris as a string using [URL::site] to create a full URL like so:
URL::site('admin/edit/user/'.$user_id);
However, Kohana also provides a method to generate the uri from the route's definition. This is extremely useful if your routing could ever change since it would relieve you from having to go back through your code and change everywhere that you specified a uri as a string. Here is an example of dynamic generation that corresponds to the `feeds` route example from above:
Route::get('feeds')->uri(array(
'user_id' => $user_id,
'action' => 'comments',
'format' => 'rss'
));
Let's say you decided later to make that route definition more verbose by changing it to `feeds/<user_id>(/<action>).<format>`. If you wrote your code with the above uri generation method you wouldn't have to change a single line! When a part of the uri is enclosed in parentheses and specifies a key for which there in no value provided for uri generation and no default value specified in the route, then that part will be removed from the uri. An example of this is the `(/<id>)` part of the default route; this will not be included in the generated uri if an id is not provided.
One method you might use frequently is the shortcut [Request::uri] which is the same as the above except it assumes the current route, directory, controller and action. If our current route is the default and the uri was `users/list`, we can do the following to generate uris in the format `users/view/$id`:
$this->request->uri(array('action' => 'view', 'id' => $user_id));
Or if within a view, the preferable method is:
Request::instance()->uri(array('action' => 'view', 'id' => $user_id));
TODO: examples of using html::anchor in addition to the above examples
## Testing routes
TODO: mention bluehawk's devtools module

View File

@@ -0,0 +1 @@
General security concerns, like using the Security class, CSRF, and a brief intro to XSS, database security, etc. Also mention the security features that Kohana provides, like cleaning globals.

View File

@@ -0,0 +1,3 @@
Discuss security of cookies, like changing the encryption key in the config.
Not sure why I'm linking to this: <http://kohanaframework.org/guide/security.cookies>

View File

@@ -0,0 +1,5 @@
Discuss database security.
How to avoid injection, etc.
Not sure why I'm linking to this: <http://kohanaframework.org/guide/security.database>

View File

@@ -0,0 +1,81 @@
Changes that should happen when you deploy. (Production)
Security settings from: <http://kohanaframework.org/guide/using.configuration>
<http://kerkness.ca/wiki/doku.php?id=setting_up_production_environment>
## Setting up a production environment
There are a few things you'll want to do with your application before moving into production.
1. See the [Bootstrap page](bootstrap) in the docs.
This covers most of the global settings that would change between environments.
As a general rule, you should enable caching and disable profiling ([Kohana::init] settings) for production sites.
[Route::cache] can also help if you have a lot of routes.
2. Catch all exceptions in `application/bootstrap.php`, so that sensitive data is cannot be leaked by stack traces.
See the example below which was taken from Shadowhand's [wingsc.com source](http://github.com/shadowhand/wingsc).
3. Turn on APC or some kind of opcode caching.
This is the single easiest performance boost you can make to PHP itself. The more complex your application, the bigger the benefit of using opcode caching.
/**
* Set the environment string by the domain (defaults to Kohana::DEVELOPMENT).
*/
Kohana::$environment = ($_SERVER['SERVER_NAME'] !== 'localhost') ? Kohana::PRODUCTION : Kohana::DEVELOPMENT;
/**
* Initialise Kohana based on environment
*/
Kohana::init(array(
'base_url' => '/',
'index_file' => FALSE,
'profile' => Kohana::$environment !== Kohana::PRODUCTION,
'caching' => Kohana::$environment === Kohana::PRODUCTION,
));
/**
* Execute the main request using PATH_INFO. If no URI source is specified,
* the URI will be automatically detected.
*/
$request = Request::instance($_SERVER['PATH_INFO']);
try
{
// Attempt to execute the response
$request->execute();
}
catch (Exception $e)
{
if (Kohana::$environment === Kohana::DEVELOPMENT)
{
// Just re-throw the exception
throw $e;
}
// Log the error
Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e));
// Create a 404 response
$request->status = 404;
$request->response = View::factory('template')
->set('title', '404')
->set('content', View::factory('errors/404'));
}
if ($request->send_headers()->response)
{
// Get the total memory and execution time
$total = array(
'{memory_usage}' => number_format((memory_get_peak_usage() - KOHANA_START_MEMORY) / 1024, 2).'KB',
'{execution_time}' => number_format(microtime(TRUE) - KOHANA_START_TIME, 5).' seconds');
// Insert the totals into the response
$request->response = str_replace(array_keys($total), $total, $request->response);
}
/**
* Display the request response.
*/
echo $request->response;

View File

@@ -0,0 +1 @@
Discuss using encryption, including setting the encryption key in config.

View File

@@ -0,0 +1,247 @@
# Validation
*This page needs to be reviewed for accuracy by the development team. Better examples would be helpful.*
Validation can be performed on any array using the [Validate] class. Labels, filters, rules, and callbacks can be attached to a Validate object by the array key, called a "field name".
labels
: A label is a human-readable version of the field name.
filters
: A filter modifies the value of an field before rules and callbacks are run.
rules
: A rule is a check on a field that returns `TRUE` or `FALSE`. If a rule
returns `FALSE`, an error will be added to the field.
callbacks
: A callback is custom method that can access the entire Validate object.
The return value of a callback is ignored. Instead, the callback must
manually add an error to the object using [Validate::error] on failure.
[!!] Note that [Validate] callbacks and [PHP callbacks](http://php.net/manual/language.pseudo-types.php#language.types.callback) are not the same.
Using `TRUE` as the field name when adding a filter, rule, or callback will by applied to all named fields.
**The [Validate] object will remove all fields from the array that have not been specifically named by a label, filter, rule, or callback. This prevents access to fields that have not been validated as a security precaution.**
Creating a validation object is done using the [Validate::factory] method:
$post = Validate::factory($_POST);
[!!] The `$post` object will be used for the rest of this tutorial. This tutorial will show you how to validate the registration of a new user.
### Default Rules
Validation also comes with several default rules:
Rule name | Function
------------------------- |-------------------------------------------------
[Validate::not_empty] | Value must be a non-empty value
[Validate::regex] | Match the value against a regular expression
[Validate::min_length] | Minimum number of characters for value
[Validate::max_length] | Maximum number of characters for value
[Validate::exact_length] | Value must be an exact number of characters
[Validate::email] | An email address is required
[Validate::email_domain] | Check that the domain of the email exists
[Validate::url] | Value must be a URL
[Validate::ip] | Value must be an IP address
[Validate::phone] | Value must be a phone number
[Validate::credit_card] | Require a credit card number
[Validate::date] | Value must be a date (and time)
[Validate::alpha] | Only alpha characters allowed
[Validate::alpha_dash] | Only alpha and hyphens allowed
[Validate::alpha_numeric] | Only alpha and numbers allowed
[Validate::digit] | Value must be an integer digit
[Validate::decimal] | Value must be a decimal or float value
[Validate::numeric] | Only numeric characters allowed
[Validate::range] | Value must be within a range
[Validate::color] | Value must be a valid HEX color
[Validate::matches] | Value matches another field value
[!!] Any method that exists within the [Validate] class may be used as a validation rule without specifying a complete callback. For example, adding `'not_empty'` is the same as `array('Validate', 'not_empty')`.
## Adding Filters
All validation filters are defined as a field name, a method or function (using the [PHP callback](http://php.net/manual/language.pseudo-types.php#language.types.callback) syntax), and an array of parameters:
$object->filter($field, $callback, $parameter);
Filters modify the field value before it is checked using rules or callbacks.
If we wanted to convert the "username" field to lowercase:
$post->filter('username', 'strtolower');
If we wanted to remove all leading and trailing whitespace from *all* fields:
$post->filter(TRUE, 'trim');
## Adding Rules
All validation rules are defined as a field name, a method or function (using the [PHP callback](http://php.net/callback) syntax), and an array of parameters:
$object->rule($field, $callback, $parameter);
To start our example, we will perform validation on a `$_POST` array that contains user registration information:
$post = Validate::factory($_POST);
Next we need to process the POST'ed information using [Validate]. To start, we need to add some rules:
$post
->rule('username', 'not_empty')
->rule('username', 'regex', array('/^[a-z_.]++$/iD'))
->rule('password', 'not_empty')
->rule('password', 'min_length', array('6'))
->rule('confirm', 'matches', array('password'))
->rule('use_ssl', 'not_empty');
Any existing PHP function can also be used a rule. For instance, if we want to check if the user entered a proper value for the SSL question:
$post->rule('use_ssl', 'in_array', array(array('yes', 'no')));
Note that all array parameters must still be wrapped in an array! Without the wrapping array, `in_array` would be called as `in_array($value, 'yes', 'no')`, which would result in a PHP error.
Any custom rules can be added using a [PHP callback](http://php.net/manual/language.pseudo-types.php#language.types.callback]:
$post->rule('username', 'User_Model::unique_username');
[!!] Currently (v3.0.7) it is not possible to use an object for a rule, only static methods and functions.
The method `User_Model::unique_username()` would be defined similar to:
public static function unique_username($username)
{
// Check if the username already exists in the database
return ! DB::select(array(DB::expr('COUNT(username)'), 'total'))
->from('users')
->where('username', '=', $username)
->execute()
->get('total');
}
[!!] Custom rules allow many additional checks to be reused for multiple purposes. These methods will almost always exist in a model, but may be defined in any class.
## Adding callbacks
All validation callbacks are defined as a field name and a method or function (using the [PHP callback](http://php.net/manual/language.pseudo-types.php#language.types.callback) syntax):
$object->callback($field, $callback);
The user password must be hashed if it validates, so we will hash it using a callback:
$post->callback('password', array($model, 'hash_password'));
This would assume that the `$model->hash_password()` method would be defined similar to:
public function hash_password(Validate $array, $field)
{
if ($array[$field])
{
// Hash the password if it exists
$array[$field] = sha1($array[$field]);
}
}
# A Complete Example
First, we need a [View] that contains the HTML form, which will be placed in `application/views/user/register.php`:
<?php echo Form::open() ?>
<?php if ($errors): ?>
<p class="message">Some errors were encountered, please check the details you entered.</p>
<ul class="errors">
<?php foreach ($errors as $message): ?>
<li><?php echo $message ?></li>
<?php endforeach ?>
<?php endif ?>
<dl>
<dt><?php echo Form::label('username', 'Username') ?></dt>
<dd><?php echo Form::input('username', $post['username']) ?></dd>
<dt><?php echo Form::label('password', 'Password') ?></dt>
<dd><?php echo Form::password('password') ?></dd>
<dd class="help">Passwords must be at least 6 characters long.</dd>
<dt><?php echo Form::label('confirm', 'Confirm Password') ?></dt>
<dd><?php echo Form::password('confirm') ?></dd>
<dt><?php echo Form::label('use_ssl', 'Use extra security?') ?></dt>
<dd><?php echo Form::select('use_ssl', array('yes' => 'Always', 'no' => 'Only when necessary'), $post['use_ssl']) ?></dd>
<dd class="help">For security, SSL is always used when making payments.</dd>
</dl>
<?php echo Form::submit(NULL, 'Sign Up') ?>
<?php echo Form::close() ?>
[!!] This example uses the [Form] helper extensively. Using [Form] instead of writing HTML ensures that all of the form inputs will properly handle input that includes HTML characters. If you prefer to write the HTML yourself, be sure to use [HTML::chars] to escape user input.
Next, we need a controller and action to process the registration, which will be placed in `application/classes/controller/user.php`:
class Controller_User extends Controller {
public function action_register()
{
$user = Model::factory('user');
$post = Validate::factory($_POST)
->filter(TRUE, 'trim')
->filter('username', 'strtolower')
->rule('username', 'not_empty')
->rule('username', 'regex', array('/^[a-z_.]++$/iD'))
->rule('username', array($user, 'unique_username'))
->rule('password', 'not_empty')
->rule('password', 'min_length', array('6'))
->rule('confirm', 'matches', array('password'))
->rule('use_ssl', 'not_empty')
->rule('use_ssl', 'in_array', array(array('yes', 'no')))
->callback('password', array($user, 'hash_password'));
if ($post->check())
{
// Data has been validated, register the user
$user->register($post);
// Always redirect after a successful POST to prevent refresh warnings
$this->request->redirect('user/profile');
}
// Validation failed, collect the errors
$errors = $post->errors('user');
// Display the registration form
$this->request->response = View::factory('user/register')
->bind('post', $post)
->bind('errors', $errors);
}
}
We will also need a user model, which will be placed in `application/classes/model/user.php`:
class Model_User extends Model {
public function register($array)
{
// Create a new user record in the database
$id = DB::insert(array_keys($array))
->values($array)
->execute();
// Save the new user id to a cookie
cookie::set('user', $id);
return $id;
}
}
That is it, we have a complete user registration example that properly checks user input!

View File

@@ -0,0 +1,17 @@
# Cross-Site Scripting (XSS) Security
*This page is not comprehensive and should not be considered a complete guide to XSS prevention.*
The first step to preventing [XSS](http://wikipedia.org/wiki/Cross-Site_Scripting) attacks is knowing when you need to protect yourself. XSS can only be triggered when it is displayed within HTML content, sometimes via a form input or being displayed from database results. Any global variable that contains client information can be tainted. This includes `$_GET`, `$_POST`, and `$_COOKIE` data.
## Prevention
There are a few simple rules to follow to guard your application HTML against XSS. The first is to use the [Security::xss] method to clean any input data that comes from a global variable. If you do not want HTML in a variable, use [strip_tags](http://php.net/strip_tags) to remove all unwanted HTML tags from a value.
[!!] If you allow users to submit HTML to your application, it is highly recommended to use an HTML cleaning tool such as [HTML Purifier](http://htmlpurifier.org/) or [HTML Tidy](http://php.net/tidy).
The second is to always escape data when inserting into HTML. The [HTML] class provides generators for many common tags, including script and stylesheet links, anchors, images, and email (mailto) links. Any untrusted content should be escaped using [HTML::chars].
## References
* [OWASP XSS Cheat Sheet](http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)

View File

@@ -0,0 +1,167 @@
# Sessions
Kohana provides classes that make it easy to work with both cookies and sessions. At a high level both sessions and cookies provide the same functionality. They allow the developer to store temporary or persistent information about a specific client for later retrieval, usually to make something persistent between requests.
Sessions should be used for storing temporary or private data. Very sensitive data should be stored using the [Session] class with the "database" or "native" adapters. When using the "cookie" adapter, the session should always be encrypted.
[!!] For more information on best practices with session variables see [the seven deadly sins of sessions](http://lists.nyphp.org/pipermail/talk/2006-December/020358.html).
## Storing, Retrieving, and Deleting Data
[Cookie] and [Session] provide a very similar API for storing data. The main difference between them is that sessions are accessed using an object, and cookies are accessed using a static class.
Accessing the session instance is done using the [Session::instance] method:
// Get the session instance
$session = Session::instance();
When using sessions, you can also get all of the current session data using the [Session::as_array] method:
// Get all of the session data as an array
$data = $session->as_array();
You can also use this to overload the `$_SESSION` global to get and set data in a way more similar to standard PHP:
// Overload $_SESSION with the session data
$_SESSION =& $session->as_array();
// Set session data
$_SESSION[$key] = $value;
### Storing Data
Storing session or cookie data is done using the `set` method:
// Set session data
$session->set($key, $value);
// Or
Session::instance()->set($key, $value);
// Store a user id
$session->set('user_id', 10);
### Retrieving Data
Getting session or cookie data is done using the `get` method:
// Get session data
$data = $session->get($key, $default_value);
// Get the user id
$user = $session->get('user_id');
### Deleting Data
Deleting session or cookie data is done using the `delete` method:
// Delete session data
$session->delete($key);
// Delete the user id
$session->delete('user_id');
## Session Configuration
Always check these settings before making your application live, as many of them will have a direct affect on the security of your application.
## Session Adapters
When creating or accessing an instance of the [Session] class you can decide which session adapter or driver you wish to use. The session adapters that are available to you are:
Native
: Stores session data in the default location for your web server. The storage location is defined by [session.save_path](http://php.net/manual/session.configuration.php#ini.session.save-path) in `php.ini` or defined by [ini_set](http://php.net/ini_set).
Database
: Stores session data in a database table using the [Session_Database] class. Requires the [Database] module to be enabled.
Cookie
: Stores session data in a cookie using the [Cookie] class. **Sessions will have a 4KB limit when using this adapter, and should be encrypted.**
The default adapter can be set by changing the value of [Session::$default]. The default adapter is "native".
To access a Session using the default adapter, simply call [Session::instance()]. To access a Session using something other than the default, pass the adapter name to `instance()`, for example: `Session::instance('cookie')`
### Session Adapter Settings
You can apply configuration settings to each of the session adapters by creating a session config file at `APPPATH/config/session.php`. The following sample configuration file defines all the settings for each adapter:
[!!] As with cookies, a "lifetime" setting of "0" means that the session will expire when the browser is closed.
return array(
'native' => array(
'name' => 'session_name',
'lifetime' => 43200,
),
'cookie' => array(
'name' => 'cookie_name',
'encrypted' => TRUE,
'lifetime' => 43200,
),
'database' => array(
'name' => 'cookie_name',
'encrypted' => TRUE,
'lifetime' => 43200,
'group' => 'default',
'table' => 'table_name',
'columns' => array(
'session_id' => 'session_id',
'last_active' => 'last_active',
'contents' => 'contents'
),
'gc' => 500,
),
);
#### Native Adapter
Type | Setting | Description | Default
----------|-----------|---------------------------------------------------|-----------
`string` | name | name of the session | `"session"`
`integer` | lifetime | number of seconds the session should live for | `0`
#### Cookie Adapter
Type | Setting | Description | Default
----------|-----------|---------------------------------------------------|-----------
`string` | name | name of the cookie used to store the session data | `"session"`
`boolean` | encrypted | encrypt the session data using [Encrypt]? | `FALSE`
`integer` | lifetime | number of seconds the session should live for | `0`
#### Database Adapter
Type | Setting | Description | Default
----------|-----------|---------------------------------------------------|-----------
`string` | group | [Database::instance] group name | `"default"`
`string` | table | table name to store sessions in | `"sessions"`
`array` | columns | associative array of column aliases | `array`
`integer` | gc | 1:x chance that garbage collection will be run | `500`
`string` | name | name of the cookie used to store the session data | `"session"`
`boolean` | encrypted | encrypt the session data using [Encrypt]? | `FALSE`
`integer` | lifetime | number of seconds the session should live for | `0`
##### Table Schema
You will need to create the session storage table in the database. This is the default schema:
CREATE TABLE `sessions` (
`session_id` VARCHAR(24) NOT NULL,
`last_active` INT UNSIGNED NOT NULL,
`contents` TEXT NOT NULL,
PRIMARY KEY (`session_id`),
INDEX (`last_active`)
) ENGINE = MYISAM;
##### Table Columns
You can change the column names to match an existing database schema when connecting to a legacy session table. The default value is the same as the key value.
session_id
: the name of the "id" column
last_active
: UNIX timestamp of the last time the session was updated
contents
: session data stored as a serialized string, and optionally encrypted

View File

@@ -0,0 +1,33 @@
# Tips and Common Mistakes
This is a collection of tips and common mistakes or errors you may encounter.
## Never edit the `system` folder!
You should (almost) never edit the system folder. Any change you want to make to files in system and modules can be made via the [cascading filesystem](files) and [transparent extension](extension) and won't break when you try to update your Kohana version.
## Don't try and use one route for everything
Kohana 3 [routes](routing) are very powerful and flexible, don't be afraid to use as many as you need to make your app function the way you want!
## Reflection_Exception
If you get a Reflection_Exception when setting up your site, it is almost certainly because your [Kohana::init] 'base_url' setting is wrong. If your base url is correct something is probably wrong with your [routes](routing).
ReflectionException [ -1 ]: Class controller_<something> does not exist
// where <something> is part of the url you entered in your browser
### Solution {#reflection-exception-solution}
Set your [Kohana::init] 'base_url' to the correct setting. The base url should be the path to your index.php file relative to the webserver document root.
## ORM/Session __sleep() bug
There is a bug in php which can corrupt your session after a fatal error. A production server shouldn't have uncaught fatal errors, so this bug should only happen during development, when you do something stupid and cause a fatal error. On the next page load you will get a database connection error, then all subsequent page loads will display the following error:
ErrorException [ Notice ]: Undefined index: id
MODPATH/orm/classes/kohana/orm.php [ 1308 ]
### Solution {#orm-session-sleep-solution}
To fix this, clear your cookies for that domain to reset your session. This should never happen on a production server, so you won't have to explain to your clients how to clear their cookies. You can see the [discussion on this issue](http://dev.kohanaframework.org/issues/3242) for more details.

View File

@@ -0,0 +1,17 @@
# Tutorials
## Tutorials in this guide
## Tutorials written elsewhere
### Ellisgl's KO3 tutorial on dealtaker.com:
1. [Install and Basic Usage](http://www.dealtaker.com/blog/2009/11/20/kohana-php-3-0-ko3-tutorial-part-1/)
2. [Views](http://www.dealtaker.com/blog/2009/12/07/kohana-php-3-0-ko3-tutorial-part-2/)
3. [Controllers](http://www.dealtaker.com/blog/2009/12/30/kohana-php-3-0-ko3-tutorial-part-3/)
4. [Models](http://www.dealtaker.com/blog/2010/02/01/kohana-php-3-0-ko3-tutorial-part-4/)
5. [Subrequests](http://www.dealtaker.com/blog/2010/02/25/kohana-php-3-0-ko3-tutorial-part-5/)
6. [Routes](http://www.dealtaker.com/blog/2010/03/03/kohana-php-3-0-ko3-tutorial-part-6/)
7. [Helpers](http://www.dealtaker.com/blog/2010/03/26/kohana-php-3-0-ko3-tutorial-part-7/)
8. [Modules](http://www.dealtaker.com/blog/2010/04/30/kohana-php-3-0-ko3-tutorial-part-8/)
9. [Vendor Libraries](http://www.dealtaker.com/blog/2010/06/02/kohana-php-3-0-ko3-tutorial-part-9/)

View File

@@ -0,0 +1,80 @@
# Clean URLs
Removing `index.php` from your urls.
To keep your URLs clean, you will probably want to be able to access your app without having `/index.php/` in the URL. There are two steps to remove `index.php` from the URL.
1. Edit the bootstrap file
2. Set up rewriting
## 1. Configure Bootstrap
The first thing you will need to change is the `index_file` setting of [Kohana::init] to false:
Kohana::init(array(
'base_url' => '/myapp/',
'index_file' => FALSE,
));
This change will make it so all of the links generated using [URL::site], [URL::base], and [HTML::anchor] will no longer include "index.php" in the URL. All generated links will start with `/myapp/` instead of `/myapp/index.php/`.
## 2. URL Rewriting
Enabling rewriting is done differently, depending on your web server.
Rewriting will make it so urls will be passed to index.php.
## Apache
Rename `example.htaccess` to only `.htaccess` and alter the `RewriteBase` line to match the `base_url` setting from your [Kohana::init]
RewriteBase /myapp/
The rest of the `.htaccess file` rewrites all requests through index.php, unless the file exists on the server (so your css, images, favicon, etc. are still loaded like normal). In most cases, you are done!
### Failed!
If you get a "Internal Server Error" or "No input file specified" error, try changing:
RewriteRule ^(?:application|modules|system)\b - [F,L]
Instead, we can try a slash:
RewriteRule ^(application|modules|system)/ - [F,L]
If that doesn't work, try changing:
RewriteRule .* index.php/$0 [PT]
To something more simple:
RewriteRule .* index.php [PT]
### Still Failed!
If you are still getting errors, check to make sure that your host supports URL `mod_rewrite`. If you can change the Apache configuration, add these lines to the the configuration, usually `httpd.conf`:
<Directory "/var/www/html/myapp">
Order allow,deny
Allow from all
AllowOverride All
</Directory>
You should also check your Apache logs to see if they can shed some light on the error.
## NGINX
It is hard to give examples of nginx configuration, but here is a sample for a server:
location / {
index index.php index.html index.htm;
try_files $uri index.php;
}
location = index.php {
include fastcgi.conf;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
}
If you are having issues getting this working, enable debug level logging in nginx and check the access and error logs.

View File

@@ -0,0 +1,168 @@
# Friendly Error Pages <small>written by <a href="http://mathew-davies.co.uk./">Mathew Davies</a></small>
By default Kohana 3 doesn't have a method to display friendly error pages like that
seen in Kohana 2; In this short guide I will teach you how it is done.
## Prerequisites
You will need `'errors' => TRUE` passed to `Kohana::init`. This will convert PHP
errors into exceptions which are easier to handle.
## 1. A Custom Exception
First off, we are going to need a custom exception class. This is so we can perform different
actions based on it in the exception handler. I will talk more about this later.
_classes/http\_response\_exception.php_
<?php defined('SYSPATH') or die('No direct access');
class HTTP_Response_Exception extends Kohana_Exception {}
## 2. An Improved Exception Handler
Our custom exception handler is self explanatory.
public static function exception_handler(Exception $e)
{
if (Kohana::DEVELOPMENT === Kohana::$environment)
{
Kohana_Core::exception_handler($e);
}
else
{
Kohana::$log->add(Kohana::ERROR, Kohana::exception_text($e));
$attributes = array
(
'action' => 500,
'message' => rawurlencode($e->getMessage())
);
if ($e instanceof HTTP_Response_Exception)
{
$attributes['action'] = $e->getCode();
}
// Error sub-request.
echo Request::factory(Route::url('error', $attributes))
->execute()
->send_headers()
->response;
}
}
If we are in the development environment then pass it off to Kohana otherwise:
* Log the error
* Set the route action and message attributes.
* If a `HTTP_Response_Exception` was thrown, then override the action with the error code.
* Fire off an internal sub-request.
The action will be used as the HTTP response code. By default this is: 500 (internal
server error) unless a `HTTP_Response_Exception` was thrown.
So this:
throw new HTTP_Response_Exception(':file does not exist', array(':file' => 'Gaia'), 404);
would display a nice 404 error page, where:
throw new Kohana_Exception('Directory :dir must be writable',
array(':dir' => Kohana::debug_path(Kohana::$cache_dir)));
would display an error 500 page.
**The Route**
Route::set('error', 'error/<action>(/<message>)', array('action' => '[0-9]++', 'message' => '.+'))
->defaults(array(
'controller' => 'error_handler'
));
## 3. The Error Page Controller
public function before()
{
parent::before();
$this->template->page = URL::site(rawurldecode(Request::$instance->uri));
// Internal request only!
if (Request::$instance !== Request::$current)
{
if ($message = rawurldecode($this->request->param('message')))
{
$this->template->message = $message;
}
}
else
{
$this->request->action = 404;
}
}
1. Set a template variable "page" so the user can see what they requested. This
is for display purposes only.
2. If an internal request, then set a template variable "message" to be shown to
the user.
3. Otherwise use the 404 action. Users could otherwise craft their own error messages, eg:
`error/404/email%20your%20login%20information%20to%20hacker%40google.com`
~~~
public function action_404()
{
$this->template->title = '404 Not Found';
// Here we check to see if a 404 came from our website. This allows the
// webmaster to find broken links and update them in a shorter amount of time.
if (isset ($_SERVER['HTTP_REFERER']) AND strstr($_SERVER['HTTP_REFERER'], $_SERVER['SERVER_NAME']) !== FALSE)
{
// Set a local flag so we can display different messages in our template.
$this->template->local = TRUE;
}
// HTTP Status code.
$this->request->status = 404;
}
public function action_503()
{
$this->template->title = 'Maintenance Mode';
$this->request->status = 503;
}
public function action_500()
{
$this->template->title = 'Internal Server Error';
$this->request->status = 500;
}
~~~
You will notice that each example method is named after the HTTP response code
and sets the request response code.
## 4. Handling 3rd Party Modules.
Some Kohana modules will make calls to `Kohana::exception_handler`. We can redirect
calls made to it by extending the Kohana class and passing the exception to our handler.
<?php defined('SYSPATH') or die('No direct script access.');
class Kohana extends Kohana_Core
{
/**
* Redirect to custom exception_handler
*/
public static function exception_handler(Exception $e)
{
Error::exception_handler($e);
}
}
## 5. Conclusion
So that's it. Now displaying a nice error page is as easy as:
throw new HTTP_Response_Exception('The website is down', NULL, 503);

View File

@@ -0,0 +1,129 @@
<http://kohanaframework.org/guide/tutorials.git>
Provide links to some git tutorials.
### Creating a New Application
[!!] The following examples assume that your web server is already set up, and you are going to create a new application at <http://localhost/gitorial/>.
Using your console, change to the empty directory `gitorial` and run `git init`. This will create the bare structure for a new git repository.
Next, we will create a [submodule](http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html) for the `system` directory. Go to <http://github.com/kohana/core> and copy the "Clone URL":
![Github Clone URL](http://img.skitch.com/20091019-rud5mmqbf776jwua6hx9nm1n.png)
Now use the URL to create the submodule for `system`:
git submodule add git://github.com/kohana/core.git system
[!!] This will create a link to the current development version of the next stable release. The development version should almost always be safe to use, have the same API as the current stable download with bugfixes applied.
Now add whatever submodules you need. For example, if you need the [Database] module:
git submodule add git://github.com/kohana/database.git modules/database
After submodules are added, they must be initialized:
git submodule init
Now that the submodules are added, you can commit them:
git commit -m 'Added initial submodules'
Next, create the application directory structure. This is the bare minimum required:
mkdir -p application/classes/{controller,model}
mkdir -p application/{config,views}
mkdir -m 0777 -p application/{cache,logs}
If you run `find application` you should see this:
application
application/cache
application/config
application/classes
application/classes/controller
application/classes/model
application/logs
application/views
We don't want git to track log or cache files, so add a `.gitignore` file to each of the directories. This will ignore all non-hidden files:
echo '[^.]*' > application/{logs,cache}/.gitignore
[!!] Git ignores empty directories, so adding a `.gitignore` file also makes sure that git will track the directory, but not the files within it.
Now we need the `index.php` and `bootstrap.php` files:
wget http://github.com/kohana/kohana/raw/master/index.php
wget http://github.com/kohana/kohana/raw/master/application/bootstrap.php -O application/bootstrap.php
Commit these changes too:
git add application
git commit -m 'Added initial directory structure'
That's all there is to it. You now have an application that is using Git for versioning.
### Adding Submodules
To add a new submodule complete the following steps:
1. run the following code - git submodule add repository path for each new submodule e.g.:
git submodule add git://github.com/shadowhand/sprig.git modules/sprig
2. then init and update the submodules:
git submodule init
git submodule update
### Updating Submodules
At some point you will probably also want to upgrade your submodules. To update all of your submodules to the latest `HEAD` version:
git submodule foreach 'git checkout master && git pull origin master'
To update a single submodule, for example, `system`:
cd system
git checkout master
git pull origin master
cd ..
git add system
git commit -m 'Updated system to latest version'
If you want to update a single submodule to a specific commit:
cd modules/database
git pull origin master
git checkout fbfdea919028b951c23c3d99d2bc1f5bbeda0c0b
cd ../..
git add database
git commit -m 'Updated database module'
Note that you can also check out the commit at a tagged official release point, for example:
git checkout 3.0.6
Simply run `git tag` without arguments to get a list of all tags.
### Removing Submodules
To remove a submodule that is no longer needed complete the following steps:
1. open .gitmodules and remove the reference to the to submodule
It will look something like this:
[submodule "modules/auth"]
path = modules/auth
url = git://github.com/kohana/auth.git
2. open .git/config and remove the reference to the to submodule\\
[submodule "modules/auth"]
url = git://github.com/kohana/auth.git
3. run git rm --cached path/to/submodule, e.g.
git rm --cached modules/auth
**Note:** Do not put a trailing slash at the end of path. If you put a trailing slash at the end of the command, it will fail.

View File

@@ -0,0 +1,106 @@
# Hello, World
Just about every framework ever written has some kind of hello world example included, so it'd be pretty rude of us to break this tradition!
We'll start out by creating a very very basic hello world, and then we'll expand it to follow MVC principles.
## Bare bones
First off we have to make a controller that Kohana can use to handle a request.
Create the file `application/classes/controller/hello.php` in your application folder and fill it out like so:
<?php defined('SYSPATH') OR die('No Direct Script Access');
Class Controller_Hello extends Controller
{
public function action_index()
{
echo 'hello, world!';
}
}
Lets see what's going on here:
`<?php defined('SYSPATH') OR die('No Direct Script Access');`
: You should recognise the first tag as an opening php tag (if you don't you should probably [learn php](http://php.net)). What follows is a small check that makes sure that this file is being included by Kohana. It stops people from accessing files directly from the url.
`Class Controller_Hello extends Controller`
: This line declares our controller, each controller class has to be prefixed with `Controller_` and an underscore delimited path to the folder the controller is in (see [Conventions and styles](about.conventions) for more info). Each controller should also extend the base `Controller` class which provides a standard structure for controllers.
`public function action_index()`
: This defines the "index" action of our controller. Kohana will attempt to call this action if the user hasn't specified an action. (See [Routes, URLs and Links](tutorials.urls))
`echo 'hello, world!';`
: And this is the line which outputs the customary phrase!
Now if you open your browser and go to http://localhost/index.php/hello you should see something like:
![Hello, World!](hello_world_1.png "Hello, World!")
## That was good, but we can do better
What we did in the previous section was a good example of how easy it to create an *extremely* basic Kohana app. (In fact it's so basic, that you should never make it again!)
If you've ever heard anything about MVC you'll probably have realised that echoing content out in a controller is strictly against the principles of MVC.
The proper way to code with an MVC framework is to use _views_ to handle the presentation of your application, and allow the controller to do what it does best control the flow of the request!
Lets change our original controller slightly:
<?php defined('SYSPATH') OR die('No Direct Script Access');
Class Controller_Hello extends Controller_Template
{
public $template = 'site';
public function action_index()
{
$this->template->message = 'hello, world!';
}
}
`extends Controller_Template`
: We're now extending the template controller, it makes it more convenient to use views within our controller.
`public $template = 'site';`
: The template controller needs to know what template you want to use. It'll automatically load the view defined in this variable and assign the view object to it.
`$this->template->message = 'hello, world!';`
: `$this->template` is a reference to the view object for our site template. What we're doing here is assigning a variable called "message", with a value of "hello, world!" to the view.
Now lets try running our code...
![Hello, World!](hello_world_2_error.png "Hello, World!")
For some reason Kohana's thrown a wobbly and isn't showing our amazing message.
If we look at the error message we can see that the View library wasn't able to find our site template, probably because we haven't made it yet *doh*!
Let's go and make the view file `application/views/site.php` for our message:
<html>
<head>
<title>We've got a message for you!</title>
<style type="text/css">
body {font-family: Georgia;}
h1 {font-style: italic;}
</style>
</head>
<body>
<h1><?php echo $message; ?></h1>
<p>We just wanted to say it! :)</p>
</body>
</html>
If we refresh the page then we can see the fruits of our labour:
![hello, world! We just wanted to say it!](hello_world_2.png "hello, world! We just wanted to say it!")
## Stage 3 Profit!
In this tutorial you've learnt how to create a controller and use a view to separate your logic from your display.
This is obviously a very basic introduction to working with Kohana and doesn't even scrape the potential you have when developing applications with it.

View File

@@ -0,0 +1,3 @@
<http://kohanaframework.org/guide/tutorials.urls>
Not sure if this is actually necessary anymore with the [routing page](routing).

View File

@@ -0,0 +1,54 @@
# Sharing Kohana
Because Kohana follows a [front controller] pattern, which means that all requests are sent to `index.php`, the filesystem is very configurable. Inside of `index.php` you can change the `$application`, `$modules`, and `$system` paths.
[!!] There is a security check at the top of every Kohana file to prevent it from being accessed without using the front controller. Also, the `.htaccess` file should protect those folders as well. Moving the application, modules, and system directories to a location that cannot be accessed vie web can add another layer of security, but is optional.
The `$application` variable lets you set the directory that contains your application files. By default, this is `application`. The `$modules` variable lets you set the directory that contains module files. The `$system` variable lets you set the directory that contains the default Kohana files. You can move these three directories anywhere.
For instance, by default the directories are set up like this:
www/
index.php
application/
modules/
system/
You could move the directories out of the web root so they look like this:
application/
modules/
system/
www/
index.php
Then you would need to change the settings in `index.php` to be:
$application = '../application';
$modules = '../modules';
$system = '../system';
## Sharing system and modules
To take this a step further, we could point several kohana apps to the same system and modules folders. For example (and this is just an example, you could arrange these anyway you want):
apps/
foobar/
application/
www/
bazbar/
application/
www/
kohana/
3.0.6/
3.0.7/
3.0.8/
modules/
And you would need to change the settings in `index.php` to be:
$application = '../application';
$system = '../../../kohana/3.0.6';
$modules = '../../../kohana/modules';
Using this method each app can point to a central copy of kohana, and you can add a new version, and quickly update your apps to point to the new version by editing their `index.php` files.

View File

@@ -0,0 +1 @@
Simple example of controller model and view working together.

View File

@@ -0,0 +1,7 @@
Making a template driven site.
<http://kerkness.ca/wiki/doku.php?id=template-site:create_the_template>
<http://kerkness.ca/wiki/doku.php?id=template-site:extending_the_template_controller>
<http://kerkness.ca/wiki/doku.php?id=template-site:basic_page_controller>

View File

@@ -0,0 +1,5 @@
<http://kerkness.ca/wiki/doku.php?id=routing:static_pages>
<http://kerkness.ca/wiki/doku.php?id=routing:multi-language_with_a_route>

View File

@@ -0,0 +1,292 @@
# Upgrading from 2.3.x
*This page needs reviewed for accuracy by the development team.*
Most of Kohana v3 works very differently from Kohana 2.3, here's a list of common gotchas and tips for upgrading.
## Naming conventions
The 2.x series differentiated between different 'types' of class (i.e. controller, model etc.) using suffixes. Folders within model / controller folders didn't have any bearing on the name of the class.
In 3.0 this approach has been scrapped in favour of the Zend framework filesystem conventions, where the name of the class is a path to the class itself, separated by underscores instead of slashes (i.e. `/some/class/file.php` becomes `Some_Class_File`).
See the [conventions documentation](start.conventions) for more information.
## Input Library
The Input Library has been removed from 3.0 in favour of just using `$_GET` and `$_POST`.
### XSS Protection
If you need to XSS clean some user input you can use [Security::xss_clean] to sanitise it, like so:
$_POST['description'] = security::xss_clean($_POST['description']);
You can also use the [Security::xss_clean] as a filter with the [Validate] library:
$validation = new Validate($_POST);
$validate->filter('description', 'Security::xss_clean');
### POST & GET
One of the great features of the Input library was that if you tried to access the value in one of the superglobal arrays and it didn't exist the Input library would return a default value that you could specify i.e.:
$_GET = array();
// $id is assigned the value 1
$id = Input::instance()->get('id', 1);
$_GET['id'] = 25;
// $id is assigned the value 25
$id = Input::instance()->get('id', 1);
In 3.0 you can duplicate this functionality using [Arr::get]:
$_GET = array();
// $id is assigned the value 1
$id = Arr::get($_GET, 'id', 1);
$_GET['id'] = 42;
// $id is assigned the value 42
$id = Arr::get($_GET, 'id', 1);
## ORM Library
There have been quite a few major changes in ORM since 2.3, here's a list of the more common upgrading problems.
### Member variables
All member variables are now prefixed with an underscore (_) and are no longer accessible via `__get()`. Instead you have to call a function with the name of the property, minus the underscore.
For instance, what was once `loaded` in 2.3 is now `_loaded` and can be accessed from outside the class via `$model->loaded()`.
### Relationships
In 2.3 if you wanted to iterate a model's related objects you could do:
foreach($model->{relation_name} as $relation)
However, in the new system this won't work. In version 2.3 any queries generated using the Database library were generated in a global scope, meaning that you couldn't try and build two queries simultaneously. Take for example:
# TODO: NEED A DECENT EXAMPLE!!!!
This query would fail as the second, inner query would 'inherit' the conditions of the first one, thus causing pandemonia.
In v3.0 this has been fixed by creating each query in its own scope, however this also means that some things won't work quite as expected. Take for example:
foreach(ORM::factory('user', 3)->where('post_date', '>', time() - (3600 * 24))->posts as $post)
{
echo $post->title;
}
[!!] (See [the Database tutorial](tutorials.databases) for the new query syntax)
In 2.3 you would expect this to return an iterator of all posts by user 3 where `post_date` was some time within the last 24 hours, however instead it'll apply the where condition to the user model and return a `Model_Post` with the joining conditions specified.
To achieve the same effect as in 2.3 you need to rearrange the structure slightly:
foreach(ORM::factory('user', 3)->posts->where('post_date', '>', time() - (36000 * 24))->find_all() as $post)
{
echo $post->title;
}
This also applies to `has_one` relationships:
// Incorrect
$user = ORM::factory('post', 42)->author;
// Correct
$user = ORM::factory('post', 42)->author->find();
### Has and belongs to many relationships
In 2.3 you could specify `has_and_belongs_to_many` relationships. In 3.0 this functionality has been refactored into `has_many` *through*.
In your models you define a `has_many` relationship to the other model but then you add a `'through' => 'table'` attribute, where `'table'` is the name of your through table. For example (in the context of posts<>categories):
$_has_many = array
(
'categories' => array
(
'model' => 'category', // The foreign model
'through' => 'post_categories' // The joining table
),
);
If you've set up kohana to use a table prefix then you don't need to worry about explicitly prefixing the table.
### Foreign keys
If you wanted to override a foreign key in 2.x's ORM you had to specify the relationship it belonged to, and your new foreign key in the member variable `$foreign_keys`.
In 3.0 you now define a `foreign_key` key in the relationship's definition, like so:
Class Model_Post extends ORM
{
$_belongs_to = array
(
'author' => array
(
'model' => 'user',
'foreign_key' => 'user_id',
),
);
}
In this example we should then have a `user_id` field in our posts table.
In has_many relationships the `far_key` is the field in the through table which links it to the foreign table and the foreign key is the field in the through table which links "this" model's table to the through table.
Consider the following setup, "Posts" have and belong to many "Categories" through `posts_sections`.
| categories | posts_sections | posts |
|------------|------------------|---------|
| id | section_id | id |
| name | post_id | title |
| | | content |
Class Model_Post extends ORM
{
protected $_has_many = array(
'sections' => array(
'model' => 'category',
'through' => 'posts_sections',
'far_key' => 'section_id',
),
);
}
Class Model_Category extends ORM
{
protected $_has_many = array (
'posts' => array(
'model' => 'post',
'through' => 'posts_sections',
'foreign_key' => 'section_id',
),
);
}
Obviously the aliasing setup here is a little crazy, but it's a good example of how the foreign/far key system works.
### ORM Iterator
It's also worth noting that `ORM_Iterator` has now been refactored into `Database_Result`.
If you need to get an array of ORM objects with their keys as the object's pk, you need to call [Database_Result::as_array], e.g.
$objects = ORM::factory('user')->find_all()->as_array('id');
Where `id` is the user table's primary key.
## Router Library
In version 2 there was a Router library that handled the main request. It let you define basic routes in a `config/routes.php` file and it would allow you to use custom regex for the routes, however it was fairly inflexible if you wanted to do something radical.
## Routes
The routing system (now refered to as the request system) is a lot more flexible in 3.0. Routes are now defined in the bootstrap file (`application/bootstrap.php`) and the module init.php (`modules/module_name/init.php`). It's also worth noting that routes are evaluated in the order that they are defined.
Instead of defining an array of routes you now create a new [Route] object for each route. Unlike in the 2.x series there is no need to map one uri to another. Instead you specify a pattern for a uri, use variables to mark the segments (i.e. controller, method, id).
For example, in 2.x these regexes:
$config['([a-z]+)/?(\d+)/?([a-z]*)'] = '$1/$3/$1';
Would map the uri `controller/id/method` to `controller/method/id`. In 3.0 you'd use:
Route::set('reversed','(<controller>(/<id>(/<action>)))')
->defaults(array('controller' => 'posts', 'action' => 'index'));
[!!] Each uri should have be given a unique name (in this case it's `reversed`), the reasoning behind this is explained in [the url tutorial](tutorials.urls).
Angled brackets denote dynamic sections that should be parsed into variables. Rounded brackets mark an optional section which is not required. If you wanted to only match uris beginning with admin you could use:
Rouse::set('admin', 'admin(/<controller>(/<id>(/<action>)))');
And if you wanted to force the user to specify a controller:
Route::set('admin', 'admin/<controller>(/<id>(/<action>))');
Also, Kohana does not use any 'default defaults'. If you want Kohana to assume your default action is 'index', then you have to tell it so! You can do this via [Route::defaults]. If you need to use custom regex for uri segments then pass an array of `segment => regex` i.e.:
Route::set('reversed', '(<controller>(/<id>(/<action>)))', array('id' => '[a-z_]+'))
->defaults(array('controller' => 'posts', 'action' => 'index'))
This would force the `id` value to consist of lowercase alpha characters and underscores.
### Actions
One more thing we need to mention is that methods in a controller that can be accessed via the url are now called "actions", and are prefixed with 'action_'. E.g. in the above example, if the user calls `admin/posts/1/edit` then the action is `edit` but the method called on the controller will be `action_edit`. See [the url tutorial](tutorials.urls) for more info.
## Sessions
There are no longer any Session::set_flash(), Session::keep_flash() or Session::expire_flash() methods, instead you must use [Session::get_once].
## URL Helper
Only a few things have changed with the url helper - `url::redirect()` has been moved into `$this->request->redirect()` within controllers) and `Request::instance()->redirect()` instead.
`url::current` has now been replaced with `$this->request->uri()`
## Valid / Validation
These two classes have been merged into a single class called `Validate`.
The syntax has also changed a little for validating arrays:
$validate = new Validate($_POST);
// Apply a filter to all items in the arrays
$validate->filter(TRUE, 'trim');
// To specify rules individually use rule()
$validate
->rule('field', 'not_empty')
->rule('field', 'matches', array('another_field'));
// To set multiple rules for a field use rules(), passing an array of rules => params as the second argument
$validate->rules('field', array(
'not_empty' => NULL,
'matches' => array('another_field')
));
The 'required' rule has also been renamed to 'not_empty' for clarity's sake.
## View Library
There have been a few minor changes to the View library which are worth noting.
In 2.3 views were rendered within the scope of the controller, allowing you to use `$this` as a reference to the controller within the view, this has been changed in 3.0. Views now render in an empty scope. If you need to use `$this` in your view you can bind a reference to it using [View::bind]: `$view->bind('this', $this)`.
It's worth noting, though, that this is *very* bad practice as it couples your view to the controller, preventing reuse. The recommended way is to pass the required variables to the view like so:
$view = View::factory('my/view');
$view->variable = $this->property;
// OR if you want to chain this
$view
->set('variable', $this->property)
->set('another_variable', 42);
// NOT Recommended
$view->bind('this', $this);
Because the view is rendered in an empty scope `Controller::_kohana_load_view` is now redundant. If you need to modify the view before it's rendered (i.e. to add a generate a site-wide menu) you can use [Controller::after].
Class Controller_Hello extends Controller_Template
{
function after()
{
$this->template->menu = '...';
return parent::after();
}
}