Ярлыки

.htaccess (4) тестирование (8) шаблоны проектирования (3) css (5) Debian (6) docker (2) Doctrine2 (6) Git (6) html (4) java (6) javascript (13) jquery (11) LFS (3) linux (23) mac os (4) mod_rewrite (2) MSSQL (4) MySQL (18) ORM Doctrine (17) patterns (3) PDO (3) perl (7) PHP (64) PHPUnit (8) Python (15) SEO (2) Silex (1) SimpleXML (1) SQL (14) ssh (4) Ubuntu (24) Yii1 (1) Zend Framework (19) ZendFramework2 (8)

суббота, 1 декабря 2012 г.

Android. Вывод в TextView с прокруткой.

Максимальное кол-во строк = 25, далее - вертикальная прокрутка
activity_main.xml
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:tools="http://schemas.android.com/tools"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:orientation="vertical"
       tools:context=".MainActivity" >
       
     <TextView  android:layout_width="fill_parent"
           android:layout_height="wrap_content"
           android:id="@+id/textOut"
           android:singleLine="false"
           android:maxLines="25"
           android:scrollbars="vertical" />

</LinearLayout>

MainActivity.java

public class MainActivity extends Activity {

    TextView outText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        outText = (TextView) this.findViewById(R.id.textOut);
        outText.setMovementMethod(new ScrollingMovementMethod());
  
        ...
  
    }

        ...
}

четверг, 13 сентября 2012 г.

PHP. Строгая типизация с SPL_Types

Строгая типизация возможна в php, для этого требуется установить расширение pecl SPL_Types



Пример для перечисления (Enum)

class Role extends SplEnum {
 
         const __default = 1;
 
         const ADMIN = 0;
 
         const USER = 1;
}

class User {
        
        ...

        protected $role = Role::USER;

        public function setRole(Role $role) {
                 $this->role = (int) $role;
        }

        public function getRole() {
                 return $this->role;   
        }

        ...
}

$user = new User();
$role = new Role(Role::ADMIN);
$user->setRole($role);

var_dump($user->getRole()); // int(0)

$role = new Role(5); // PHP Fatal error:  Uncaught exception 
                     // 'UnexpectedValueException'
                     // with message 'Value not a const in enum Role' in ...

пятница, 27 апреля 2012 г.

Unix. Как убить процессы с регулярным выражением kill + grep.

kill -9 `ps -ef | grep processmask | grep -v grep | awk '{print $2}'`

Mac OSX. SSH и кириллица.

Если при подключении по ssh к удаленному linux-серверу c кириллическим набором
не все гладко: некоторые символы не печатаются или вообще возникает конструкция "?:",
то сразу смотрим текущую locale ...
Если она не ru_RU.UTF-8, то сразу пишем в /etc/profile
export LANG=ru_RU.UTF-8
export LC_CTYPE="ru_RU.UTF-8"
export LC_NUMERIC="ru_RU.UTF-8"
export LC_TIME="ru_RU.UTF-8"
export LC_COLLATE="ru_RU.UTF-8"
export LC_MONETARY="ru_RU.UTF-8"
export LC_MESSAGES="ru_RU.UTF-8"
export LC_PAPER="ru_RU.UTF-8"
export LC_NAME="ru_RU.UTF-8"
export LC_ADDRESS="ru_RU.UTF-8"
export LC_TELEPHONE="ru_RU.UTF-8"
export LC_MEASUREMENT="ru_RU.UTF-8"
export LC_IDENTIFICATION="ru_RU.UTF-8"
export LC_ALL=

вторник, 24 апреля 2012 г.

Apache. MultiViews и mod_rewrite.

Наткнулся на то, что на моем mac не работает правило mod_rewrite вида

RewriteRule ^(.*)$ index.php?route=$1 [L,QSA]

при запросах вида http://myhost/index/action

Оказалось что включена опция MultiViews в настройках хоста.
Apache искал и находил файл по соотв маске, получалось что запрос идет к файлу index.php.

http://myhost/index.php/action

Соотв сегмент action вообще игнорировался.

Zend Framework. Отправка писем через smtp-сервер.

$smtpConfig = array(
    'host' => 'smtphost',
    'username' => 'username',
    'password' => 'password',
    'port' => 25,
    'auth' => 'login'
);

$message = 'my html message';
$subject = 'test message';
$transport = new Zend_Mail_Transport_Smtp($smtpConfig['host'],
                 $smtpConfig);
Zend_Mail::setDefaultTransport($transport);
$mail = new Zend_Mail('utf-8');
$mail->setBodyHtml($message, 'utf-8');
$mail->setFrom('myemail@myhost.com', 'From me');
$mail->addTo($email, 'recipient name');
$mail->setSubject($subject);
$mail->send();

пятница, 13 апреля 2012 г.

MySQL. Добавить поле в существующую таблицу.

ALTER TABLE contacts ADD email VARCHAR(60);

ALTER TABLE contacts ADD email VARCHAR(60) AFTER name;

ALTER TABLE contacts ADD email VARCHAR(60) FIRST;

ALTER TABLE contacts ADD opt INT(1) NOT NULL DEFAULT 0;

среда, 11 апреля 2012 г.

Mac OSX. Не работает mod_rewrite.

Вроде все правильно сделали, модуль подключен, .htaccess лежит как надо, ан нет не работает ... Смотрим сюда /etc/apache2/users/myusername.conf

<Directory "/Users/<username>/Sites/">
        Options Indexes MultiViews
       AllowOverride None
       Order allow,deny
       Allow from all
</Directory>


и меняем на следующее

<Directory "/Users/<username>/Sites/">
        Options Indexes MultiViews FollowSymlinks
        AllowOverride All
        Order allow,deny
        Allow from all
</Directory>

Mac OSX. Установка и настройка mysql.

Качаем последнюю версию с офф сайта.
Распаковываем архив и устанавливаем сам сервер, модуль системных настроек, автозагрузчик. Добавим mysql в PATH

nano ~/.profile
и пишем ... export
PATH=/usr/local/mysql/bin:$PATH

Со старта сокет создает в tmp
Это надо изменить

sudo mkdir /var/mysql
sudo ln -s /tmp/mysql.sock /var/mysql/mysql.sock

Из важного вроде все ...

MySQL. Настройка виртуальных хостов apache для Mac OSX.

Добавляем хост в /etc/hosts

sudo nano /etc/hosts

...
127.0.0.1 myhost

В конфигурации apache снимаем комментарий со строки, разрешающей виртуальные хосты

sudo edit /etc/apache2/httpd.conf

...
# Virtual hosts
Include /private/etc/apache2/extra/httpd-vhosts.conf

Открываем файл конфигурации хостов

sudo nano /private/etc/apache2/extra/httpd-vhosts.conf

Там есть примеры, можно обойтись минимумом
<VirtualHost *:80>
        DocumentRoot "/Users/username/www/myhost"
        ServerName myhost
</VirtualHost>

Перезапуск apache

sudo /usr/sbin/apachectl restart

воскресенье, 8 апреля 2012 г.

PHP. Многозадачность aka многопоточность.

Полноценных потоков(Threads), как в java, perl или python, в php нет. Но во многих случаях они и не нужны. Класс Pool создает новые процессы ос, запуская php скрипты и передавая им параметры командной строки. Взаимодействие между процессами можно реализовать различными способами, например, разделяемая память и семафоры, базы данных (sqlite в оперативной памяти), apc, memcache, файлы. Придется, разумеется, реализовать блокировки при записи из различных процессов, так как, например, apc и memcache их не имеют. Запускаем и смотрим:
Task ID: 1
Task ID: 0
Task ID: 2
Task ID: 3
Task ID: 4
Task ID: 5
Task ID: 6
Task ID: 7
Task ID: 8
Task ID: 9
Total time: 10 s
Видно, что скрипт выполнялся всего 10 секунд.

пятница, 6 апреля 2012 г.

Apache. PHP. Контролируемое скачивание файлов.

Задача такая, файлы лежат вне корневой директории сервера. Дать возможность скачать файл можно при определенных условиях, например, только авторизованным. Или в зависимости от оплаты аккаунта и тд. Делается это просто, с использованием XSendfile.
Во-первых можно просто послать заголовки
$file = 'path/to/file';
header("X-Sendfile: $file");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
Во-вторых для Zend Framework нашел такой хелпер контроллера
/**
 * X-Sendfile controller helper
 *
 * @copyright Copyright (c) 2008-2009 Pro Soft Resources USA Inc. 
 * (http://www.prosoftpeople.com)
 * @author    Rolando Espinoza La fuente (rho@prosoftpeople.com)
 * @license   http://www.opensource.org/licenses/bsd-license.php 
 * New BSD License
 * @version   $Id$
 */ 

class Zend_Controller_Action_Helper_Xsendfile 
    extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * @var bool Use Nginx specific header
     */
    protected $_isNginx = false;

    /**
     * @var bool Wheter to force download. Default true.
     */
    protected $_forceDownload = true;

    /**
     * Send files through X-Sendfile feature
     * 
     * @param  string $path     File path.
     * @param  string $filename File name. Optional.
     * @return $this
     */
    public function xsendfile($path, $mime = null, $filename = null)
    {
        // set filename if not given
        if (null === $filename) {
            $filename = basename($path);
        }

        $response = $this->getResponse();

        // send mime info
        if (null !== $mime) {
            $response->setHeader('Content-Type', $mime);
        }

        // send file as attachment
        if ($this->forceDownload()) {
            $contentDisposition = 'attachment; filename=' . $filename;
        } else {
            $contentDisposition = 'inline; filename=' . $filename;
        }

        // if server is Nginx use different header
        if ($this->isNginx()) {
            $headerName = 'X-Accel-Redirect';
        } else {
            $headerName = 'X-Sendfile';
        }

        // set response headers
        $response->setHeader('Content-Disposition', $contentDisposition)
                 ->setHeader($headerName, $path);

        // Done. Webserver will send the file 

        return $this;
    }

    /**
     * Force download flag
     * 
     * @param  boolean  Optional. 
     * @return boolean
     */
    public function forceDownload($flag = null)
    {
        if ($flag !== null) {
            $this->_forceDownload = (bool) $flag;
        }

        return $this->_forceDownload;
    }

    /**
     * Nginx web server flag
     *
     * TODO: Support internal paths
     * 
     * @param  boolean  Optional. 
     * @return boolean
     */
    public function isNginx($flag = null)
    {
        if ($flag !== null) {
            $this->_isNginx = (bool) $flag;
        }

        return $this->_isNginx;
    }

    /**
     * Direct pattern
     * 
     * @param  string $path     File path.
     * @param  string $filename File name. Optional.
     * @return $this
    */
    public function direct($path, $mime = null, $filename = null)
    {
        return $this->xsendfile($path, $mime, $filename);
    }
}
Пример использования в контроллере
public function downloadAction() {
    // disable output
    $this->_helper->layout->disableLayout();
    $this->_helper->viewRenderer->setNoRender(TRUE);

    $fileId = $this->_request->getParam('fid');
    $fileObj = $this->_helper->doctrineEntityManager
                    ->getRepository('File')
      ->findOneBy(array('id' => $fileId));
    // Проверяем права на скачивание файла
    if ($this->user->getStatus() === 'free') {
        if ($fileObj->getStatus() === 'pro')
            die('Permission denied');
    }
    // if using nginx
    //$this->_helper->Xsendfile->isNginx(true);
    // not force download
    //$this->_helper->Xsendfile->forceDownload(false);
    // send file with custom name
    //$this->_helper->xsendfile($file, 'application/pdf', 'report.pdf');

    $this->_helper->xsendfile($fileObj->getFilePath(),
        $fileObj->getFileMime());
}
Для того чтобы все это работало должен быть установлен модуль apache XSendfile (иначе будете скачивать файлы весом 0 байт):
root@host:~# apt-cache search xsendfile
libapache2-mod-xsendfile - Serve large static files efficiently from web applications
root@host:~# apt-get install libapache2-mod-xsendfile
После того как модуль установлен, проверяем его наличие в разрешенных модулях apache
root@host:~# ls /etc/apache2/mods-enabled/
...
xsendfile.load
...
И если его там нет создаем ссылку на загрузчик модуля в /etc/apache2/mods-avaliable. И в debian squeeze и в ubuntu 12.04 он прописался там после установки. Далее нам нужно включить модуль в .htaccess нашего сайта. Тут для debian и ubuntu есть различия, все дело в разных версиях модуля в репозиториях. Для Ubuntu .htaccess:
...
# enable xsendfile
XSendFile On
...
А в конфигурации хоста надо добавить следующую директиву, она разрешает загружать файлы из указанной директории, которая находится за пределами корня веб-сервера (корень /var/www/mysite/public)
leon@Berta:~$ cat /etc/apache2/sites-enabled/mysite.conf 
XSendFilePath /var/www/mysite/uploads

...
Для debian squeeze просто добавляем в .htacess две директивы
# enable xsendfile
XSendFile On

XSendFileAllowAbove on
Без этих двух вариантов директив XSendFileAllowAbove и XSendFile вы будете получать различные ошибки, или закачку файлов длинной 0 байт. Ошибки сервера смотрим
cat /var/log/apache2/error.log
К слову, директива XSendFileAllowAbove устарела и в новой версии модуля ее нет.

понедельник, 2 апреля 2012 г.

SQL. Сортировка выборки с условием.

Допустим надо вывести список городов и чтобы при этом некоторые города оказались в начале списка.
SELECT * FROM city ORDER BY
CASE 
    WHEN name LIKE "%Москва%" THEN 1 
    WHEN name LIKE "%Санкт-Петербург%" THEN 2 
    ELSE 3 
END
Или для Doctrine1 DQL:
$q = Doctrine_Query::create()
    ->from('City')
    ->select('id, name')
    ->orderBy('CASE 
            WHEN name LIKE "%Москва%" THEN 1 
            WHEN name LIKE "%Санкт-Петербург%" THEN 2 
            ELSE 3 
        END')
   ->setHydrationMode(Doctrine::HYDRATE_ARRAY);
$city = $q->execute();

пятница, 23 марта 2012 г.

Zend Framework. Композитный элемент формы на примере номера телефона.

Объект элемента формы library/App/Form/Element/Phone.php

class App_Form_Element_Phone extends Zend_Form_Element_Xhtml {

        public $helper = 'phoneElement';
        protected $countryCode;
        protected $providerCode;
        protected $number;

        public function setCountryCode($num) {
                $this->countryCode = $this->filter($num);
                return $this;
        }

        public function setProviderCode($num) {
               $this->providerCode = $this->filter($num);
               return $this;
        }
 
        public function setNumber($num) {
               $this->number = $this->filter($num);
               return $this;
        }
 
        // Примитивная фильтрация данных, вырезаем все кроме цифр
        protected function filter($num) {
              return preg_replace('/[^\d]/', '', $num);
        }

        public function __construct($spec, $options = null) {
             parent::__construct($spec, $options);
             // Можно не удалять
             $this->removeDecorator('HtmlTag'); 
        }

        public function setValue($value) {
             if (is_array($value) && isset($value['country_code'],
                          $value['provider_code'], $value['number'])) {
                     $this->setCountryCode($value['country_code'])
                         ->setProviderCode($value['provider_code'])
                         ->setNumber($value['number']);
                    }
        }
 
        public function getValue() {
             if(! $this->countryCode || ! $this->providerCode
                  || ! $this->number)
                 return false; // Благодаря этому срабатывает валидатор NotEmpty
             return array(
                 'country_code' => $this->countryCode,
                 'provider_code' => $this->providerCode,
                 'number' => $this->number
             );
        }

}

Помощник вида для элемента library/App/View/Helper/PhoneElement.php

class App_View_Helper_PhoneElement extends Zend_View_Helper_FormElement {

     protected $html = '';

     public function phoneElement($name, $value = null, $attribs = null) {
         $providerCode = $countryCode = $number = '';
         // Формирует элемент html - текстовое поле,
         // стандартный помощник вида
         $helper = new Zend_View_Helper_FormText();
         $helper->setView($this->view);
         $countryCode = isset($value['country_code'])
             ? $value['country_code'] : '7';
         $providerCode = isset($value['provider_code'])
             ? $value['provider_code'] : '';
         $number = isset($value['number'])
             ? $value['number'] : '';
         // Формируем html-код элемента
         $this->html .= '<div class="phone-plus">+</div>' 
                   . $helper->formText($name 
                   . '[country_code]', $countryCode, array('size' => 3,
                                                         'maxlength' => 3));
         $this->html .= $helper->formText($name 
                   . '[provider_code]', $providerCode, array('size' => 5,
                                                         'maxlength' => 5));
         $this->html .= $helper->formText($name 
                   . '[number]', $number, array('size' => 7, 'maxlength' => 7));
         $this->html .= '<div class="clear"></div>';
         return $this->html;
     }

}

Применение элемента в форме


...
$phone = new App_Form_Element_Phone('phone');
$phone->setLabel('Телефон +7(XXX)XXXXXXX')->setRequired();
$phone->addValidator('NotEmpty', true, array(
     'messages' => array('isEmpty' => 'Необходимо ввести номер телефона')));
$this->addElement($phone);
...

четверг, 22 марта 2012 г.

PHP. Публикация вакансий для поискового сервиса Trovit.

Однажды потребовалось генерировать xml-поток объявлений о работе для поискового сервиса http://www.trovit.com/. Сервис довольно популярен в штатах. Позволяет размещать объявления о работе, недвижимости и продаже автомобилей. Так как задача ограничивалась объявлениями о работе а человек я ленивый, то любой желающий может запросто дописать пару очень коротких классов для работы с другими разделами. Написал очень маленькую библиотеку.

Тут собственно требования к потоку http://about.trovit.com/your-ads-on-trovit/russia/feed-ru-rabota/

 Страница валидатора http://about.trovit.com/your-ads-on-trovit/russia/feed-ru-rabota/

Клиентский код, так примерно это может выглядеть

require_once 'Trovit/Ad.php';
require_once 'Trovit/JobsAd.php';
require_once 'Trovit/Feed.php';
require_once 'Trovit/JobsFeed.php';

// Некий класс вакансии (объявления о работе)
class Vacancy {

   public $id;
   public $url;
   public $title;
   public $content;
   public $date;
   public $company;

}

// Некий адаптер клиентских данных,
// расширяющий абстрактный класс объявления о работе
class Ad extends Trovit_JobsAd {

   protected $vacancy;

   public function __construct(Vacancy $vacancy) {
      $this->vacancy = $vacancy;
   }

   public function getId() {
      return $this->vacancy->id;
   }

   public function getUrl() {
      return $this->vacancy->url;
   }

   public function getTitle() {
      return $this->vacancy->title;
   }

   public function getContent() {
      return $this->vacancy->content;
   }

   public function getDate() {
      return $this->vacancy->date;
   }

   public function getCompany() {
      return $this->vacancy->company;
   }
 
   public function getPostcode() {
      return '099900';
   }

   ...

   // Опциональные методы определены в классе-родителе

}

$vacancy = new Vacancy();
$vacancy->id = 1;
$vacancy->url = 'http://example.com';
$vacancy->title = 'Tester Vacancy';
$vacancy->content = 'Lorem ipsum dolor sit amet, consectetur 
 adipisicing elit, sed do eiusmod tempor incididunt ut labore
 et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
 exercitation ullamco laboris nisi ut aliquip ex ea commodo
 consequat. Duis aute irure dolor in reprehenderit in
 voluptate velit esse cillum dolore eu fugiat nulla pariatur.
 Excepteur sint occaecat cupidatat non proident,
 sunt in culpa qui officia deserunt
 mollit anim id est laborum.';
$vacancy->company = 'Tester Company';
$vacancy->date = '22/03/2012 17:30:00';

$vacancy1 = new Vacancy();
$vacancy1->id = 2;
$vacancy1->url = 'http://example1.com';
$vacancy1->title = 'Tester Vacancy';
$vacancy1->content = 'Lorem ipsum dolor sit amet, consectetur
 adipisicing elit, sed do eiusmod tempor incididunt ut labore
 et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
 exercitation ullamco laboris nisi ut aliquip ex ea commodo
 consequat. Duis aute irure dolor in reprehenderit
 in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
 Excepteur sint occaecat cupidatat non proident,
 sunt in culpa qui officia deserunt mollit anim id est laborum.';
$vacancy1->company = 'Tester Company';
$vacancy1->date = '22/03/2012 17:30:00'; //ДД/ММ/ГГГГ ЧЧ:ММ:CC

$ad = new Ad($vacancy);
$ad1 = new Ad($vacancy1);

$feed = new Trovit_JobsFeed();
$feed->addAd($ad);
$feed->addAd($ad1);
file_put_contents('trovit.xml', $feed->getXML());

Проходит валидацию



Скачать архив с библиотекой Post to Trovit.com with PHP easy

среда, 21 марта 2012 г.

Zend Framework. Валидатор длинны строки.

Пример:
$pwd = new Zend_Form_Element_Password('password');
$pwd->setLabel('Пароль не менее 6 символов *');
...
$stringLength = new Zend_Validate_StringLength(6);
$stringLength->setMessage(
    'Пароль слишком короткий; минимальная длинна %min% символов',
    Zend_Validate_StringLength::TOO_SHORT);
$pwd->addValidator($stringLength);

четверг, 8 марта 2012 г.

Nginx. Проксирование запросов.

Потребовалось иметь хост, который проксирует запросы к разным версиям сайта.
Например, мы делаем запрос http://mysiteproxy/index/action1 и выдается страница с
http://site1/index/action1, а если делаем запрос http://mysiteproxy/index/action2 -
получаем http://site2/index/action2. Что-то вроде этого. Пример для ubuntu.

Настраиваем nginx и apache2.

Apache будет слушать порт 8080.
$ sudo nano /etc/apache2/ports.conf

...
NameVirtualHost *:8080
Listen 8080
...
Настриваем проксирование запросов к apache. Создаем файл
/etc/nginx/conf.d/proxy.conf
proxy_redirect          off;
proxy_set_header        Host            $host;
proxy_set_header        X-Real-IP       $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size    10m;
client_body_buffer_size 128k;
client_header_buffer_size 64k;
proxy_connect_timeout   90;
proxy_send_timeout      90;
proxy_read_timeout      90;
proxy_buffer_size   16k;
proxy_buffers       32   16k;
proxy_busy_buffers_size 64k;
Редактируем настройки хоста по-умолчанию /etc/nginx/sites-available/default.
Все запросы шлем на localhost:8080, по-умолчанию nginx висит на порту 80. Не забываем потом поместить конфиг. файл в sites-enabled. Я обычно делаю ссылку.
server {
     #listen   80; ## listen for ipv4; this line is default and implied
     #listen   [::]:80 default ipv6only=on; ## listen for ipv6

     root /usr/share/nginx/www;
     index index.html index.htm;

     # Make site accessible from http://localhost/
     server_name localhost;

     location / {
         # First attempt to serve request as file, then
         # as directory, then fall back to index.html
         try_files $uri $uri/ /index.html;
  
         proxy_pass http://127.0.0.1:8080;
     }
}

Перезапускаем сервера
sudo /etc/init.d/apache2 restart
sudo /etc/init.d/nginx restart

Настраиваем хост-прокси

Создаем конфигурационный файл хоста
sudo nano /etc/nginx/sites-avaliable/mysiteproxy.conf

server {
        listen   80;
        server_name  mysiteproxy;

        access_log  /var/log/nginx/access.log;

        location = / {
               include         /etc/nginx/site1.proxy.conf;
        }

        location / {
               include         /etc/nginx/site2.proxy.conf;
        }

        location ~* \.(jpeg|jpg|gif|png|css|js|pdf|txt|tar)$ {
               root /var/www/site1/;
        }
}
Тут директивы location указывают как будут проксироваться запросы (подробно в документации nginx).
В данном случае главная будет с site1, внутренние страницы - site2.
Создаем два файла конфигурации прокси
sudo nano /etc/nginx/site1.proxy.conf


proxy_pass       http://127.0.0.1:8080/;
proxy_set_header        Host            site1;

proxy_redirect          http://site1/  /;
proxy_set_header        X-Real-IP       $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_connect_timeout   90;
proxy_send_timeout      90;
proxy_read_timeout      90;
proxy_buffers           32 4k;
Аналогично для второго хоста.
Перезапускаем nginx
sudo /etc/init.d/nginx restart
Если не настраиваем DNS, то прописываем хосты в /etc/hosts.

Doctrine2. Использование репозиториев.

При работе с Doctrine2 все манипуляции с базой производим через Doctrine\ORM\EntityManager.
Простой способ получить запись из базы данных.
$user = $em->find('Entities\User', $id);
Очень часто нам приходится работать с репозиториями объектов (что-то общее с коллекциями Doctrine1).
$user = $em->getRepository('Entities\User')->find($id);
В данном случае менеджер проверяет репозитории сопоставленные с данным классом и если таких нет,
то использует класс репозитория по-умолчанию (Doctrine\ORM\EntityRepository). Он содержит
следующие базовые методы:
public function findAll()
public function findBy(array $criteria)
public function findOneBy(array $criteria)
Часто этого бывает недостаточно. Класс EntityRepository содержит метод
public function createQueryBuilder($alias)
Мы можем наследовать базовый класс и создавать собственные репозитории
namespace Repositories;
 
use Doctrine\ORM\EntityRepository;
use Entities;
 
class UserRepository extends EntityRepository
{
    public function finderMethod($arguments){
        // My custom query etc
    }
}
Мы должны указать, что надо использовать новый репозиторий в классе модели
namespace Entities;
 
/** @Entity(repositoryClass="Repositories\UserRepository")
 *  @Table(name="dealers")
 */
class User
...
Теперь мы можем это использовать
$users = $em->getRepository('Entities\User')->finderMethod($arguments);

среда, 7 марта 2012 г.

Zend Framework. Помощник контроллера.

1. Сообщаем helper broker, путь к каталогу с помощниками.
Это можно сделать, например, в bootstrap:
  Zend_Controller_Action_HelperBroker::addPath(
        APPLICATION_PATH .'/controllers/helpers');
2. Пишем хелпер application/controllers/helpers/Myhelper.php:

class Zend_Controller_Action_Helper_Myhelper extends
                Zend_Controller_Action_Helper_Abstract
{
    function direct()
    {
        return __METHOD__;
    }

    function another()
    {
        return __METHOD__;
    }
}
3. Вызываем хелпер в контроллере:
class IndexController extends Zend_Controller_Action 
{
    public function indexAction() 
    {
        echo $this->_helper->myhelper();
        echo $this->_helper->myhelper->another();
        die;
    }
}

вторник, 28 февраля 2012 г.

Nano. Выделение текста.

Начало выделения: Alt-A или Ctrl-^
Навигация до момента выполнения действия над текстом
Удаление текста: Ctrl-K или F9
Копирование в буфер: Alt-6
Вставка текста из буфера: Ctrl-U или F10

суббота, 25 февраля 2012 г.

Zend Framework. Декоратор формы Description.

Например, данные пользователя не приняты на сервере, удобно послать сообщение методом setDescription.
Чтобы это работало надо установить декоратор для формы:
class LoginForm extends Zend_Form
{
    public function init() 
    {
        ...
        $this->setDecorators(array('Description', 'FormElements', 'Form'));
        ...
    }
}
После этого можно использовать:
$form = new LoginForm();
$form->setDescription('Неправильные имя или пароль');
$form->populate($formData)

пятница, 24 февраля 2012 г.

Git. Основы.

Работаем в одиночку


В каталоге проекта выполняем создаем репозиторий
git init
Добавляем файлы проекта
git add .
Делаем коммит (всегда предварительно надо добавлять отредактированные файлы)
git commit -m "First Commit"
Потребовался новый функционал - создаем ветку
git branch new-feature
Переключаемся на нее
git checkout new-feature
Вносим изменения и смотрим на них, индексируем, делаем коммит
git status
git add .
git commit -m "New feature added"
Делаем слияние веток, переключаемся на master
git checkout master
git merge new-feature
Если требуется внести небольшое изменение, можно не создавать ветку:
git stash
Вносим изменения и применяем их
git stash apply

Работа с удаленным репозиторием


Копируем репозиторий
git clone git://github.com/username/project.git master
Команда создала у вас репозитарий, и внесла туда копию ветки master проекта project.
Создаем новую ветку, вносим изменения в код
git branch new-feature
edit README
git add .git 
commit -m "Added a super feature"
Переходим в основную ветку, заберем последние изменения в проекте, и попробуем добавить новую фишку в проект:
git checkout master
git pull
git merge new-feature
Если есть конфликты - решаем, если нет, то сразу выгружаем
git push

Как сделать push в серверный репозиторий с файлами.

git config receive.denyCurrentBranch ignore
Также пишем в .git/hooks/post-receive
#!/bin/bash
cd ..
env -u GIT_DIR git reset --hard master
Помечаем как исполняемый
chmod a+x .git/hooks/post-receive

Копирование ветки удаленного репозитория

git checkout -b experimental origin/experimental

Создание пустой ветки без коммитов

git checkout --orphan mybranch

вторник, 21 февраля 2012 г.

Ubuntu. Установка пакета i386 на amd64.

Захотелось поставить антивирус avast! Linux Home Edition На сайте сборка для i386, иногда можно установить с помощью ключа --force-architecture
leon@Berta:~/Рабочий стол$ sudo dpkg -i --force-architecture avast4workstation_1.3.0-2_i386.deb
[sudo] password for leon: 
Выбор ранее не выбранного пакета avast4workstation:i386.
(Чтение базы данных ... на данный момент установлено 331920 файлов и каталогов.)
Распаковывается пакет avast4workstation:i386 (из файла avast4workstation_1.3.0-2_i386.deb)...
Настраивается пакет avast4workstation:i386 (1.3.0) ...
Обрабатываются триггеры для man-db ...
В данном случае работает

четверг, 16 февраля 2012 г.

Doctrine2. Введение.

Doctrine2 - object-relational mapper (ORM) для PHP 5.3.0+ Ядром является реализация шаблона Data Mapper

Что такое Entities?

 

Entities (классы модели) - объекты PHP, которым не надо расширять или реализовывать никакие абстрактные классы или интерфейсы. Есть некоторые ограничения, например,
- они не могут быть final или иметь методы final;
 - реализовывать clone или wakeup;
... Entity имеет свойства сохраняемые в базе данных и получаемые из нее.

 

 Пример модели: BUG TRACKER

 

Требования: Ошибка имеет описание, дату создания, статус, корреспондента и разработчика Ошибка может происходить с разными продуктами (платформами) Продукт имеет название Корреспондент и разработчик - оба пользователи системы Пользователь может сообщить об ошибке Назначенный разработчик должен исправить ошибку Пользователь может видеть все ошибки о которых он сообщил или для решения которых он назначен Ошибки могут быть представлены как список с постраничным выводом

 

Создание проекта

 

Установите Doctrine2
$ pear channel-discover pear.doctrine-project.org
$ pear install --alldeps doctrine/DoctrineORM
Создайте каталог проекта
$ mkdir project
$ cd project
Структура должна быть такой
project
|-- config
|   |-- xml
|   `-- yaml
`-- entities

 

 Начало проектирования модели

 

Создайте классы
// Bug.php
class Bug
{
    protected $id;
    protected $description;
    protected $created;
    protected $status;
}


// Product.php
class Product
{
    protected $id;
    protected $name;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}


// User.php
class User
{
    protected $id;
    public $name; // public for educational purpose, see below

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

Создание связей
// entities/Bug.php
use Doctrine\Common\Collections\ArrayCollection;

class Bug
{
    // ... (previous code)

    protected $products = null;

    public function __construct()
    {
        $this->products = new ArrayCollection();
    }
}

// entities/User.php
use Doctrine\Common\Collections\ArrayCollection;
class User
{
    // ... (previous code)

    protected $reportedBugs = null;
    protected $assignedBugs = null;

    public function __construct()
    {
        $this->reportedBugs = new ArrayCollection();
        $this->assignedBugs = new ArrayCollection();
    }
}
Для отладки классов не используйте var_dump() и тд, используйте Doctrine\Common\Util\Debug::dump() Создаем связи между пользователем и ошибкой
// entities/Bug.php
class Bug
{
    // ... (previous code)

    protected $engineer;
    protected $reporter;

    public function setEngineer($engineer)
    {
        $engineer->assignedToBug($this);
        $this->engineer = $engineer;
    }

    public function setReporter($reporter)
    {
        $reporter->addReportedBug($this);
        $this->reporter = $reporter;
    }

    public function getEngineer()
    {
        return $this->engineer;
    }

    public function getReporter()
    {
        return $this->reporter;
    }
}


// entities/User.php
class User
{
    // ... (previous code)

    protected $reportedBugs = null;
    protected $assignedBugs = null;

    // Используется именование методов в прошедшем времени,
    // это обозначает что назначение уже произошло и
    // и методы используются лишь для обеспечения согласованности ссылки. 
    // Очевидно что одностороннее использование методов 
    // User::addReportedBug() и User::assignedToBug()
    // в пользовательском коде не добавит ошибку в коллекцию
    // в свойствах Bug::$reporter или Bug::$engineer.
    // Использование этих методов и вызов Doctrine не обновят
    // представление коллекций в базе данных.

    public function addReportedBug($bug)
    {
        $this->reportedBugs[] = $bug;
    }

    public function assignedToBug($bug)
    {
        $this->assignedBugs[] = $bug;
    }
}

 

Метаданные отображения для классов модели

 

Далее надо добавить метаданные для Doctrine, чтобы классы модели могли быть сохранены в базе данных. Это можно сделать 3 способами: XML, YAML, аннотации. В первом и втором случае, мы создаем файлы и кладем их в каталоги project/config/xml и project/config/yaml соотвественно. В последнем - аннотации добавляются непосредственно в код класса. Пример для продукта
// entities/Product.php
/**
 * @Entity @Table(name="products")
 **/
class Product
{
    /** @Id @Column(type="integer") @GeneratedValue **/
    protected $id;
    /** @Column(type="string") **/
    protected $name;

    // .. (other code)
}

<!-- config/xml/Product.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                    http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

      <entity name="Product" table="products">
          <id name="id" type="integer">
              <generator strategy="AUTO" />
          </id>

          <field name="name" type="string" />
      </entity>
</doctrine-mapping>


# config/yaml/Product.dcm.yml
Product:
  type: entity
  table: products
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    name:
      type: string


Для поля id дерективы указывают на то, что будет использован механизм генерации родной для СУБД (AUTO INCREMENT для MySQL, например). Для Bug
// entities/Bug.php
/**
 * @Entity @Table(name="bugs")
 **/
class Bug
{
    /**
     * @Id @Column(type="integer") @GeneratedValue
     **/
    protected $id;
    /**
     * @Column(type="string")
     **/
    protected $description;
    /**
     * @Column(type="datetime")
     **/
    protected $created;
    /**
     * @Column(type="string")
     **/
    protected $status;

    /**
     * @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
     **/
    protected $engineer;

    /**
     * @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
     **/
    protected $reporter;

    /**
     * @ManyToMany(targetEntity="Product")
     **/
    protected $products;

    // ... (other code)
}


<!-- config/xml/Bug.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                    http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

    <entity name="Bug" table="bugs">
        <id name="id" type="integer">
            <generator strategy="AUTO" />
        </id>

        <field name="description" type="text" />
        <field name="created" type="datetime" />
        <field name="status" type="string" />

        <many-to-one target-entity="User" field="reporter" inversed-by="reportedBugs" />
        <many-to-one target-entity="User" field="engineer" inversed-by="assignedBugs" />

        <many-to-many target-entity="Product" field="products" />
    </entity>
</doctrine-mapping>

# config/yaml/Bug.dcm.yml
Bug:
  type: entity
  table: bugs
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    description:
      type: text
    created:
      type: datetime
    status:
      type: string
  manyToOne:
    reporter:
      targetEntity: User
      inversedBy: reportedBugs
    engineer:
      targetEntity: User
      inversedBy: assignedBugs
  manyToMany:
    products:
      targetEntity: Product

Тут определны две ссылки на сущность User. Имя класса связанной сущности задается тэгом targetEntity. Имеет место двунаправленная связь, поэтому с помощью тэга inversedBy указываем атрибут связанной сущности, соответствующей нашему объекту. И наконец для User
// entities/User.php
/**
 * @Entity @Table(name="users")
 **/
class User
{
    /**
     * @Id @GeneratedValue @Column(type="integer")
     * @var int
     **/
    protected $id;

    /**
     * @Column(type="string")
     * @var string
     **/
    protected $name;

    /**
     * @OneToMany(targetEntity="Bug", mappedBy="reporter")
     * @var Bug[]
     **/
    protected $reportedBugs = null;

    /**
     * @OneToMany(targetEntity="Bug", mappedBy="engineer")
     * @var Bug[]
     **/
    protected $assignedBugs = null;

    // .. (other code)
}


<!-- config/xml/User.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                    http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

     <entity name="User" table="users">
         <id name="id" type="integer">
             <generator strategy="AUTO" />
         </id>

         <field name="name" type="string" />

         <one-to-many target-entity="Bug" 
                         field="reportedBugs"
                         mapped-by="reporter" />
         <one-to-many target-entity="Bug" 
                         field="assignedBugs" 
                         mapped-by="engineer" />
     </entity>
</doctrine-mapping>

# config/xml/User.dcm.yml
User:
  type: entity
  table: users
  id:
    id:
      type: integer
      generator:
        strategy: AUTO
  fields:
    name:
      type: string
  oneToMany:
    reportedBugs:
      targetEntity: Bug
      mappedBy: reporter
    assignedBugs:
      targetEntity: Bug
      mappedBy: engineer
Связи один-ко-многим уже определены на стороне владельца (Bug), поэтому поэтому просто указываем его атрибут с помощью тэга mappedBy.

 

Настройка EntityManager

 

Выборку и сохранение сущностей в базе данных обеспечивает EntityManager. Для этого его необходимо создать и настроить.
// bootstrap_doctrine.php

// See :doc:`Configuration <../reference/configuration>` for up to date autoloading details.
use Doctrine\ORM\Tools\Setup;

require_once "Doctrine/ORM/Tools/Setup.php";
Setup::registerAutoloadPEAR();

// Create a simple "default" Doctrine ORM configuration for XML Mapping
$isDevMode = true;
$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
// or if you prefer yaml or annotations
//$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/entities"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);

// database configuration parameters
$conn = array(
    'driver' => 'pdo_sqlite',
    'path' => __DIR__ . '/db.sqlite',
);

// obtaining the entity manager
$entityManager = \Doctrine\ORM\EntityManager::create($conn, $config);
Продолжение следует (или нет) ...

среда, 8 февраля 2012 г.

Debian. Обновление php на debian 5 lenny.

На данный момент доступна версия 5.3.10

Добавляем реп в /etc/apt/sources.list
deb http://php53.dotdeb.org oldstable all
deb-src http://php53.dotdeb.org oldstable all

Качаем и ставим ключ
wget http://www.dotdeb.org/dotdeb.gpg
cat dotdeb.gpg | sudo apt-key add -

Обновляем
apt-get update
apt-get dist-upgrade

вторник, 7 февраля 2012 г.

Zend Framework. Создание rss или atom потока.

Форматируем данные
//Create an array for our feed
$feed = array();
 
//Setup some info about our feed
$feed['title']          = "ZendCoding.com's Newest Stories";
 
$feed['link']           = 'http://www.zendcoding.com/newest-stories.rss';
 
$feed['charset']    = 'utf-8';
 
$feed['language']   = 'en-us';
 
$feed['published']  = time();
 
$feed['entries']    = array();//Holds the actual items
 
//Loop through the stories, adding them to the entries array
foreach($stories->fetchAll($select) as $story){
    $entry = array(); //Container for the entry before we add it on
 
    $entry['title']     = $story->title; //The title that will be displayed for the entry
 
    $entry['link']      = $story->url; //The url of the entry
 
    $entry['description']   = $story->teaser; //Short description of the entry
 
    $entry['content']   = $story->description; //Long description of the entry
 
    //Some optional entries, usually the more info you can provide, the better
    $entry['lastUpdate']    = $story->modified; //Unix timestamp of the last modified date
 
    $entry['comments']  = $story->commentsUrl; //Url to the comments page of the entry
 
    $entry['commentsRss']   = $story->commentsRssUrl; //Url of the comments pages rss feed
 
    $feed['entries'][]  = $entry;
}

Импорт в поток
$feedObj = Zend_Feed::importArray($feed, 'rss'); //Or importArray($feed, 'atom');

Вывод
//Return the feed as a string, we're not ready to output yet
$feedString = $feedObj->saveXML();
 
//Or we can output the whole thing, headers and all, with
$feedObj->send();

четверг, 2 февраля 2012 г.

Git. Установка и настройка репозитория для веб-сайта.

Установка git на debian, ubuntu:
$ sudo apt-get install git-core

Если хотим, чтобы каталоги или файлы были исключены из репозитория, надо создать файл .gitignore в каталоге сайта:
$ touch .gitignore
$ cat .gitignore >> images/
$ cat .gitignore >> robots.txt

Создаем репозиторий на локальной машине. Для этого заходим в каталог сайта:
$ git init
Initialized empty Git repository in /var/www/mysite
$ git commit -a -m "my first commit"

Доступ будет по ssh. На сервере в корне сайта создаем каталог и пустой репозиторий:
$ mkdir mysite.git && cd mysite.git
$ git init --bare
Initialized empty Git repository in /var/www/mysite/mysite.git/

Далее на сервере указываем действие, которое будет совершаться после коммита. Для этого в скрипт post-recieve добавляем переменную, которая указывает на каталог, в который будут распаковываться файлы. Ставим права на исполнение:
$ cat > hooks/post-receive
#!/bin/sh
GIT_WORK_TREE=/var/www/mysite git checkout -f
$ chmod +x hooks/post-receive

На локальной машине выгружаем репозиторий на сервер:
$ git remote add origin user@mysite:/var/www/mysite/mysite.git
$ git push origin master
user@mysite's password: 
Counting objects: 6735, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6512/6512), done.
Writing objects: 100% (6735/6735), 22.22 MiB | 191 KiB/s, done.
Total 6735 (delta 2614), reused 0 (delta 0)
To user@mysite:/var/www/mysite
 * [new branch]      master -> master

четверг, 26 января 2012 г.

PHP. Установка Codeception.









Ubuntu 11.10, PHP 5.3.9
Ставим из pear

$ pear channel-discover codeception.com/pear
$ pear install codeception/Codeception

Если не работает
$ codecept install

Обновляем PEAR
$ sudo pear channel-update pear.php.net
$ sudo pear upgrade 

Ставим Mink
$ pear channel-discover pear.symfony.com
$ pear channel-discover pear.behat.org
$ pear install behat/mink

Ставим Symfony
$ sudo pear install symfony/symfony

Ставим PHPUnit
$ sudo pear install pear.phpunit.de/PHPUnit

Теперь переходим в корень проекта, который собираемся тестировать и выполняем:
$ codecept bootstrap
$ codecept build

В итоге у нас должен быть каталог с тестами, что-то вроде этого:
итого 36
drwxr-xr-x 2 root root 4096 2012-01-26 16:57 acceptance
-rw-r--r-- 1 root root  437 2012-01-26 16:57 acceptance.suite.yml
drwxr-xr-x 2 root root 4096 2012-01-26 16:57 _data
drwxr-xr-x 2 root root 4096 2012-01-26 16:57 functional
-rw-r--r-- 1 root root  250 2012-01-26 16:57 functional.suite.yml
drwxr-xr-x 2 root root 4096 2012-01-26 16:57 _helpers
drwxr-xr-x 2 root root 4096 2012-01-26 16:57 _log
drwxr-xr-x 2 root root 4096 2012-01-26 16:57 unit
-rw-r--r-- 1 root root  136 2012-01-26 16:57 unit.suite.yml

вторник, 24 января 2012 г.

Debian. RAID.

Вылетел RAID - смотрим прогресс восстановления

leon:~# watch cat /proc/mdstat

Personalities : [raid0] [raid1] [raid6] [raid5] [raid4] [raid10] 
md2 : active raid1 sdb3[2] sda3[0]
      1462766336 blocks [2/1] [U_]
      [=>...................]  recovery =  8.8% (129999424/1462766336) finish=357.2min speed=62166K/sec
      
md1 : active raid1 sda2[0] sdb2[1]
      264960 blocks [2/2] [UU]
      
md0 : active raid1 sda1[0] sdb1[1]
      2102464 blocks [2/2] [UU]
      
unused devices: