PHP – Prosty framewework – Część 1

PHP – Prosty framewework – Część 1

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

<?php

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: Products, Categories, Pages, 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.
<?php 

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.

<?php 

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.

<?php

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

Komentarze

Brak komentarzy. Może dodasz pierwszy?

Chcę dodać komentarz