Powrót do pracy

części pierwszej napisaliśmy podstawowe elementy frameworka. Pora zająć się rozbudową naszego projektu. Do efektywnej pracy potrzebne są dodatkowe moduły zapewniające obsługę baz danych, sesji czy operacji wejścia. Poniżej opiszę je pokrótce.

Dodatkowe moduły

system/libraries/database.php
Moduł zapewniający obsługę baz danych. Zwrócone dane przekazywane są w postaci obiektów.

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

class database_core
{
    protected $config;

    public static function & instance()
    {
        static $instance;
        if(!isset($instance))
        {
            $instance = new database();
        }
        return $instance;
    }

    protected function __construct() {}

    public function connect($config=array())
    {
        $this->config=(object)$config;
        mysql_connect($this->config->host, $this->config->user, $this->config->password);
        @mysql_select_db($this->config->dbname);
        mysql_query("SET NAMES 'utf8'");
    }

    public function query($sql = '')
    {
        $result = mysql_query($sql);
        if(mysql_num_rows($result) > 0)
        {
            $temp = array();
            while($r = mysql_fetch_object($result))
              $temp[]=$r;
            return $temp;
        }
        else false;
    }

    public function execute($sql = '')
    {
        mysql_query($sql);
    }

    public function last_id()
    {
        return mysql_insert_id();
    }
}

system/libraries/session.php
Moduł tworzy sesję. Ponadto dostarcza metody umożliwiające zapisywanie, pobieranie oraz usuwanie wartości.

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

class session_core
{
    public static function & instance()
    {
        static $instance;
        if(!isset($instance))
        {
            $instance = new session();
            session_start();
        }
        return $instance;
    }
 
    protected function __construct() { }
   
    public function set($key,$value)
    {
        $_SESSION[$key]=$value;
    }

    public function get($key,$default=false)
    {
        if (isset($_SESSION[$key]))
            return $_SESSION[$key];
        else
            return $default;
    }

    public function delete($key)
    {
      unset($_SESSION[$key]);
    }
}

system/libraries/input.php
Moduł wejścia. Zapewnia dostęp do zmiennych $_POST, $_GET. Warto go wzbogacić o funkcje zarządzające bezpieczeństwem.

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

class input_core
{
    public static function & instance()
    {
        static $instance;
        if(!isset($instance))
        {
            $instance = new input();
        }
        return $instance;
    }

    public function post($key)
    {
        return $_POST[$key];
    }

    public function get($key)
    {
        return $_GET[$key];
    }
}


Status

Mamy do dyspozycji już wystarczająco kompletne narzędzie by wykonać całą stronę internetową. Zgodnie z tradycją udostępniłem przykład w załączniku. Dalszych inspiracji w rozbudowie frameworka polecam poszukiwać również w innych projektach. Napiszę na ten temat coś jeszcze jeśli spotkam się z zainteresowaniem.

Galeria

Załączniki

Przykładowa strona internetowa

Wstęp

Oto pierwsza część z serii artykułów poświęconych budowie własnego frameworka. Materiał przygotowałem dla wszystkich osób, które rozpoczęły już przygodę z programowaniem w PHP ale brakuje im jeszcze wyrobionego warsztatu pracy. Tworząc skrypt zaczerpnąłem inspiracji z projektu Kohana. Teorię starałem się ograniczyć do minimum. Uznałem jednak za stosowne wspomnieć o wzorcach projektowych, które stanowią tutaj niemal najważniejszy element. W części pierwszej napiszemy jądro naszego systemu. Życzę miłej lektury.

Wzorce projektowe

Wzorce projektowe (ang. design pattern) opisują uniwersalne rozwiązania często spotykanych problemów projektowych. Ich użycie upraszcza tworzenie kodu źródłowego oraz poprawia jego czytelność. Wzorcom towarzyszy programowanie obiektowe.

Singleton – jeden z najpopularniejszych wzorców projektowych. Jego zadaniem jest ograniczenie możliwości tworzenia obiektu do jednej instancji. Singleton posiada pole statyczne będące wskaźnikiem na niego samego.

class Singleton
{
    private $data=1;

    public static function & instance()
    {
        static $instance;
        if(!isset($instance))
        {
            $instance = new Singleton;
        }
        return $instance;
    }

    protected function __construct() {}

    public function set($data)
    {
        $this->data=$data;
    }

    public function get()
    {
        return $this->data;
    }
}

$A = Singleton::instance();
$A->set(2);
echo $A->get();
echo ' ';
$B = Singleton::instance();
echo $B->get();
//W wyniku działania skryptu otrzymamy: "2 2".

MVC (Model-View-Controller) – wzorzec projektowy z podziałem zadań, w którym:

  • Model zajmuje się pracą z danymi. Zawiera metody, które upraszczają pobranie (przetwarzanie) danych. Przykładowo model Users może mieć metodę get zwracającą listę użytkowników. Programista nie musi przy tym znać zapytań użytych w modelu tylko obowiązujące w nim metody. Inne przykładowe modele: ProductsCategoriesPages,News.
  • Widok zajmuje się (tylko i wyłącznie) wyświetlaniem danych.
  • Kontroler jest obiektem zarządzającym całą aplikacją. Przykładowo: do pokazania news-ów kontroler pobiera dane z modelu oraz przekazuje je odpowiedniemu widokowi w celu wyświetlenia.
class News_Controller extends Controller
{
    public function index()
    {
        $news = new News_Model();
        $view = new View('news');
        $view->news = $news->get();
        $view->render();
    }
}


Piszemy nasz własny framework

W budowanym przez nas frameworku oddzielimy warstwę aplikacji od warstwy systemowej. Zachowamy dzięki temu czytelny podział. Struktura plików i katalogów wygląda następująco:

  • framework
    • application
    • system
      • core
        • core.php
        • uri.php
      • libraries
        • model.php
        • view.php
        • controller.php
    • index.php
    • .htaccess

index.php
Ładuje jądro naszego systemu.

require 'system/core/core.php';

.htaccess
Plik konfiguracyjny serwera Apache.

RewriteEngine On
RewriteBase /framework/
RewriteCond $1 ^(application|system)
RewriteRule ^(.*)$ index.php/access_denied/$1 [PT,L]
RewriteCond $1 ^(index\.php|data)
RewriteRule ^(.*)$ - [PT,L]
RewriteRule ^(.*)$ index.php/$1 [PT,L]

system/core/core.php
Jądro frameworka.

<?php
//definiujemy stałe
define('SYSPATH', 'system');
define('APPPATH', 'application');
define('DEFAULT_CONTROLLER', 'welcome');
define('DEFAULT_ACTION', 'index');

//final - klasa po której nie można dziedziczyć
final class FRAMEWORK
{
    public static $controller;

    public static function & instance()
    {
        static $instance;
        if(!isset($instance))
        {
            $instance = new UCEARN;
        }
        return $instance;
    }

    protected function __construct() {}

    public static function setup()
    {
        static $run;
        if ($run === TRUE) return;
        require SYSPATH.'/core/uri.php';
        //Uruchamiamy moduł URI
        URI::setup();
        //Zarejestrowanie autoloadera
        spl_autoload_register(array('FRAMEWORK', 'autoload'));
        $controller = URI::getController().'_controller';
        //utworzenie kontrolera
        self::$controller = new $controller();
        // tworzymy obiekt refleksji klasy
        $class  = new ReflectionClass(self::$controller);
        //pobieramy metodę
        $method = $class->getMethod(URI::getAction());
        //wykonanie metody z argumentami URI
        $method->invokeArgs(self::$controller,URI::getArguments());
        $run = TRUE;
    }

    private static function loadlibrary($class)
    {
        //wczytujemy bibliotekę
        require_once SYSPATH.'/libraries/'.$class.'.php';
        //rozszerzamy ją jeśli jest to możliwe, w przeciwnym wypadku stosujemy hack
        if (file_exists(URI::getPath().'/libraries/'.$class.'.php'))
            require_once URI::getPath().'/libraries/'.$class.'.php';
        else
            eval($extension = 'class '.$class.' extends '.$class.'_core { }');
    }

    public static function autoload($class)
    {
        //pobieramy typ obiektu
        if (($type = strrpos($class, '_')) !== FALSE)
        {
            $type = substr($class, $type + 1);
        }
        else
            $type = 'default';
        //dla danego typu
        switch($type)
        {
            case 'controller':
                self::loadlibrary('controller');
                $file = substr($class, 0, -11);
                if (file_exists(URI::getPath().'/controllers/'.$file.'.php'))
                    require_once URI::getPath().'/controllers/'.$file.'.php';
                else
                {
                    header('HTTP/1.1 404 File Not Found');
                    self::exception_handler(NULL,'[404] File not found');
                    exit;
                }
                break;
            case 'model':
                require_once SYSPATH.'/libraries/model.php';
                $file = substr($class, 0, -6);
                require_once URI::getPath().'/models/'.$file.'.php';
                break;
            case 'default':
                self::loadlibrary($class);
                break;
        }
    }
}
//uruchamiamy moduł
FRAMEWORK::setup();

system/core/uri.php
Moduł rozbijający adres strony na części: kontroler, akcja, argumenty.

final class URI
{
    protected static $pathinfo;
    protected static $array;
    protected static $controller;
    protected static $action;
    protected static $arguments;
    protected static $path;

    public static function & instance()
    {
        static $instance;
        if(!isset($instance))
        {
            $instance = new URI;
        }
        return $instance;
    }

    protected function __construct() { }

    public static function setup()
    {
        static $run;
        if ($run === TRUE) return;
        self::$pathinfo = $_SERVER['PATH_INFO'];
        self::$pathinfo = preg_replace('#\.[\s./]*/#', '', self::$pathinfo);
        self::$pathinfo = trim(self::$pathinfo, '/');
        //kontroler
        self::$array = explode('/',self::$pathinfo);
        if (!empty(self::$array[0]))
        {
            $temp = array_shift(self::$array);
            self::$controller = $temp;
            self::$path = APPPATH;
        }
        else
        {
            self::$controller = DEFAULT_CONTROLLER;
            self::$path = APPPATH;
        }
        //akcja
        if (!empty(self::$array[0]))
            self::$action = array_shift(self::$array);
        else
            self::$action = DEFAULT_ACTION;
        //argumenty
        self::$arguments = self::$array;
    }

    public static function getController()
    {
        return self::$controller;
    }

    public static function getAction()
    {
        return self::$action;
    }

    public static function getArguments()
    {
        return self::$arguments;
    }

    public static function getPath()
    {
        return self::$path;
    }
}

system/libraries/controller.php
Klasa kontrolera.

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

class controller_core
{
    public function __construct()
    {
        $this->uri = URI::instance();
    }
}

system/libraries/model.php
Klasa modelu.

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

class model_core
{
    public function __construct()
    {
    }
}

system/libraries/view.php
Klasa widoku.

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

class view_core
{
    protected $filename = FALSE;
    protected $data = array();

    public function __construct($name = NULL)
    {
        if (is_string($name) AND $name !== '')
        {
            $this->filename = $name;
        }
    }

    public function __toString()
    {
        return $this->render();
    }

    public function __set($key, $value)
    {
        if ( ! isset($this->$key))
        {
            $this->data[$key] = $value;
        }
    }

    public function __get($key)
    {
        if (isset($this->data[$key]))
            return $this->data[$key];
        if (isset($this->$key))
            return $this->$key;
    }

    protected function load_view($filename, $input_data)
    {
        if ($filename == '')
            return;
        //buforowanie wyjścia
        ob_start();
        //importowanie zmiennych z tablicy do bieżącej tablicy symboli
        extract($input_data, EXTR_SKIP);
        include $filename;
        //zwróć bufor
        return ob_get_clean();
    }

    public function render($print=FALSE)
    {
        $output = $this->load_view(URI::getPath().'/views/'.$this->filename.'.php', $this->data);
        if ($print==TRUE)
        {
            header('Content-Type: text/html; charset=UTF-8');
            echo $output;
            return;
        }
        return $output;
    }
}


To… dopiero początek

Dysponujemy już podstawowymi elementami frameworka. Za ich sprawą możemy wykonać naszą pierwszą aplikację (udostępniłem ją w archiwum). Do poważnej pracy potrzebujemy jednak czegoś jeszcze – dodatkowych modułów. Już wkrótce je napiszemy i dołączymy do projektu. Zapraszam.

Załączniki

Framework