You are viewing [info]darkair's journal


Давно хотел написать серию постов "как сделать framework" своими руками.
Я не преследую цели написать крутой и быстрый код, скорее здесь будет описан мой личный опыт обкатки и внедрения шаблонизаторов, orm и прочих технологий.
Весь приведенный код будет работать в классах. Приводить я буду только куски кода, непосредственно отражающие суть вопроса.

Итак флешбэк, часть 1

1. Планируем архитектуру
2. Выбираем ORM
3. Решаем вопросы обмена данными

Все началось с идеи сделать php-основу для упрощения разработки сайтов и игр, а заодно и попробовать изнутри основные технологии. Для теста я решил написать простенькую игру на базе комнат (большинство логических игр, где игроки создают "комнаты" для игры и, набирая противников, запускают в "комнатах" изолированные игровые сессии).
Игра будет работать в браузере, поэтому использоваться будет только PHP + JavaScript с визуализацией на сервере.

Первым делом я стал выбрать архитектурное решение.
Сперва я разделил проект на три различных модуля.

1. Клиент - часть программы не относящаяся непосредственно к фреймворку, выполняющая функции отправки и приема пакетов, визуализации, работы с пользователем.
2. Проектно-зависимая прослойка на сервере. По сути это мост между конкретным проектом и модулем API. Основные задачи: 
    - приемка пакетов от клиента;
    - общение с независимым модулем API (см. ниже);
    - формирование и отправка клиенту данных для визуализации или непосредственно готовых шаблонов;
3. Независимый от проектов модуль API. Его задача - работать с базой данных, обрабатывать и изменять данные, отдавать из вовне.

Client работает с контроллером страниц, который через API работает с данными и собирает из них вьюшки.
Данная схема показывает, что MVC было разбито на две части - PageController + View собраны в одном модуле, а Model + Controller в другом. Таким образом контроллер был разделен на две части - часть, зависящую от проекта, и независимую часть, работающую непосредственно с данными.
Все это, как хочется верить, позволит использовать API (и модели данных) повторно, переписывая лишь визуализацию и обработку пакетов от клиента.

Визуализация будет работать на нашем собственном аналоге шаблонизатора, а котором поговорим в следующих постах.
В общем схема работы стандартная - каждая страницы формируется на базе основного шаблона со встроенными блоками (тоже на шаблонах).

Вторым шагом я решил избавиться от рутинной работы с SQL. Главная задача на этом шаге - избавить себя от прямых запросов в базу, работать с записями, как с объектами, комфортно использовать связку объектов из разных таблиц (foreign key, join).

За основу будущего framework выбрал уже готовый ORM-framework. Долго не думал - выбрал из top-10 самый удобный для себя Propel. Он сочетал в себе простоту синтаксиса (в чем-то похожий на Yii), удобство работы и расширяемость.

Третьим шагом я решил определить как именно будут взаимодействовать все три модуля.
Я решил пойти по пути Yii и сделать одну точку входа на сервер, т.к. не хотел плодить кучу php-файлов с однотипным кодом (пусть даже небольшим). К тому же единственную точку входа легко контролировать не только из кода, но и анализом логов.

Команды подаются в виде - "Controller/Action" (далее route) + параметры, где controller - это имя контроллера страниц, а action - это действие, производимое на странице.

Все данные от клиента будут приходить в POST-запросах (route, произвольные данные), а возвращаться в JSON (т.к. он компактнее и программно с ним проще работать как на стороне сервера, так и на стороне клиента).
Общение с API будет также происходит POST (API-method, параметры), данные приходить в JSON.
GET-запросы я решил не использовать, т.к. url получается очень некрасивый, а маскировать его просто лень.
XML для возврата значений тоже не использую - формат очень рыхлый и работа с ни не во всех языках реализована просто.


Итак, наш проект на этом этапе состоит из:

1. Вспомогательные классы:
    - Работа с HTTP (получение GET, POST параметров)
    - Работа с Route (собирание / разбирание роута на Controller+Action, получение имени PageController'a)
 public static function getRoute( $route=null )
 {
  if( $route == null )
   $route = HttpUtils::getPost( 'route', null );
  $res = array(
   'page' => 'mainPage',
   'action' => 'index'
  );
  if( $route != null )
  {
   list( $page, $action ) = explode("/", $route, 2);
   $res['page'] = $page;
   $res['action'] = $action;
  }
  return $res;
 }
 public static function makeRoute( $page, $action=null )
 {
  if( $action==null )
   $action = 'index';
  return $page.'/'.$action;
 }

2. Главный класс приложения.
    - Парсинг роута и выборка класса обработки. Для отображения будем использовать собственный шаблонизатор, который умеет рендерить шаблон с подставленными в него переменными и отдавать результат пользователю.
    - работа с API через curl
class App
{
  private $_errors = array(); // Ошибки в ходе рендера
  private $_apiError = null; // Ошибка API
   // Синглтон на получение класса
  private static $_instance = null;
  public static function instance()
  {
    if( self::$_instance === null )
      self::$_instance = new App();
    return self::$_instance;
  }

  // Основная точка входа
  public function run()
  {
    // Создаем шаблонизатор
    $mainPage = new PlainViewPhp();
    ...
    $routeArr = RouteUtils::getRoute();
    $page = $routeArr['page'];
    $action = $routeArr['action'];

    switch( $page )
    {
      case 'mainPage':
        require 'pages/mainPage.php';
        $pageObj = new MainPage( $mainPage, $page, $action );
        // Отрисовываем страницу, она будет добавлена как HTML-код в основную страницу
        $pageObj->show();
        $pageTemplateName = 'mainPage.php';
        break;
        
        ...
    }

    // Если есть ошибки, добавляем их на страницу, чтобы они отобразились у клиента
    if( !empty($this->_errors) )
      $mainPage->assign( 'errors', $this->_errors );

    // Отрисовываем через шаблонизатор, используя шаблон страницы
    print $mainPage->show( "$pageTemplateName" );
  }

 // Работа с API
 public function api( $method, $params=array() )
 {
  $this->_apiError = null;
  $p['auth']   = <Ключ авторизации>
  $p['uid']    = <ID пользователя>
  $p['method'] = $method;
  $p['params'] = serialize( $params );  // Пакуем в json, чтобы передавать одним параметром

  $ch = curl_init();
  curl_setopt( $ch, CURLOPT_URL,  );
  curl_setopt( $ch, CURLOPT_POST, true );
  curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
  curl_setopt( $ch, CURLOPT_POSTFIELDS, $p );
  $res = curl_exec( $ch );
  curl_close( $ch );

  // Если включена оборачивалка слешей, то убираем их
  if( get_magic_quotes_gpc() )
   $res = stripslashes($res);

  $resEnd = unserialize( $res );

  // Проверяем на наличие ошибок
  if( isset( $resEnd['error'] ) )
  {
    // Может прийти ошибка
    $this->_apiError = $resEnd['error'];
    return false;
  }

  if( !isset($resEnd['data']) )
  {
    // Может прийти не правильный формат (например exception на стороне API)
    $this->_apiError = $res;
    return false;
  }

  // Все в порядке - возвращаем данные, запакованные в json
  return $resEnd['data'];
 }

3. Классы страниц (Page Controllers). В них находится основной код.
Базовый класс страницы:

class BasePage
{
 protected $_mainPage;
 protected $_page;
 protected $_action;


 public function __construct( &$mainPage, $page, $action )
 {
  $this->_mainPage = $mainPage;
  $this->_page   = $page;
  $this->_action = $action;
 }


 public function show()
 {
  $methodName = 'action'.ucfirst($this->_action);
  return call_user_func( array($this, $methodName) );
 }
}

Класс первой страницы:

class MainPage extends BasePage
{
 // Главное действие, вызываемое для роута main/index
 public function actionIndex()
 {
  ...
  // Заполняем шаблон данными
  $this->_mainPage->assign( 'var1', 123123 );
  $this->_mainPage->assign( 'var2', 'какой-то текст' );
 }

 ... Остальные действия, если есть ...
}

4. Шаблоны для отображения (Views)
Шаблоны будут разделены на шалоны страниц и шаблоны блоков.
О шаблонизаторах поговорим позже, пока я приведу лишь примеры кода.
Все написано на базе php, $this - указывает на текущий класс шаблона.
views/mainPage.php:
<div class='error' id='error'>
<?php
   $err = $this->getVar( 'errors' );
  if( is_array($err) && count($err)>0 )
    var_dump( $err );
?>
</div>

<?php
  // Пример блока
  echo PlainPhpView::loadBlock( 'login/loginBlock.php', array(
    'var1' => 123123,
  ));
?>
  ... Выводим все, что угодно ...
login/loginBlock.php:
  <form method='post'>
    Логин: <input name='login' type='text' />
    Пароль:<input name='password' type='password' />
    <input name="route" type="hidden" value="<?= RouteUtils::makeRoute('mainPage', 'login') ?>" />
    <input type="submit" value="Войти" />
  </form>
  <form method="post">
    <input name="route" type="hidden" value="<?= RouteUtils::makeRoute('registration') ?>" />
    <input type="submit" value="Зарегистрироваться" />
  </form>

5. Точка входа в API Это отдельный php файл.
$auth   = HttpUtils::getPost( 'auth',   null );
$userId = HttpUtils::getPost( 'uid',    null );
$method = HttpUtils::getPost( 'method', null );
$params = unserialize( HttpUtils::getPost( 'params', null ) );

$res = array();
$data = array();

// Главный класс API
$game = Game::instance();

try
{
 switch( $method )
 {
  case 'метод 1':
   ... работаем с классом game ...
   // Формируем данные для возврата
   $data = array(
    'var1' => $var1,
    'var2' => $var2,
   );
   break;
  default:
   $res['error'] = 'API-method "'.$method.'" not found';
   break;
 }
}
catch( GameException $e )
{
 $res['error'] = $e->getMessage();
}
$res['data'] = $data;
echo serialize($res);


6. Класс для работы моделями данных. (Controller)
В нашем случае он назван Game, описывать его нет смысла. Он работает с моделями данных, зависит от используемого ORM-фреймворка.
Ошибки выбрасываются через Exception, что позволяет не заботится о кодах возврата ошибок и работать с кодом так, будто он всегда корректно работает.

7. Модели данных и работа с БД. (Model + DB)
Все это на совести ORM-фреймворка, поэтому даже не будем лезть внутрь.

Первый пост получился немного сумбурным. Код, приведенный здесь нужен больше для иллюстрации подхода, т.к. большая его часть вырезана. Дальше я постараюсь давать более конкретные примеры.

Один из способов отправки почты через SMTP сервер с авторизацией.
Для простоты вбил параметры сервера mail.ru, осталось вписать имя пользователя (в моем случае полный email-адрес) и пароль.

$config['smtp_username'] = '';
$config['smtp_port']     = '25';
$config['smtp_host']     = 'smtp.mail.ru';
$config['smtp_password'] = '';
$config['smtp_debug']    = true;
$config['smtp_charset']  = 'utf8';
$config['smtp_from']     = '';

define ("ERROR_DONT_SEND_MAIL",     "Failed to send mail. Please contact the site administrator at:");
define ("ERROR_DONT_SEND_HELO",     "I can not send HELO!");
define ("ERROR_AUTH_LOGIN",         "I can not find an answer to an authorization request. ");
define ("ERROR_LOGIN_INCORRECT",    "Login authorization has not been accepted by the server!");
define ("ERROR_PASSWORD_INCORRECT", "Password was not accepted as a true server! Authorization Error!");
define ("ERROR_MAIL_FROM",          "I can not send command MAIL FROM: ");
define ("ERROR_RCPT_TO",            "I can not send command RCPT TO: ");
define ("ERROR_DATA",               "I can not send command DATA ");
define ("ERROR_BODY",               "not able to send the message body. A letter has been sent not! ");
define ("ERROR_TROUBLE",            "Problems sending mail");
define ("ERROR_NO_CLUBNAME",        "Enter the name of the club.");
define ("ERROR_NO_CLUBADDRESS",     "Enter the address of the club.");
define ("ERROR_NO_CLUBINFO",        "Enter the contact information.");
define ("ERROR_NO_NAME",            "You have not entered your name.");
define ("ERROR_NO_EMAIL",           "You did not enter your e-mail address.");
define ("ERROR_WRONG_EMAIL",        "You have entered an incorrect e-mail address.");
define ("SUCCESS_SEND_MAIL",        "The letter was sent.");

function smtpmail($mail_to, $subject, $message, $headers='')
{
        global $config;
        $SEND =   "Date: ".date("D, d M Y H:i:s") . " UT\r\n";
        $SEND .=   'Subject: =?'.$config['smtp_charset'].'?B?'.base64_encode($subject)."=?=\r\n";
        if( $headers != '' )
        {
            $SEND .= $headers."\r\n\r\n";
        }
        else
        {
            $SEND .= "Reply-To: ".$config['smtp_username']."\r\n";
            $SEND .= "MIME-Version: 1.0\r\n";
            $SEND .= "Content-Type: text/plain; charset=\"".$config['smtp_charset']."\"\r\n";
            $SEND .= "Content-Transfer-Encoding: 8bit\r\n";
            $SEND .= "From: \"".$config['smtp_from']."\" <".$config['smtp_username'].">\r\n";
            $SEND .= "To: $mail_to <$mail_to>\r\n";
            $SEND .= "X-Priority: 3\r\n\r\n";
        }

        $SEND .=  $message."\r\n";
        if( !$socket = fsockopen($config['smtp_host'], $config['smtp_port'], $errno, $errstr, 30) ) {
            if ($config['smtp_debug']) echo $errno."<br>".$errstr;
            return false;
        }

        if (!server_parse($socket, "220", __LINE__)) return false;

        fputs($socket, "HELO " . $config['smtp_host'] . "\r\n");
        if (!server_parse($socket, "250", __LINE__)) {
                if ($config['smtp_debug']) echo ERROR_DONT_SEND_HELO;
                fclose($socket);
                return false;
        }

        fputs($socket, "AUTH LOGIN\r\n");
        if (!server_parse($socket, "334", __LINE__)) {
                if ($config['smtp_debug']) echo ERROR_AUTH_LOGIN;
                fclose($socket);
                return false;
        }

        fputs($socket, base64_encode($config['smtp_username']) . "\r\n");
        if (!server_parse($socket, "334", __LINE__)) {
                if ($config['smtp_debug']) echo ERROR_LOGIN_INCORRECT;
                fclose($socket);
                return false;
        }

        fputs($socket, base64_encode($config['smtp_password']) . "\r\n");
        if (!server_parse($socket, "235", __LINE__)) {
                if ($config['smtp_debug']) echo ERROR_PASSWORD_INCORRECT;
                fclose($socket);
                return false;
        }

        fputs($socket, "MAIL FROM: <".$config['smtp_username'].">\r\n");
        if (!server_parse($socket, "250", __LINE__)) {
                if ($config['smtp_debug']) echo ERROR_MAIL_FROM;
                fclose($socket);
                return false;
        }

        fputs($socket, "RCPT TO: <" . $mail_to . ">\r\n");
        if (!server_parse($socket, "250", __LINE__)) {
                if ($config['smtp_debug']) echo ERROR_RCPT_TO;
                fclose($socket);
                return false;
        }

        fputs($socket, "DATA\r\n");
        if (!server_parse($socket, "354", __LINE__)) {
                if ($config['smtp_debug']) echo ERROR_DATA;
                fclose($socket);
                return false;
        }

        fputs($socket, $SEND."\r\n.\r\n");
        if (!server_parse($socket, "250", __LINE__)) {
                if ($config['smtp_debug']) echo ERROR_BODY;
                fclose($socket);
                return false;
        }

        fputs($socket, "QUIT\r\n");
        fclose($socket);
        return TRUE;
}
function server_parse( $socket, $response, $line = __LINE__ )
{
        global $config;
        $server_response = '';
        while( substr($server_response, 3, 1) != ' ' )
        {
               if( !($server_response = fgets($socket, 256)) )
               {
                   if ($config['smtp_debug'])
                       echo '<p>' . ERROR_TROUBLE . '</p>$response<br>$line<br>';
                   return false;
               }
        }
        if( !(substr($server_response, 0, 3) == $response) )
        {
           if( $config['smtp_debug'] )
               echo '<p>' . ERROR_TROUBLE . '</p>$response<br>$line<br>';
           return false;
        }
        return true;

}

Уже несколько раз пришлось собирать по крупицам инфу о модальных окнах на Javascript'е.
В результате нашел приемлимый для себя вариант, чем и спешу поделиться.

Готовим CSS для прозрачного фона:

#TB_overlay
{
    position: fixed;
    z-index: 0;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    background-color: #000;
    filter: progid:DXImageTransform.Microsoft.Alpha(opacity=25);
    -moz-opacity: 0.25;
    -khtml-opacity: 0.25;
    opacity: 0.25;
}


Вставляем в страницу само окно:

<div id='modalWindow'>
    содержимое окна
</div>
<div id='bgOverlay'></div>


И пишем ко всему этому скрипт:

// Скроем окно до лучших времен
$('#modalWindow').hide();

...

// Открытие окна
function openModalWindow()
{
    // Отображаем и центрируем окно
    var modalWindow = $('#modalWindow');
    modalWindow.css(
    {
        position: 'absolute',
        left: ( $(document).width()  - modalWindow.outerWidth()  ) / 2,
        top:  ( $(document).height() - modalWindow.outerHeight() ) / 2,
        'z-index': '100'
    });
    modalWindow.show();

    // включаем задник
    $('#bgOverlay').append( '<div id='TB_overlay'></div>' );
}

// Закрытие окна
function closeModalWindow()
{
    // Отключаем окно
    $('#modalWindow').hide();

    // Выключаем задник
    $('#bgOverlay').empty();
}

Scooter vs. Kiss

Интересные ссылки


jsFiddle.net
Это инновационный инструмент, работающий с HTML, CSS и JavaScript прямо в браузере. Он позволяет видеть результат не перезагружая страницу, а просто взглянуть на окно с заголовком Result.
Все свои наработки вы можете сохранить и получить url короткого вида на них.

Copyscape.com
Достаточно ввести адрес любой странички Вашего сайта и он выдаст все замеченные в сети дубли.

iStio.com
Сервис анализа текстов. Очень полезен для копирайтеров.
Позволяет оценить текст по таким параметрам как "плотность ключевых слов", "водность", "тошнота" и искать дубли в сети.

recipdonor.com
RDS - Recipient Donor Service, удобный сервис для анализа сайта по ссылкам, цитируемости и прочим показателям. Есть куча плагинов для разных браузеров.

Alexa.com
Один из самых известных анализаторов сайта на предмет рейтинга, ссылок и пр.

AS3 библиотеки


3D-движки

3D-движки для игр

Читать про остальные полезные библиотеки... )
  • Add to Memories

Для меня выходом по watermark стал простенький скрипт (см. ниже), который я нашел в сети. Работает он через .htaccess. В данном случае .htaccess перенаправляет с любой картинки на этот скрипт (_watermark.php), а этот скрипт уже по переданной информации (путь к картинке), добавляет на неё watermark.
Итак, если это кого-то заинтересует вот краткое содержание что нужно сделать:

1.Создаем в корне сайта папку с названием watermark и забрасываем в нее два этих файла:
_watermark.php (сам скрипт):



waterMark($_SERVER['DOCUMENT_ROOT'].$_SERVER['REQUEST_URI'], "watermark.png", "bottom=5,right=5");

function waterMark($original, $watermark, $placement = 'bottom=5,right=5', $destination = null) {
$original = urldecode($original);
$info_o = @getImageSize($original);
if (!$info_o)
return false;
$info_w = @getImageSize($watermark);
if (!$info_w)
return false;

list ($vertical, $horizontal) = split(',', $placement,2);
list($vertical, $sy) = split('=', trim($vertical),2);
list($horizontal, $sx) = split('=', trim($horizontal),2);

switch (trim($vertical)) {
case 'bottom':
$y = $info_o[1] - $info_w[1] - (int)$sy;
break;
case 'middle':
$y = ceil($info_o[1]/2) - ceil($info_w[1]/2) + (int)$sy;
break;
default:
$y = (int)$sy;
break;
}

switch (trim($horizontal)) {
case 'right':
$x = $info_o[0] - $info_w[0] - (int)$sx;
break;
case 'center':
$x = ceil($info_o[0]/2) - ceil($info_w[0]/2) + (int)$sx;
break;
default:
$x = (int)$sx;
break;
}

header("Content-Type: ".$info_o['mime']);

$original = @imageCreateFromString(file_get_contents($original));
$watermark = @imageCreateFromString(file_get_contents($watermark));
$out = imageCreateTrueColor($info_o[0],$info_o[1]);

imageCopy($out, $original, 0, 0, 0, 0, $info_o[0], $info_o[1]);
if( ($info_o[0] > 250) && ($info_o[1] > 250) )
{
imageCopy($out, $watermark, $x, $y, 0, 0, $info_w[0], $info_w[1]);
}

switch ($info_o[2]) {
case 1:
imageGIF($out);
break;
case 2:
imageJPEG($out);
break;
case 3:
imagePNG($out);
break;
}

imageDestroy($out);
imageDestroy($original);
imageDestroy($watermark);

return true;
}


и watermark.png(вашу картинку)
2.Создаем файл .htaccess:


DirectoryIndex index.php

<filesmatch>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^(.*)$ /watermark/_watermark.php [T=application/x-httpd-php,L,QSA]
</filesmatch>


и забрасываем его в папку с вашими картинками. К примеру в папку stories - в данном случае водяные знаки будут накладываться на все картинки и папки с картинками находящиеся в папке stories. Если нужно накладывать watermarkи не на все изображения папки stories, а на какие-то определенные папки с картинками - закидываем файл .htaccess именно в те папки с изображениями где нужно наложение водяных знаков.
Все! Ваши фотки с водяным знаком!

Tags:

Гришковец vs Альянс

Лечение Windows ручками


Сегодня в очередной раз лечил ручками винды.
Все шло как обычно, вирусы лечились, винда восстанавливалась, но вот только на некоторые сайты нельзя было попасть, ни по URL ни по IP. Пришлось идти на поклон к гуглу и коллегам по работе.
В результате в реестре был найден интересный раздел, в котором и затаился корень зла.

А чтобы ниче не искать заново, буду писать ниже всякие ссылки на инструментарий и инфу по лечению Windows XP.

HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\PersistentRoutes
- тот самый ключ, в нем содержатся IP-маршруты, загружаемые при старте винды.
Из на него я не мог выйти на кучу разных сайтов. Грохнул раздел целиком, все починилось.

Инструментарий, которым я пользуюсь для лечения:
AVZ4
Trojan Remover
HiJackThis
autoruns
Drweb Cureit!

Jeff Blickenstaff


Вчера искал гитарную музыку через abmp3.com, наткнулся на американского гитариста, очень понравилась его манера игры и пение.

Вот его сайт jeffblickenstaff.com и страничка его группы "Two capitals" на MySpace www.myspace.com/twocapitals

Latest Month

April 2012
S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     

Syndicate

RSS Atom
Powered by LiveJournal.com
Designed by Terri McAllister