📜 ⬆️ ⬇️

Sample Kohana Bulletin Board


Kohana is a fairly young PHP framework, fork CI, totally tied to OOP. The advantages of Kohana include the use of all the features of PHP5 at 100%, high speed, "lightness" and ease of both use and study. Of the minuses, a small community clearly stands out, as a result, not very high quality documentation and a small number of modules and libraries.

Not so long ago, I began my acquaintance with frameworks with Kohana and, I must say, I was surprised how easily he succumbed to me. I thought it would be much more difficult. But the lack of documentation and code samples was very significant. Having dug in mana and source analysis, after a while, I filled in the gaps of interest to me regarding Kohana, and so I decided to write this article so that other people who find themselves in a similar situation should not get enough sleep at night and heal headaches.

Under the cut is an example of a bulletin board, written with the help of Kohana, perhaps in some places it does not pretend to rationality and common sense, but still I hope to hear constructive criticism.
')
The article is intended for people who have the concept of MVC and OOP, but who have not, or who have had little to do, dealing with frameworks.


Let's start


Recently, I faced the task of writing a small bulletin board based on a news site, where users could post their ads for buying, selling and so on. Write very quickly. Honestly, prior to this incident, my whole experience with the frameworks was to install the symfony sandbox and then remove it. And all I knew about frameworks is that most of them “use MVC” and that they make life much easier . Because at that time, I was already reading Habr, for some reason, one of the publications , which claimed that the Kohana framework "was created to be lightweight, fast and easy to use," sank into my head. I think that's why I chose it. So,

What do we want to get as a result?


We want us to have such opportunities for users:

Conditions for categories:


With what is needed from us, we have decided, now we will figure out how we will implement it.

To register users, we will use the Auth module, which is included in the standard version of version 2.3. To use it, create several tables in the database:
# <br/>
CREATE TABLE IF NOT EXISTS `users` (
`id` int (11) unsigned NOT NULL auto_increment,
`username` varchar (32) NOT NULL default '' ,
`password` char (50) NOT NULL default '' ,
`email` varchar (127) NOT NULL default '' ,
` join ` int (10) unsigned NOT NULL default '0' ,
`last_login` int (10) unsigned NOT NULL default '0' ,
`logins` int (10) unsigned NOT NULL default '0' ,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_username` (`username`),
UNIQUE KEY `uniq_email` (`email`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3;
INSERT INTO `users` (`id`, `username`, `password`, `email`, ` join `, `last_login`, `logins`) VALUES (1, 'admin' , '098f6bcd4621d373cade4e832627b4f6' , 'example@example.com' , 1215075372, 0, 0);

# <br/>
CREATE TABLE IF NOT EXISTS `roles` (
`id` int (4) unsigned NOT NULL auto_increment,
`name` varchar (32) NOT NULL ,
`description` varchar (255) NOT NULL ,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3;
INSERT INTO `roles` (`id`, `name`, `description`) VALUES (1, 'login' , '' );
INSERT INTO `roles` (`id`, `name`, `description`) VALUES (2, 'admin' , '' );

# <br/>
CREATE TABLE IF NOT EXISTS `roles_users` (
`user_id` int (10) unsigned NOT NULL ,
`role_id` int (10) unsigned NOT NULL ,
PRIMARY KEY (`user_id`,`role_id`),
KEY `fk_role_id` (`role_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `roles_users` (`user_id`, `role_id`) VALUES (1, 2);

# a Auth <br/>
DROP TABLE IF EXISTS `d_user_tokens`;
CREATE TABLE IF NOT EXISTS `user_tokens` (
`id` int (11) unsigned NOT NULL auto_increment,
`user_id` int (11) unsigned NOT NULL ,
`user_agent` varchar (40) NOT NULL ,
`token` varchar (32) NOT NULL ,
`created` int (10) unsigned NOT NULL ,
`expires` int (10) unsigned NOT NULL ,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_token` (`token`),
KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


Next, create tables for categories and ads. In this case, all categories will have a parent_id field, which will point to the id of the parent category whose parent_id will be 0.
CREATE TABLE IF NOT EXISTS `categories` (
`id` int (10) unsigned NOT NULL auto_increment,
`parent_id` int (10) unsigned NOT NULL default '0' ,
`name` varchar (150) character set utf8 NOT NULL ,
PRIMARY KEY (`id`),
KEY `parent_id` (`parent_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=14;
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (1, 0, '' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (2, 1, '' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (3, 1, '' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (4, 1, ', ' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (5, 0, '' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (6, 5, ' ' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (7, 5, ', ' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (8, 5, ', ' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (9, 0, '' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (10, 9, ' ' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (11, 9, ', ' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (12, 9, ' ' );
INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (13, 9, ' ' );

CREATE TABLE IF NOT EXISTS `items` (
`id` int (10) unsigned NOT NULL auto_increment,
`title` varchar (250) character set utf8 NOT NULL ,
`category_id` int (10) unsigned NOT NULL default '0' ,
`content` text NOT NULL ,
`user_id` int (10) unsigned NOT NULL ,
`datepub` int (10) unsigned NOT NULL default '0' ,
PRIMARY KEY (`id`),
KEY `title` (`title`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=2;
INSERT INTO `items` (`id`, `title`, `category_id`, `content`, `user_id`, `datepub`) VALUES (1, ' ' , 2, ' . ' , 1, 1229251545);


Models


Models are part of MVC that presents data and responds to requests, being the final authority between the script and the database.

Now about the models that we will use. To work with users, we have standard models of the Auth module, to refer to articles and categories, create our own - create the file /application/models/category.php and write the following class in it:
class Category_Model extends ORM_Tree{
protected $children = 'categories' ;
protected $has_many = array( 'items' );
}

Now create another file /application/models/item.php with this class:
class Item_Model extends ORM{
protected $has_one = array( 'user' );
protected $belongs_to = array( 'category' );
}

Now in Russian - for the category model, we created an extended ORM (about this a little below) the class of the categories table with a tree structure (ORM_Tree), which records can have descendants in the form of records from the same table (protected $ children = `categories` , by default the parent is the `parent_id` field) and each entry of which can have many nested entries in the` items` table. And for the ad model, we have a simple ORM class of the `items` table, which contains one entry from the` users` table and belongs to the `categories` table

I think it should be clarified that the names of models should be in a single number, i.e. if the items table is used, then the model name should be Item_Model. Also, the situation is with the declarations of model variables, if a set of objects is supposed ($ children, $ has_many), then the argument must be plural, if the object is one ($ has_one, $ belong_to), then the number is a single one.


Controllers


It seems that they have figured out the models, now about the controllers - they serve to display information, being a layer between models and views.

What controllers do we need? I think these:
  1. for the main page, at the same time and categories
  2. for sub-categories
  3. to view ads
  4. to log users
  5. for their registration

Since the first with the second, also the third with the fourth, are logically interconnected, combine them into one for each type and as a result we get three controllers - category.php , items.php and user.php .

Then a lot of code, most of which is intuitive, and zanakom people working with ORM and MVC.

What is ORM?
I think that if you have read to here, then you should not have any questions about MVC, but with regard to ORM they may well appear. Wikipedia tells us that ORM is a programming technology that links databases with the concepts of object-oriented programming languages, creating a “virtual object database.” What does it mean? This means that by calling the factory method of the ORM class with a parameter, for example, 'table', we get an object, a projection of the `tables` table (I’ve talked about plural numbers and singular numbers above), and the place for making SQL queries to the database through an extension, or some abstract class, we can access the methods and properties of the called class. Those. for example, the selection of records with id equal to 5 from the database will occur not the usual $ db-> query ('SELECT * FROM `table` WHERE (` id` = 5)') , but by referring to the ORM projection - ORM :: factory ('table ') -> where (' id ', 5) -> find_all () . But unlike simple abstract access, ORM provides a lot of goodies that are difficult to understand without trying. So, if we are talking about id, then the record can be obtained even easier - ORM :: factory ('table', 5) . Using the example of our above-named models, we can get the name of the category in which the declaration is with id = 5 by calling ORM :: factory ('item', 5) -> category-> name , and not writing a bunch of SQL queries.


category.php :
class Category_Controller extends Template_Controller {<br> public $template = 'index' ;<br><br> public function index() { // <br> $_result = '' ;<br> $_cats = array();<br> $categories = ORM::factory( 'category' )-> where ( 'parent_id' , 0)->find_all();<br> foreach ($categories as $l) {<br> $_tmp = new View( 'category' );<br> $_tmp->id = $l->id;<br> $_tmp->name = $l->name;<br> $_tmp->children = $l->children;<br> $_cats[] = $_tmp;<br> }<br> $_result = new View( 'category' );<br> $_result->cats = $_cats;<br> $ this ->template->content = $_result;<br> }<br><br> public function view($ params ) { // <br> $_result = '' ;<br> $categories = ORM::factory( 'category' , $ params )->children;<br> foreach ($categories as $l) {<br> foreach ($l->items as $n) {<br> $_tmp = new View( 'item' );<br> $_tmp->content = $n;<br> $_result.= $_tmp;<br> }<br> }<br> $ this ->template->content = $_result;<br> }<br><br> public function viewsub($ params ) { // - <br> $_result = '' ;<br> $categories = ORM::factory( 'category' , $ params );<br> foreach ($categories->items as $l) {<br> $_tmp = new View( 'item' );<br> $_tmp->content = $l;<br> $_result.= $_tmp;<br> }<br> $ this ->template->content = $_result;<br> }<br>}

In the index method, we collect an array of main categories with objects of subcategories in it and the number of declarations (count) in subcategories and output this whole thing through the category view, which will be described in the “Views” chapter.

The view method shows us the contents of all subcategories of the category with the id passed in the parameter (how it gets there - in the “Final Settings” chapter). The viewsub method also works with the difference that it is applicable to subcategories.

Now the ad controller, items.php :
class Items_Controller extends Template_Controller {<br><br> public $template = 'index' ;<br><br> public function index() { // , <br> url::redirect( '/index' );<br> }<br><br> public function view($arg) { // id = $arg <br> $_item = ORM::factory( 'item' , $arg);<br> $ this ->template->content = new View( 'item' );<br> $ this ->template->content->content = $_item;<br> }<br><br> public function edit($arg) { // c id = $arg <br> $_tmp = ORM::factory( 'item' , $arg);<br><br> if (Auth::instance()->logged_in( 'admin' ) || (Auth::instance()->get_user() &&<br> (Auth::instance()->get_user()->id == $_tmp->user_id))) { // <br> $category = array();<br> // <br> foreach (ORM::factory( 'category' )-> where ( 'parent_id' , 0)->find_all() as $l) {<br> foreach ($l->children as $n) {<br> $category[$l->name][$n->id] = $n->name;<br> }<br> }<br> // <br> $form = new Forge(url::current());<br> $form->set_attr( 'method' , 'post' );<br> $form->input( 'title' ) // <input id = 'title' /> <br> ->label( '' ) // <label for='title'></label> <br> ->rules( 'required|length[3,40]' ) // - , 3 40 <br> -> value ($_tmp->title); // $_tmp->title <br> $form->textarea( 'addtext' )<br> ->label( '' )<br> ->rules( 'required' )<br> -> value ($_tmp->content);<br> $form->dropdown( 'category' )<br> ->label( '' )<br> ->options($category)<br> ->selected($_tmp->category_id);<br> $form->submit( '' );<br><br> // <br> if ($form->validate()) {<br> // ORM <br> $ new = ORM::factory( 'item' , $arg);<br> $ new ->title = $form->inputs[ 'title' ]-> value ;<br> $ new ->content = $form->inputs[ 'addtext' ]-> value ;<br> $ new ->category_id = $form->inputs[ 'category' ]-> value ;<br> // - <br> $ new ->save();<br> url::redirect( '/' );<br> }<br> // <br> $ this ->template->content = $form->render();<br> } else {<br> $ this ->template->content = " " ;<br> }<br> }<br><br> public function add() { // <br> if (Auth::instance()->logged_in()) { // <br> $category = array();<br> foreach (ORM::factory( 'category' )-> where ( 'parent_id' , 0)->find_all() as $l) {<br> foreach ($l->children as $n) {<br> $category[$l->name][$n->id] = $n->name;<br> }<br> }<br> $form = new Forge(url::current());<br> $form->set_attr( 'method' , 'post' );<br> $form->input( 'title' )<br> ->label( '' )<br> ->rules( 'required|length[3,40]' );<br> $form->textarea( 'addtext' )<br> ->label( '' )<br> ->rules( 'required' );<br> $form->dropdown( 'category' )<br> ->label( '' )<br> ->options($category)<br> ->selected(0);<br> $form->submit( '' );<br> if ($form->validate()) {<br> $ new = ORM::factory( 'item' );<br> $ new ->title = $form->inputs[ 'title' ]-> value ;<br> $ new ->content = $form->inputs[ 'addtext' ]-> value ;<br> $ new ->category_id = $form->inputs[ 'category' ]-> value ;<br> // Auth <br> $ new ->user_id = Auth::instance()->get_user()->id;<br> $ new ->datepub = time();<br> $ new ->save();<br> url::redirect( '/' );<br> }<br> $ this ->template->content = $form->render();<br> } else {<br> $ this ->template->content = " " ;<br> }<br> }<br><br> public function delete($arg) { // <br> $ new = ORM::factory( 'item' , $arg);<br> if (Auth::instance()->logged_in( 'admin' ) || (Auth::instance()->get_user() &&<br> (Auth::instance()->get_user()->id == $ new ->user_id))) { // <br> $ new ->delete();<br> url::redirect( '/' );<br> } else {<br> $ this ->template->content = " " ;<br> }<br> }<br>}

I think the only class that we have not met before is the Forge. It is used to facilitate working with forms, but, unfortunately, it is excluded from the standard delivery of Kohana. Where to get it, I will tell below.

Well, the user controller, user.php :
class User_Controller extends Template_Controller {<br><br> public $template = 'index' ;<br><br> public function login() { // <br> if (Auth::instance()->logged_in()) { // , <br> url::redirect( '/' );<br> } else { // <br> $form = new Forge;<br> $form->set_attr( 'method' , 'post' );<br> $form->input( 'username' )<br> ->label( '' )<br> ->rules( 'required|length[4,32]' );<br> $form->password( 'password' )<br> ->label( '' )<br> ->rules( 'required|length[4,40]' );<br> $form->submit( '' );<br> if ($form->validate()) {<br> $user = ORM::factory( 'user' , $form->username-> value );<br> // , <br> if (Auth::instance()->login($user, $form->password-> value )) {<br> url::redirect( '/' );<br> } else {<br> // , <br> $form->password->add_error( 'login_failed' , ' , .' );<br> }<br> }<br> }<br> $ this ->template->content = $form->render();<br> }<br><br> public function logout() { // <br> if (Auth::instance()->logged_in()) {<br> Auth::instance()->logout(TRUE);<br> }<br> url::redirect( '/' );<br> }<br><br> public function register() { // <br> if (Auth::instance()->logged_in()) {<br> url::redirect( '/' );<br> } else {<br> $form = new Forge(url::current(), '' );<br> $form->set_attr( 'method' , 'post' );<br> $form->input( 'username' )<br> ->label( '' )<br> // , '_' '-' 4 32 <br> ->rules( 'required|length[4,32]|valid_alpha_dash' );<br> $form->password( 'password' )->label( '' );<br> $form->password( 'password2' )<br> ->label( ' ' )<br> ->rules( 'required|length[6,40]|valid_alpha_dash' )<br> // 'password' <br> ->matches($form->password);<br> $form->input( 'email' )<br> ->label( 'E-Mail' )<br> ->rules( 'required|valid_email' );<br> $form->submit( '' );<br> if ($form->validate()) {<br> $user = ORM::factory( 'user' , $form->username-> value );<br> // , <br> if (!$user->username_exists($form->username-> value )) {<br> $user->username = $form->username-> value ;<br> $user->password = $form->password-> value ;<br> $user->email = $form->email-> value ;<br> // <br> if ($user->save() && $user->add(ORM::factory( 'role' , 'login' ))) {<br> Auth::instance()->login($user, $form->password-> value );<br> url::redirect( '/' );<br> }<br> }<br> }<br> }<br> $ this ->template->content = $form->render();<br> }<br>}

An interesting point when creating a registration form, namely the object's matches () method, which is returned by the Forge password method. When accessing it, the parameter needs to specify another password object, if the values ​​do not match, the script will generate an error.

Kinds


Views (views) are responsible for displaying information and are the final layer between the user and the application.
Now a little about the types. In total, in our engine, we used three types, alternately about each:

category.php :
<?php foreach($cats as $content): ?> <br> < div class ="category" > <br> < h3 >< a href ="/ category / <?php echo $content->id ?> " > <?php echo $content->name ?> </ a > / h3 > <br> < ul > <br> <?php foreach($content->children as $l): ?> <br> < li >< a href ="/ subcategory / <?php echo $l->id ?> " > <?php echo $l->name ?> </ a ></ li > <br> <?php endforeach; ?> <br> </ ul > <br> </ div > <br> <?php endforeach; ?>

As you remember, we passed an array with categories and subcategories to it, which it processes, in a loop, displaying them all.

index.php :
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" > <br> < html xmlns ="http://www.w3.org/1999/xhtml" > <br> < head > <br> < title > Kohana </ title > <br> </ head > <br> < body > <br> < div id ="enter" style ="float:right" > <br> <?php if(Auth::instance()->logged_in()) { ?> <br> , < b > <?php echo Auth::instance()->get_user()->username ?> </ b > • < a href ="/logout" > </ a > <br> <?php } else { ?> <br> < a href ="/login" > </ a > • < a href ="/register" > </ a > <br> <?php } ?> <br> </ div > <br> < div id ='links' > <br> < a href ="/" > </ a > <br> < a href ="/add" > </ a > <br> </ div > <br> < div id ='content' > <br> <?php echo $content ?> <br> </ div > <br> </ body > <br> </ html >

This is a “wrapper” for all pages that we display. In the enter block, we have such a trigger, which, depending on the status of the user, either displays his name with a link to the output, or links to the input and registration.

item.php :
< div class ="item" > <br> <?php if(Auth::instance()->logged_in('admin') || (Auth::instance()->get_user() &&<br> Auth::instance()->get_user()->id == $content->user_id)) { ?> <br> < span style ="float:right" > <br> < a href ="/ items / edit / <?php echo $content->id ?> " > </ a > <br> < a href ="/ items / delete / <?php echo $content->id ?> " > </ a > <br> </ span > <br> <?php } ?> <br> < h1 > <?php echo html::anchor('/' . $content->id, html::specialchars($content->title)) ?> </ h1 > <br> < div class ="other" > <?php echo date("jMY ", $content->datepub) ?> <br> < b > <?php echo html::specialchars($content->>user->username) ?> </ b > <br> </ div > <br> < div class ="news" > <br> <?php<br> echo text::auto_p($content->content);<br> ?> <br> </ div > <br> </ div >

This view shows us the ad itself, displaying links to delete and edit in case the user is admin or the author of the ad. You can also notice the use of two helper - html and text. html :: specialchars makes the string “safe”, html :: anchor makes a link — the first parameter is the address, the second is the text, and text :: auto_p automatically adds paragraphs to the plain text, as stated in off. documentation "nl2br () on steroids".

Final settings


Now we are a little tweaking our “system” before launching.

Create the database.php file in the / application / config / folder and put the following in it:
$config[ 'default' ][ 'connection' ] = array(
'type' => 'mysql' ,
'user' => '' ,
'pass' => '' ,
'host' => '' ,
'database' => ' ' ,
);

This, as you understood, is the settings for MySQL.

Next, open the config.php file in the same folder and bring the $ config ['modules'] array to the form
$config[ 'modules' ] = array
(
MODPATH. 'auth' ,
MODPATH. 'forge' ,
);

You should also change the $ config ['site_domain'] to '/', if the Kohana directories are in your site root. And I highly recommend setting the value of $ config ['index_page'] to an empty string, otherwise Kohana will generate relative links with the insertion of /index.php/ in the URL (for example : site.com/index.php/mail ).

And now create a file routes.php and write in it
$config[ '_default' ] = 'category' ;
$config[ 'category/([0-9]+)' ] = 'category/view/$1' ;
$config[ 'subcategory/([0-9]+)' ] = 'category/viewsub/$1' ;
$config[ 'add' ] = 'items/add' ;
$config[ 'items/([0-9]+)' ] = 'items/view/$1' ;
$config[ 'edit/([0-9]+)' ] = 'items/edit/$1' ;
$config[ 'delete/([0-9]+)' ] = 'items/delete/$1' ;
$config[ 'login' ] = 'user/login' ;
$config[ 'logout' ] = 'user/logout' ;
$config[ 'register' ] = 'user/register' ;
$co

Source: https://habr.com/ru/post/47941/


All Articles