Pull to refresh

Comments 67

для своей хоумпаги писал что-то подобное, только там было 2 возможных модели урлов — /controller/action и произвольные роуты, ну и немного другая модель переменных, без всяких ассертов, сразу в маршруте пишем регулярку.
Примерно так:
    require_once('../framework/Main.php');
    require_once('../application/config/config.php');

    $routes = array(
        '/blog/get/(\d+)' => array('controller' => 'blog', 'action' => 'get', 'params' => array(1 => 'id')),
    );

    Main::setRoot(dirname(__FILE__) . '/../');
    Main::setRoutes($routes);

    arr2obj($config);
    Registry::set('config', $config);

    try
    {
        Main::run();
    } catch (Exception $e)
    {
        header("HTTP/1.0 404 Not Found");
        include('404.html');
    }

/updates/get попытается вызвать updatesContoller->getAction
/blog/get/123 blogController->getAction, и сиглтон Request будет содержать параметр id = 123.

А в ваш код неплохо было бы добавить autoload.
    private function __construct()
    {
        spl_autoload_register(array($this, 'loader'));
    }

    private function loader($class)
    {
        $incPath = get_include_path();
        $end_ptr = strlen($class) - strlen('controller');
        if ($end_ptr != 0 && strcasecmp(substr($class, $end_ptr), 'controller') === 0)
            set_include_path($this->_root . $this->_controllerPath . PATH_SEPARATOR . $incPath);
        else
            set_include_path($this->_root . $this->_modelsPath . PATH_SEPARATOR . 
                             $this->_root . $this->_libraryPath . PATH_SEPARATOR . $incPath);
        $filename = $class . '.php';
        $f = fopen($filename, 'r', true);
        if (!$f)
        {
            set_include_path($incPath);
            throw new Exception('Class not found.');
        }
        fclose($f);
        include $filename;
        set_include_path($incPath);
    }
Чтоб /controller/action/smth/ приравнять к /controller/action/smth можно же просто trim($path, '/') :)
В моем варианте если кто-то введет /controller/////////action/////////smth/, то оно спокойно обработает это так же, как и /controller/action/smth :-)
Я в том плане что это можно делать изначально, чтоб тупо унифицировать урлы))
К тому же, если кто введет /controller/////////action/////////smth/, надо этого кого-то послать на… главную с рекомендацией воспользоваться меню.

+1 за унификацию урлов. Плохо, если по 500 разным урлам у вас будет одна страница, еще и с ответом 200 и без canonical.
>К тому же, если кто введет /controller/////////action/////////smth/, надо этого кого-то послать на… главную с рекомендацией воспользоваться меню.

Одна из причин использования «красивых» урлов это как раз возможность использовать адресную строку для навигации внутри приложения.
То, что по структуре ЧПУ можно проследить структуру сайта — понятно, но я не о том. Я говорю о том, что /controller/action и /controller//action — два разных урла.
Если брать во внимание ПС — некоторые время у вас будет 100% одинаковый контент на разных страницах (урлах). А этих вещей надо избегать. Один из вариантов — canonical, но вообще — лучше не доводить до такого.

1 урл = 1 страница
У меня не будет :) Второй у меня выдаст 404. А вот с /smth/ и /smth (или / и /index.php) проблемка: с одной стороны формально, как вы правильно заметили, это разные страницы, с другой — фактически это внутренний редирект. По хорошему надо делать внешний редирект, с другой стороны — это лишние проверки/действия не дающие, имхо, какого либо профита для пользователя, а время ответа увеличивающие.
На счет времени ответа — согласен, но, как мне кажется, это экономия на спичках. Проверки у вас в начале работы всех скриптов идут, ресурсов (а вместе с ними и времени) ушло мало. Вы же не будете бить тревогу лишь потому, что время загрузки увеличилось на 0.001с, я надеюсь.
Но, согласен, можно обойтись и без этого, тут-то как раз canonical и решает.
Проверки это одно, но для внешнего редиректа это плюс, как минимум, один пинг.
Если уж сильно хочется сделать создание объекта Request в одну строчку, то лучше не в функцию оборачивать, а делать фабрику. Вместо:
function Request($method, $path, $callback)
{
	return new Request($method, $path, $callback);
}

делать

class Request {

    public static function factory($method, $path, $callback)
    {
	return new self($method, $path, $callback);
    }

    ...

}

Спасибо, просто не знал о подобном.
Еще я не понял зачем в конструкторе Request нужна строчка:
global $application;

И зачем свойства класса объявлены как public? Лучше используй getters/setters. __construct в классе Application вообще какой-то страшный. Ты, наверное пытался реализовать Singleton, но он реализуется совсем не так.
Да, мой косяк. Синглтон поправил, а $application удалил — оно осталось, когда я прикидывал варианты реализации. А для чего нужно использовать здесь getters/setters?
А зачем у тебя свойства класса объявлены публичными? Сокрытие свойств не реализовано. А если я в коде напишу $request->callback = 'ololo'. Будет fatal error. Лучше ведь сделать getCallback/setCallback, в котором проверять is_callable и бросать исключение — его по крайней мере проще отлавливать.
В таком случае лучше делать проверку прямо перед вызовом call_user_func_array. Потому что если делать ее при присвоении, будет неправильно работать, если кэллбек создать с помощью create_function дальше по коду.
Может и так, но в любом случае свойства класса не следует делать публичными.
Кроме того, конкретно этот момент я бы сделал так:

if ($callback  !== null) {
    if (!is_callable($callback)) {
        throw new Exception('Not callable');
    }
}

Тогда если параметр callback не передан, то ничего выполняться не будет.
Singleton, совершенно определенно, работает неправильно. Вызов Application::getInstance() не работает, если не вызван new Application перед этим
Вот же ж жесть. Я только что открыл для себя новое в ПХП. Оказывается, что приватные методы класса можно вызывать из уже созданного объекта, если вызов происходит в контексте класса (статический метод). Автор, признавайся, откуда это узнал и главное — зачем так сделал?

class Application 
{
        private function i_run() 
        { 
            ....
        } 

        public static function run() 
        { 
            return Application::getInstance()->i_run(); 
        } 
}
Открыл методом тыка. ООП в PHP сам плоховато знаю :( Сделал так исключительно для красоты, чтобы вместо длинной строчки:
Application::getInstance()->run();
можно было написать короткую:
Application::run();
Ну, как бы такое поведение ПХП совсем неочевидно, и не факт, что это не баг, и оно сохранится в следующих версиях.
Вообще, как насчет залить на github? Так проще смотреть.
Если не ошибаюсь, здесь мы получаем нормальный такой инстанс синглтона, на котором потом вызываем его метод. Меня тоже удивило такое решение, но мне оно кажется далеко не багованным — ведь статический метод доступен всегда, инстанс создать можно когда угодно, значит и метод не статический на нем вызвать можно. А выглядит хорошо. Этакая вещь в себе — сам создался, сам себя задействовал =)
Ага, нашел:

Статические методы не определяются через переменную $this, поскольку они не должны быть ограничены определенным объектом.

То есть статические члены изначально не ограничены определенным классом => могут использоваться и таким способом.

P. S.: Выше абзац там тоже интересное решение описывает. Учитывая, что подобные решения вводятся, оно сохранится в следующих версиях (скорее всего).

PHP.SU
как бы вроде стандартное решение, особенно в синглтонах.
только удобнее использовать не Application::getInstance(), а self::getInstance(), тогда не забудем поменять имя класса при рефакторинге возможном :)
Почитайте про scope и наследование в PHP там все описано подробно про доступ к свойствам/методам из потомков и прочего. В общем… матчасть!
This feature is available since PHP 5.3.0.

У меня на хостинге 5.2, поэтому использовать не получится :(
Охохо.
1. Что делать при количестве возможных активити Ю 10(100(1000(и т.д.)))?
2. Как разделить его на функциональные части (user/shop/updates...) — что по-идее должно быть контроллером
3. Добавка нового роута да и сам роутинг сомнителен

П.С. Такое впечатление, что Вы смотрели ещё и на Kohana :)
1. Так это ведь мини-фреймворк. Если нужно делать что-то большое, то и взять для этого нужно что-то полноценное.
2. Request('get', '/user/{username}/profile', UserProfile)->assert('username', '|^[\w\d]+$|');
Request('get', '/shop/{id}/rating', ShopItemRating)->assert('id', '|^\d+$|');

3. Так я и не утверждаю, что надо писать именно так. Это просто пример ;)
Если действительно ставилась цель написать вызов фреймворка в одну строчку, то вместо неочевидных фишечек с auto_prepend_file и auto_append_file лучше было бы добавить в Application::run() параметр Request.
Тогда вызов бы свелся к следующему коду:

Application::run(Request::factory('/user/{id}', UserProfile)->assert('|^\d+$|')->run())

Конечно для этого нужно будет изменить метод run класса Request и возвращать из него this.
извините, а «callback» читается «кэллбек»?? я просто все время думал «коллбек»
UFO just landed and posted this here
Советую перед тем, как начинать кодить, сделать себе список задач, корыте будет решать Ваш фреймворк. Вот все что решает он сейчас — это маршрутизация при вызове разных урлов в разные обработчики. Все это же пишется ровно в 3 раза меньше кода без придумывания лишнего. Но задайтесь вопросами посложнее: а можно ли наследовать и переопределять функциональность и от одного обработчика в другой. То есть, чтобы вложенные урлы могли наследовать часть объектов и шаблонов от родительского урла и переопределять, когда это нужно, а потом, в дальнейшем ветвлении урлов, опять возвращаться к функционалу предка (inherited). Наличие Application, возможно продиктовано Вашим Дэлфийским прошлым, так? Тогда Вам не нужно объяснять, что такое inherited, я надеюсь. Потом еще фреймворковые задачи для развития идеи: доступ к базам, сессии и куки, кеширование, и т.д. Я понимаю, что оно у Вас называется «мини-двиг» и это хорошо, но начинайте проектирование систем с требований.
Я ориентировался на Silex и писал это просто для примера (Application пришло оттуда же, а не с дельфей). Специализация узкая, но если делать нормальный фреймворк/cms, то это будет лишь один из модулей. Доступ к базам и прочее будут реализовываться другими модулями. Собственно, сейчас еще попутно пишу для себя небольшую cms со всем этим и в этом мне здорово помогает хабр — отсюда я узнал о существовании PDO, работе с постоянными соединениями и многое другое.
Можете направить где посмотреть реализацию или почитать про наследования по УРЛ. Или можете предоставить свой пример.
Интересно кто как это делает.
Заранее спасибо.
* в первом предложении вопросительный знак потерялся.
Выслал в личку бета версию. Если еще кто-то хочет бетатестить, то могу выслать. Через пару недель выложу в свободный доступ, когда доки подготовлю.
Даже так? Спасибо!
Делал подобный велосипед, только по пути вида

/controller/action/param1/param2/.../paramN

запускается метод %имя_действия%Action класса %имя_контроллера%Controller с массивом параметров.

Это, конечно, топорно, но большинство простых задач охватывает.
У себя сделал довольно гибкий метод:
// Parse query
// host/dir/subdir/../subdirX/?param1=1&param2=2&...&paramX=x
// $url_array[]
// $url_array[0] = "dir";
// $url_array[1] = "subdir";
// ..
// $url_array[X] = "subdirX";
// $_GET["param1"] = "1";
// $_GET["param2"] = "2";
// ..
// $_GET["paramX"] = "X";
$url = "http://localhost".$_SERVER['REQUEST_URI'];
$temp_url = parse_url($url);
$dirs = explode('/', $temp_url['path']);
isset($temp_url['query']) && parse_str($temp_url['query'], $_GET);


А уже дальше логика зависит от задач. Из плюсов — можно делать любое ЧПУ и комбинировать со стандартными правилами формирования переменных в GET.
Вопрос: а URL вида /hello_{name} оно распознает? Или параметр обязательно должен быть окружен косыми чертами? И как с этим обстоит дело в Silex?
Увы, пока не распознает и параметр должен быть окружен косыми чертами. Как в Silex с этим — не знаю.
Посмотрел — нет, Silex такого тоже не понимает. Наверное, и правильно :)
Мне кажется, что чпу и роуты нужно делать не в первую очередь.

Лучше сделать удобную систему ввода-вывода с валидацией единую для всего приложения.

Дальше делаем на пример:
class Meat implements iListView
он и любой другой класс с интерфейсом может быть представлен как список.

Делаем контроллер для списка
class ListView()

передаем ему объект с интерфейсом, делаем привязку переменных, названия переменных, тип списка

$ListView->option('type', 'ul');
$ListView->with_var(array(a => 'var a'));

Основной вывод это или текст или список или таблица. Прийдется реальзовать (или наследовать) несколько методов, но зато будет меньше писать потом.
Простите, а — смысл?
Каждый первый фреймворк реализует MVC,
Но их все равно пишут, потому что кроме MVC у каждого их них своя особенная, «самая лучшая» концепция.
И это хотя бы можно понять.
А здесь?
Здесь — просто для примера, с объяснением принципа работы. Я ведь не утверждаю, что у меня оно особенное или лучшее.
Зачем вам callback для array_filter? Чем вам плох array_filter($array)? Более того, откуда у вас будет false в $array после explode()?
Ту функцию просто скопировал из другого исходника (сейчас тут поправил, спасибо за заметку). array_filter без подобного кэллбека плох тем, что оставляет те элементы массива, значение которых после приведения к типу bool не равно false. Однако по правилам преобразования в булевый тип строки "" и «0». Так что если не написать кэллбек, будет ошибка при разборе пути "/some/0/info".
Ох уж эти велосипедисты… Увидел и как маленький «хочу такое же». Не лучше ли «увидел что-то и улучшил это»?
Порой лучше написать свой велосипед, чем допиливать чужой.

Труъ кодер не будет читать Войну и Мир. Проще новую написать, чем разбираться в 4 Мб чужого кода. © Баш
Форкать и/или патчить чужой проект не самая интересная задача, особенно если тебе нужно 10% функциональности «оригинала», которые решают 10% твоих задач.
Конечно, кто же спорит то… Вот только если он решает 10% твоих задач + 10 мегабайт ненужного кода. Но тут небольшое НО.
1. Мотивация была выражена словами: «Недавно, прочитав про мини-фреймворк Silex, я подумал: а что в нем сложного? Попробовал написать нечто подобное и получилось довольно легко.». Т.е. это не решение задачи, а просто написать свой велосипед и потом с ним бороться.
2. Пых тормозит в развитии именно из-за того, что тут много велосипедистов и нет ничего более менее де факто. Возьмите Java, там под каждый чих есть фреймворк, 1 — 2, а не 100500. Возьмите Ruby. Что касается веба, то почти все проекты поднимаются на рельсах. Люди пишут плагины и выкладывают. Возьмите JS, основная масса давно пересела на JQuery и вместо своих велосипедов стараются просто написать плагин и выложить. Давайте посмотрим на PHP, что мы видим… ZF, symfony, kahona, CI и пару миллионов велосипедов. Много ли кто знает плагинов для них?
1. Для аналогичных своих действий могу предложить следующие мотивации на выбор или в комплексе:
— лень разбираться с чужим кодом, который не находится под моим контролем, а значит может уйти куда-то куда мне не нужно, а куда нужно не уйти и, как следствие, усилия по изучению и использованию будут потрачены зря (показательна ситуации c Silex+Doctrine или просто Symfony 2 и 1.x — человек прислал push реквест на расширение для ORM и DBAL — приняли только DBAL, ORM не «sylex-way», а Symfony2 несовместима с Symfony 1.x), в общем Not Invented Here в чистом виде.
— заведомый оверхид чужого кода, если все его возможности не использую. Возможно это можно считать преждевременной оптимизацией, а возможно нет — гвозди микроскопом забивать тоже как-то не тру.

2. Отчасти согласен, но, во-первых, сам PHP для веба де-факто находится на уровне мини-фреймворков или библиотек для языков изначально общего назначения — средства абстрагирования HTTP являются средствами языка, а не сторонних продуктов или даже стандартных библиотек — грубо говоря, PHP это что-то вроде Ruby+Rack. Для создания типичных приложений не нужно писать или использовать чужой код, абстрагирующий взаимодействие с веб-сервером, а, тем более, с сетью. Для других языков нужно или писать такой код (что больше относится к системному, а не веб-программированию — сокеты, треды/потоки и т. п.), или использовать чужой, после чего использовать ещё кусок чужого уже психологически проще — одна внешняя зависимость или две, кроме собственно языка, уже не так принципиально. А если ещё вспомнить, что и шаблонизаторы для других языков если не необходимы, то крайне желательны, то две или три зависимости ещё менее важно. Плюс менеджеры пакетов, говорят, намного удобнее и эти зависимости контролировать проще.

Во-вторых, почему-то для PHP нет лёгких (и в смысле ресурсоемкости, и в смысле изучения/использования), гибких и расширяемых/конфигурируемых full-stack фреймворков. Как-то или монстры, регламентирующие всё и вся с жёсткими зависимостями и большим оверхидом для простых приложений, или минимальная функциональность, которую сначала нужно расширить до привычного уровня, а лишь потом можно начинать писать что-то сложнее «Hello world». Золотой середины, с которой можно было бы и быстро сделать прототип, и развить его до легкомасштабируемого приложения, обслуживающего миллионы клиентов в сутки я не знаю. Возможно это свойства языка и/или его единственной (практически) реализации, раз даже автор многообещающего, но заведомо громоздкого Symfony2, не закончив работу с ним, создаёт второй, на этот раз относительно легковесный, микро-фреймворк Sylex, основанный на компонентах первого.

кэллбеков -> коллбэков

хорошо хоть не калл…
Кстати, вместо той мороки удалению пустых частей массивов, можно было вместо explode() воспользоваться preg_split с флагом PREG_SPLIT_NO_EMPTY.
Интересно, конечно.
Но Вы же понимаете, что Silex все-таки сложнее и элегантней?:)
Конечно. У меня его простенькое подобие, написанное для примера :-)
как учебное пособие HOWTO — то что надо.
не хватает проверки на «отсутствия класса» и «отсутствия метода».
к сожалению практически не увидел, как используются исключения.

Для имплементации класса советую все же использовать Reflection а не return new Request($method, $path, $callback); Это неопходимо при использовании акселератора. Если вы его не используете и вам плевать на производительность, то можете ни чего не поправлять.

Данный пример описывает упрощеннную работу контроллера. Остается сделать развернутое описание View и Model: Придумываем красивое название диреректорий и правила, где что лежит. На основании этого описываем правила для автолоадера.

Не забываем отделять код, данные и представление.

А так в целом все даже очень не плохо.
if (!class_exists('Application'))
{ ...
class Request {...}

class Application {...}

Application::init();
}else
Application::run();

жуткий Хардкод!!!
Кто предложит вариант получше?
Пардон, я сам ООП в PHP знаю плоховато и поэтому написал подобное. Если объясните, как сделать лучше, то сделаю.
чуть позже объясню обязательно,
просто интересно как думает большинство.
к сожалению пост исчерпал потенциал, большшинство не думает ничего :)

1) использование статики — ни есть хороший тон ООП
статику надо использовать в крайних случаях. Было время, статику использовали как немспейсы. Это время уже прошло.

2) используем автолоадер, писалось об этом ранее

3) Данный код должен выглядить как-то так
01  $app = new Application( );
02  if ( $app->isInit() )
03       $app->run();
04  else
05       $app->init( new Request());

При инициализации должны в Приложение передаться все внешние данные, будь то конфиги, коннекции, запросы, классы мемкеша и пр… А лучше это делать и в самом методе инициализации.
строка 5 может быть такой: $app->init(); а сам метод таким
public function init() {
    $this->request = new Request();

    $this->config = new Config();
    $this->config->load('main');

    $this->db = new Db( $this->config->db );
// и тд
}


Далее, делаем так, чтоб инициализация была всегда, а если что-то по какой-то случайности не проинициализировалось ранее — то используем «ленивую модель », т.е строки 2-5 заменяем на один метод: $app->run();, а в методе run() вызываем private function init();
public function run() {
    $this->init();
...
}

private function init() {
// используем ленивую проверку
    if (! $thid->db)
        $this->db=new Db( $this->config->db );
}

В общем должно выглядеть как-то так…

если что не очень понятно
то задавай вопросы — не стесняйся

вот пример моего фреймворка github.com/akalend/quickly
приблизительно к такой или похожей OOП структуре надо стремиться.

Only those users with full accounts are able to leave comments. Log in, please.