This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
khosb/includes/kohana/modules/userguide/guide/about.upgrading.md
2011-05-03 09:49:01 +10:00

11 KiB

Upgrading from 2.3.x

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 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 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.

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 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();
	}
}