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