Mittwoch, 2. Mai 2012

Kohana 3.2 für Einsteiger

In diesem ersten Post in meinem Blog geht es über das Programmieren eines kleinen Rechners mit dem PHP-Framework Kohana. Dieses kleine Projekt soll dazu dienen die Grundlagen der Benutzereingabeverarbeitung, Validierung und Authentifizierung in Kohana zu verstehen.

Ich setze vorraus, dass ihr das Kohana Framework bereits fertig eingerichtet habt und die Grundprinzipien von Kohana(Filesystem, Routing, Moduleinbindung usw.) verstanden habt. Solltet ihr noch nicht soweit sein lest euch einfach den guide durch: http://kohanaframework.org/3.2/guide/.
Des Weiteren solltet ihr den Umgang mit phpMyAdmin beherschen.





Was das Programm kann
Der Benutzer kann sich registrieren und einloggen. Danach wird er zum eigentlichen Rechner weitergeleitet. Der Loginablauf ist einfach gehalten es ist auch keine Bestätigung nach der Registrierung notwendig.
Mit dem Rechner kann man einfache Addtions und Subtraktionsaufgaben lösen, welche dann automatisch in einer MySQL Datenbank gespeichert werden(Sowohl die beiden Summanden/Minuenden/Subtrahenden sowie das Ergebnis).
Die bereits gerechneten Aufgaben kann man sich anzeigen lassen und einzelne Aufgaben wieder löschen.


Vorbereitungen/ Konfiguration

Als erstes passen wir die bootstrap.php an. Ihr solltet folgende Module einbinden:


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

    // 'unittest'   => MODPATH.'unittest',   // Unit testing

     'userguide'  => MODPATH.'userguide',  // User guide and API documentation

    ));


Folgende Route verwenden wir (die einzigste für dieses Projekt)

Route::set('default', '(<controller>(/<action>(/<id>)))')

       ->defaults(array(

        'controller' => 'login',

        'action'     => 'index',

    ));


Als nächstes Konfigurieren wir unser Datenbankmodul. Dafür müssen wir ein paar Einstellungen in der "Application/config/database.php" vornehmen. Diese Zeilen sind zu ändern:

'hostname'   => 'localhost',
            'database'   => 'kohana_rechner',
            'username'   => 'root',
            'password'   => 'root',
            'persistent' => FALSE,


Einrichten der Datenbank

 Ihr geht in euer phpMyAdmin und legt eine neue Datenbank namens "kohana_rechner" an. Dem user "root" gebt ihr das Passwort root (wenn Ihr ein anderes haben wollt müsst ihr dieses auch in der database.php ändern).
Hinweis: Komischerweise müssen wir eine Tabelle namens "Session" einrichten obwohl wir nur Cookie Sessions verwenden ansonsten tauchen Fehlermeldungen auf. Wenn jemand weiß, warum das so ist, würde ich mich über einen Kommentar freuen.

Hier ist das gesamte MySQL Datenbankschema:

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
CREATE TABLE `addition` (   `ID` int(11) NOT NULL AUTO_INCREMENT,   `summand1` decimal(10,0) DEFAULT NULL,   `summand2` decimal(10,0) DEFAULT NULL,   `ergebnis` decimal(10,0) DEFAULT NULL,   PRIMARY KEY (`ID`) ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=48 ; CREATE TABLE `sessions` (   `session_id` varchar(24) NOT NULL,   `last_active` int(10) unsigned NOT NULL,   `contents` text NOT NULL,   PRIMARY KEY (`session_id`),   KEY `last_active` (`last_active`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE `sub` (   `ID` int(11) NOT NULL AUTO_INCREMENT,   `summand1` decimal(10,0) DEFAULT NULL,   `summand2` decimal(10,0) DEFAULT NULL,   `ergebnis` decimal(10,0) DEFAULT NULL,   PRIMARY KEY (`ID`) ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 ; CREATE TABLE `users` (   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,   `email` varchar(127) NOT NULL,   `username` varchar(32) NOT NULL DEFAULT '',   `password` char(70) NOT NULL,   `logins` int(10) unsigned NOT NULL DEFAULT '0',   `last_login` int(10) unsigned DEFAULT NULL,   PRIMARY KEY (`id`),   UNIQUE KEY `uniq_username` (`username`),   UNIQUE KEY `uniq_email` (`email`) ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=12 ;









Los gehts!

Leider kann ich aus zeitlichen Gründen nicht jeden Schritt einzeln erklären deshalb werde ich die Quelltexte der einzelnen Dateien nacheinander hier einfügen. Diese sind mit einigen Kommentaren versehen sodass ihr alles gut verstehen müsstet. Wenn doch noch Fragen auftauchen freue ich mich über euer Kommentar.

Da alle Controller das Modell nutzen werde ich euch dieses zuerst zeigen. Ihr werdet sehen das wir hier die nützliche ORM Funktion von Kohana nutzen das bedeutet, wir setzen die SQL Statements aus Kohana Funktionen zusammen statt sie in reinem SQL einzugeben, dadurch erreichen wir eine größere Flexibilität.
 "Application/classes/model/rechner.php"

 <?php defined('SYSPATH') or die('No direct script access.');



Class Model_Rechner extends Model{

    

    public function write_tasks($summand1, $summand2, $ergebnis, $table){

        //Zu gunsten der Einfachheit habe ich in der Datenbank in der Subtraktionstabelle die Minuenden und Subtrahenden auch Summanden genannt

        $query = DB::insert($table, array('summand1', 'summand2', 'ergebnis'))->values(array($summand1, $summand2, $ergebnis))->execute();

    }



    public function daten_auslesen($table){

        return DB::select()->from($table)->execute();

    }



    public function delete_tasks($id, $table){

        $query = DB::delete($table)->where('ID', '=', $id)->execute();

    }



    public function registrieren($username, $password, $email){

        $query = DB::insert('users', array('username', 'password', 'email'))->values(array($username, $password, $email))->execute();

    }



    public function getPassword($username){

        $result_set = DB::select('password')->from('users')->where('username', '=', $username)->execute();

        foreach($result_set as $password){

            return $password['password'];

        }

    }



    public function addLogin($username){

        $query = DB::query(Database::UPDATE, 'UPDATE users SET logins=logins+1 WHERE username=:username');

        $query->parameters(array(

            ':username' => $username,

        ));

        $query->execute();

    }
}


Nun kommen wir zur Registrierung. Als erstes erstellen wir natürlich eine View um die Eingabefelder anzuzeigen:
"Applications/views/register/register.php" (im Viewsordner habe ich immer noch einen unterordner angelegt)

<html>

    <head>

        <title>Dies ist eine Registrierung</title>

    </head>

    <body>

        <h1>Registrierung</h1>

        <?php  echo Form::open(); ?>

        <ul class="errors">

            <?php foreach($valid_errors as $error){

                echo '<li>';echo $error; echo '</li>';

                }

            ?>

        </ul>

        <?php  echo Form::hidden('hidden','form_sent'); ?>

        <?php  echo Form::label('labelusername', 'Neuer Benutzername:'); ?>

        <?php  echo Form::input('username'); ?>

        <br><br>

        <?php  echo Form::label('labelpassword', 'Neues Passwort:'); ?>

        <?php  echo Form::password('password'); ?>

        <br><br>

        <?php  echo Form::label('labelemail', 'Emailadresse:'); ?>

        <?php  echo Form::input('email'); ?>

        <br /> <br />

        <?php  echo Form::submit('submit', 'registrieren'); ?>

        <?php  echo Form::close(); ?>

        <?php $content; ?>

    </body>

</html>

Und hier der dazugehörige Registrierungs-Controller:
"Application/classes/controller/register.php"

<?php defined('SYSPATH') OR die('No direct Script Access');



    Class Controller_Register extends Controller_Template {

        public $template = "register/register";



        public function action_index(){

            $this->auth = Auth::instance();

            $this->model = Model::factory('Rechner');

            $this->template->valid_errors = array();



            $post = Validation::factory($_POST)

                ->rule('username', 'not_empty')

                ->rule('password', 'not_empty')

                ->rule('email', 'not_empty');



            if(!empty($_POST['submit'])){

                if($post->check()){

                    //Userdaten in der Datenbank speichern

                    $this->model->registrieren($_POST['username'], $this->auth->hash($_POST['password']), $_POST['email']);

                    //Dann zur Loginseite weiterleiten

                    $this->request->redirect('login');

                }

                else{

                    $this->template->valid_errors = $post->errors('rechnervalid');

                }

            }

        }

    }

?>


Jetzt kommen wir zu unserer Loginview
"Application/views/login/login.php"

<html>

    <head>

        <title>Dies ist ein Kohana Testprogramm</title>

    </head>

    <body>

        

        <?php  echo Form::open(); ?>

        

        <?php  echo Form::hidden('hidden','form_sent'); ?>

        <?php  echo Form::label('labelusername', 'Benutzername:'); ?>

        <?php  echo Form::input('username'); ?>

        <br><br>

        <?php  echo Form::label('labelpassword', 'Passwort:'); ?>

        <?php  echo Form::password('password'); ?>

        <br><br>

        <?php  echo Form::submit('submit', 'einloggen'); ?>

        <?php  echo Form::close(); ?>

        <p><?php echo $message ?></p>

        <p><?php echo HTML::anchor('register', 'Hier registrieren'); ?></p> 

    </body>

</html>

Und der Login-Controller:
"Apllication/classes/controller"

<?php defined('SYSPATH') OR die('No direct Script Access');



    class Controller_Login extends Controller_Template{

      

        public $template = "login/login";





        public function action_index() 

         { 

             $this->model = Model::factory('Rechner');

             $this->session = Session::instance('cookie');

             $this->template->message = '';



             if(!empty($_POST['submit'])){

                 $success = $this->login_versuch($_POST['username'], $_POST['password']);



                 if ($success == true)

                 {

                    //Datenbankfeld "logins" hochzählen

                    $this->model->addLogin($_POST['username']);

                 }

                 else

                 {

                    $this->template->message = 'Login fehlgeschlagen';

                 }

            }

            //WEITERLEITUNG: Überprüfen ob schon eingeloggt,  wenn eingeloggt dann weiterleiten

            $username = $this->session->get('user');

            if(isset($username)){

                $this->request->redirect('rechner');

            }

         }



         public function login_versuch(){

            $inputpassword = Auth::instance()->hash($_POST['password']); //Eingegebenes Passwort in Hashformat umwandeln

            $realpassword = $this->model->getPassword($_POST['username']);



            if($inputpassword == $realpassword){

                $this->session->set('user', $_POST['username']);

                return true;

            }

            return false;

         }



         public function action_logout(){

            Session::instance('cookie')->destroy();

         }

    }

?>


So die Benutzerverwaltung müsste nun schon bestens laufen. Jetzt kommen wir zu unserem eigentlichen Rechner. Natürlich erstmal die Rechner-View
"Application/views/site.php" (ausnahmsweise ohne Unterordner könnt ihr aber machen wie ihr wollt ;))

<html>

    <head>

        <title>Dies ist ein Kohana Testprogramm</title>

    </head>

    <body>

        <h1><?php  echo $message?></h1>

        <?php  echo Form::open(); ?>

        <ul class="errors">

            <?php foreach($valid_errors as $error){

                echo '<li>';echo $error; echo '</li>';

                }

            ?>

        </ul>

        <?php  echo Form::hidden('hidden','form_sent'); ?>

        <?php  echo Form::label('labelsummand1', 'Zahl1:'); ?>

        <?php  echo Form::input('summandeins'); ?>

        <br><br>

        <?php  echo Form::label('labelsummand2', 'Zahl2:'); ?>

        <?php  echo Form::input('summandzwei'); ?>

        <br><br>

        <?php  echo Form::submit('addieren', 'addieren'); ?>

        <?php  echo Form::submit('subtrahieren', 'subtrahieren'); ?>

        <?php  echo Form::close(); ?>

        <p>Ergebnis: <?php echo $ergebnis ?></p>

        <br>

        <a href="http://localhost/kohana/ergebnistabelle">Zur Aufgabenhistorie</a>

        <a href="http://localhost/kohana/login/logout">Ausloggen</a>

    </body>

</html>

Und nun zum Controller indem u.a die Rechenlogik festgelegt wird und die eingegebenden Aufgaben in die Datenbank geschrieben werden:
"Application/classes/controller/rechner.php"

<?php defined('SYSPATH') OR die('No direct Script Access');



Class Controller_Rechner extends Controller_Template{

    

    public $template = 'site';

    public function action_index(){

        $this->template->ergebnis = NULL;

        $this->template->valid_errors = array();

        $this->model = Model::factory('Rechner');

        $this->session = Session::instance('cookie');

        //Validierung

        $post = Validation::factory($_POST)

                    ->rule('summandeins', 'not_empty')

                    ->rule('summandzwei', 'not_empty');



        if (!empty($_POST["addieren"]))

        {

            if($post->check()){

                $this->template->ergebnis = Arr::get($_POST, 'summandeins') + Arr::get($_POST, 'summandzwei');

                //Datenbankeintrag vornehmen

                $this->model->write_tasks(Arr::get($_POST, 'summandeins'), Arr::get($_POST, 'summandzwei'), $this->template->ergebnis, 'addition');

            }

            else{

                $this->template->valid_errors = $post->errors('rechnervalid');

            }

        }

        else if(!empty($_POST['subtrahieren']))

        {

            if($post->check()){

                $this->template->ergebnis = Arr::get($_POST, 'summandeins') - Arr::get($_POST, 'summandzwei');

                $this->model->write_tasks($_POST['summandeins'], $_POST['summandzwei'], $this->template->ergebnis, 'sub');

            }

            else{

                //Die Fehlermeldung ist in der Datei "rechnervalid.php" im Messages Ordner angegeben

                $this->template->valid_errors = $post->errors('rechnervalid');

            }

        }

        $this->template->message = 'eingeloggt als '.$this->session->get('user', 'leer');

    }

}


Jetzt fehlt nur noch unsere Aufgabenübersicht. Dazu erstellen wir einfach für Addition und Subtraktion einfach jeweils eine Tabelle und geben jedem Datensatz eine Checkbox, die der Benutzer anklicken kann, um diesen zu löschen.
"Application/views/ergebnistabelle/ergebnistabelle.php"

<html>

    <head>

        <title>Ergebnistabelle</title>

    </head>

    <body>

        <p>Addition:</p>

        <table>

            <tr>

                <th>Summand1</th>

                <th>Summand2</th>

                <th>Ergebnis</th>

            </tr>

            <?php

                echo Form::open();

                foreach($result_data_addition as $row){

                echo '<tr>';

                    echo '<td>';

                        echo $row['summand1'];

                    echo '</td>'; 

                    echo '<td>';

                        echo $row['summand2'];

                    echo '</td>';

                    echo '<td>';

                        echo $row['ergebnis'];

                    echo '</td>';

                    echo '<td>';

                        echo Form::checkbox('loeschen_add[]', $row['ID'], FALSE);

                    echo'</td>';

                echo '</tr>';

                }

            ?>

        </table>

            <p>Subtraktion:</p>

        <table>

            <tr>

                <th>Minuend1</th>

                <th>Minuend2</th>

                <th>Ergebnis</th>

            </tr>

            <?php

                foreach($result_data_subtraktion as $row){

                echo '<tr>';

                    echo '<td>';

                        echo $row['summand1'];

                    echo '</td>'; 

                    echo '<td>';

                        echo $row['summand2']; 

                    echo '</td>';

                    echo '<td>';

                        echo $row['ergebnis'];

                    echo '</td>';

                    echo '<td>';

                        echo Form::checkbox('loeschen_sub[]', $row['ID'], FALSE);

                    echo'</td>';

                echo '</tr>';

                }

            ?>

        </table>

        <br />

        <?php echo Form::submit('ausfuehren', 'ausgewählte löschen');

        echo Form::close();

        echo HTML::anchor('rechner', 'Zur Startseite'); ?><br /><br />

    </body>

</html>
 

Und der letzte Controller für heute:
"Application/classes/controller/ergebnistabelle.php"

<?php defined('SYSPATH') OR die('No direct Script Access');



Class Controller_Ergebnistabelle extends Controller_Template{

    public $template = 'ergebnistabelle/ergebnistabelle';

    

    public function action_index(){

        $this->model = Model::factory('Rechner');

        //Wenn Löschbutton geklickt dann Datensätze löschen

        $this->daten_loeschen();

        //Tabellen füllen

        $this->template->result_data_addition = $this->model->daten_auslesen('addition');

        $this->template->result_data_subtraktion = $this->model->daten_auslesen('sub');

    }



    /*Für die Löschung sämtlicher Datensätze zuständig. Jeder Datensatz bekommt in der View eine eigene Checkbox,

     die als Value die ID in aus der jeweiligen Datenbanktabelle hat*/

    private function daten_loeschen(){

        if(!empty($_POST["ausfuehren"]) && Arr::get($_POST, 'loeschen_add') != NULL){

            foreach($_POST['loeschen_add'] as $loeschbar){

                $this->model->delete_tasks($loeschbar, 'addition');

            }

        }

        if(!empty($_POST["ausfuehren"]) && Arr::get($_POST, 'loeschen_sub') != NULL){

            foreach($_POST['loeschen_sub'] as $loeschbar){

                $this->model->delete_tasks($loeschbar, 'sub');

            }

        }

    }

}

Fertig!!!

Wenn ihr jetzt in eure Adressleiste "/localhost/kohana" eingebt müsstet ihr zum Logininterface kommen. Dort findet ihr den Link zur Registrierung. Wenn ihr euch Registriert habt gelangt ihr automatisch zum Logininterface zurück und nach dem Login kommt ihr von dort aus direkt zum Rechner.

Dieses kleine Programm ist dafür gedacht einfach ein bisschen  damit zu experimentieren und verschiedene Dinge auszuprobieren. Ich empfehle euch die Quelltexte in eurem Quelltexteditor zu kopieren denn durch die dortige Syntaxhervorhebung ist alles wesentlich leichter zu verstehen.
Also viel Spaß dabei!

5 Kommentare:

  1. Dieser Kommentar wurde vom Autor entfernt.

    AntwortenLöschen
  2. Also einige dinge hast du eigentlich nicht so umgesetzt wie die gedacht sind.

    Erstens sollte man nicht mit $_POST arbeiten sondern mit $this->request->post()
    Zweitens statt !empty sollte man die Validator klasse verwenden und dort regeln festlegen
    Drittens das Login erlaubt nicht mehrere Rollen, ORM Modul hat das bereits drin implementiert.

    Vielleicht hilft dir mein Code etwas weiter
    https://github.com/BlackScorp/opentribes/blob/develop/classes/Game/Controller/Auth.php

    Viele Grüße

    AntwortenLöschen
    Antworten
    1. Hallo,
      vielen Dank für dein Feedback werde ich überarbeiten. Welchen Vorteil hat es denn genau wenn ich statt $_POST mit $this->request->post() arbeite?
      Vg

      Löschen
    2. Naja Vorteile hat es keine, jedoch wiedersprechen $_POST ,$_GET etc dem OOP chema, $this->request->post kapselt die superglobalen in eine methode, außerdem benutzt du dann solche konstrukte wie Arr::get($_POST) das ist bereits in request post drin.

      Will sagen es geht um einheitlichen code und superglobale sind böse :D

      Löschen
    3. Super Arbeit, leider erscheint bei mir:

      ErrorException [ Parse Error ]: syntax error, unexpected end of file, expecting function (T_FUNCTION)
      APPPATH\classes\Model\rechner.php [ 69 ]
      64
      65 ));
      66
      67 $query->execute();
      68
      69 }
      {PHP internal call} » Kohana_Core::shutdown_handler()

      Und ich verstehe die Bedeutung noch nicht ganz.

      Löschen