Dec 01

Работейки по новата ми CMS система - Control Depo 3 ми се наложи да имам малко по advanced конфигурационен файл, така че за в бъдеще да ми е по-лесно да се наместват различните части на системата. Преди ползвах стандартното за едно PHP приложение - един файл config.php:


// start config
$_CONFIG = array();

// database
$_CONFIG['db_host'] = 'localhost';
$_CONFIG['db_name'] = 'project';
$_CONFIG['db_user'] = 'root';
$_CONFIG['db_pass'] = 'password';

// languages
$_CONFIG['default_language'] = 'bg';
$_CONFIG['laguages'] = array('bg' => 1, 'en' => 2, /* ... */);

// session
$_CONFIG['session_salt'] = 'SD23aeda';
$_CONFIG['session_expire'] = 4*60*60;
// ... и така много много реда код

После тази глобална променлива( $_CONFIG) когато ми трябва се вика със global и се ползва. Обаче от една страна че не е много красиво така написано, но от друга и това с global просто прави кода малко разхвърлян(на английски имат страхотна дума за това - messy) .

Това което ми трябваше основно е може да имам няколко enviroment конфигурационни файла подобно на Rails. Другото важно нещо беше да се побира в един екран, така че да не се налага да скролирам и да мога с един поглед да виждам всичко което ми трябва. И естествено да не е много сложно и да работи достатъчно бързо.

Първо погледнах Zend_Config, защо все пак са component-based-framework и може директно да видя как работи конфигурационната им система. И общо взето това което видях не ми хареса въобще. Много ми напомня на LEGOs, Play-Doh, and Programming от Jamis Buck, за която бях писал преди време(даже има я качена на видео в confreaks заедно с цялото rubyconf 2008).
От една страна колко пъти ще искам да ползвам XML и INI за конфигурация?! От една страна php си има array(), с която идеално може да се запишат всички неща който ни трябват и да са достатъчно четими. От друга самото четене на огромен xml/ini/yml/… файл отнема време и ресурси и са доста по-бавни от прост php код.

Добре че поне тук от Zend са сложили подразбиране да се ползва само php масив. Но от тук дойде 2рото ми учудване. Това а именно самата работа на имплементация на Zend_Config. Идеята е доста проста подава се масив, който се обгръща(wrap) от Zend_Config обект и след това се работи само със Zend_Config обект. Пример:


// Create the object-oriented wrapper upon the configuration data
$config = new Zend_Config(array(
    'webhost'  => 'www.example.com',
    'database' => array(
        'adapter'   => 'pdo_mysql',
        'params'    => array(
            'host'      => 'db.example.com',
            'username'  => 'dbuser',
            'password'  => 'secret',
            'dbname'    => 'mydatabase'
        )
    )
));

// Print a configuration datum (results in 'www.example.com')
echo $config->webhost;

// Use the configuration data to connect to the database
$db = Zend_Db::factory($config->database->adapter,
$config->database->params->toArray());

// Alternative usage: simply pass the Zend_Config object.
// The Zend_Db factory knows how to interpret it.
$db = Zend_Db::factory($config->database);

Идеята като цяло е много хубава, и при добро желание може човек да си направи основната част класовете му приемат Zend_Config обекти. Но има едно НО и то доста голямо. Защо ние е това? Единствения плюс който се сетих е че може да се направи immutable config обект. Но за сметка на това ще има доста излишен код и много памет за съхраниението на Zend_Config обекти и техните атрибути. А и колкото и да обичам __set/__get магиите тук ми се виждат напълно излишни защото както в примера:


$config->database->params->toArray()
// Това би представлявало нещо такова като backtrace
$config->__get('database')   // магически се търси атрибута 'database'
$config->_data['database']   // това което се връща тук пак е Zend_Config обект, който ще го нарека $object
$object->__get('params')     // пак същото за
$object->_data['params']     // друг $object
$object->toArray();          // автоматично всеки Zend_Config обект, който се стрещне му се вика Zend_Config::toArray()
$object->_data               // това за което ни трябва

Zend_Config си има и своите плюсове(immutable, секции, default стойности) и може за някое наистина “enterprise” приложение с много разчленена конфигурация (и много стабилен и мощен сървър :) ) да върши работа. Но не е за мен и аз си предпочитам “голите” array(). Все пак

No code is faster than no code

- някъде го чух това.

Така че какво реших ? Ами в предишния ми пост - PHP tips - include и return, описах метода за връщане на стойности от php файл и реших него да ползвам плюс малко правила и подрежанки. Сега имам следните файлове:

  • /config/config.php - тук ще е основната конфигурация
  • /config/enviroment/ - в тази папка ще се съдържат конфигурационните файлове за различните среди за работа, като стандартно имам 3 вида среди
  • /config/enviroment/development.php
  • /config/enviroment/test.php
  • /config/enviroment/production.php
  • /config/mailer.php - тук ще има конфигурация за mail системата, тя е изнесена в отделен файл, защото тук няма само да се връщат config данни, а и ще се вързва към smtp сървър (ако трябва), ще се настройват достъпи и други подобни неща. За разлика от другите конфигурационни файлове mailer.php се вика само когато се налага да се изпращат писма не по-рано.
  • /config/routes.php - това са настройките ми за различните пътища, подобно на Rails (пак)

Това са различните конфигурационни файлове (поне за сега).


return array(
    'database'  => array(
        'engine'    => 'mysql',
        'host'      => 'localhost',
        'name'      => 'project',
        'user'      => 'root',
        'pass'      => 'password'
    ),
    'i18n'      => array(
        'default'   => 'bg',
        'languages' => array('bg' => 1, 'en' => 2, /* ... */)
    ),
    'session'   => array(
        'engine'    => 'cookie',
        'salt'      => 'vW34Aaasa',
        'expire'    => 4*60*60
    ), // на последния ред имам , защото при добавяне на нов ред да не ми се налага да я слагам
    // а и SVN/Git ще го сметне като изтрит ред и после добавен ред
    // ... още конфигурация ...
);
// /config/enviroment/development.php - примерно
return array(
    'database'  => array(
        'engine'    => 'mysql',
        'host'      => 'localhost',
        'user'      => 'root',
        'pass'      => '',
        'name'      => 'project_dev'
    ),
    'smarty'    => array(
        'compile_check'     => true,
        'force_compile'     => false,
        'debugging'         => false,
        'caching'           => false,
        'cache_lifetime'    => 0
    ),
    'logging'           => true,
    'display_errors'    => 1,
);

А в bootstrap-а имам просто това


// ENVIROMENT е просто константа в която казва в кой режим на работа е приложението
$_CONFIG = array_merge_recursive(include($_CFGDIR . '/config.php'), include($_CFGDIR . '/enviroments/' . ENVIROMENT . '.php'));

// ... код ...
// общо взето извличам всичко което ми трябва от $_CONFIG и го разпределям по обекти
// така че да не ми трябва повече $_CONFIG
// ... код ...

unset($_CONFIG /* заедно с още няколко вече не потребни ми променливи */);

Това решение ми се вижда най-елегантно, в моя случай. Може и да не може да се нарече “система” но е достатъчно надежно и ефективно да ми свърши работа. Все пак съм фен на Convention over configuration И ако ми се наложи да имам подобен на Zend_Config обект, с които да работя мисля да не променям основните неща, а просто този обект да използва ArrayAccess, но за това друг път, че сега май малко по-дълго стана от плануваното.

Надявам се някой да намери този пост полезен и ако съвети и идеи ще се радвам да ги чуя :) .

Nov 30

Изненадващо е колко много хора не знаят, че в PHP файловете може да връщат резултат с return, който може да бъде прочетен с include / require. Даже го има и в документацията на PHP, eто пример:


// file1.php
return array('key' => 'value');

// file2.php
$arr = include 'file1.php';

echo $arr['key'];

// резултат: value

Като тук вместо include, може да се използва require (единствената разлика между двете е грешката, която възниква при проблем с отварянето на файла). Но аз лично предпочитам да използва include само когато php скрипта връща резултат, a require когато добавям нещо.

Тук трябва да се обърне внимание на 2 неща:

  1. require_once / include_once ако има return връщат стойност само първия път в който са извикани, а после нищо не връщат. Но те по принцип е добре да се избягват, особено ако се връща резултат
  2. всяка променлива / функция / клас / … която е била декларирана във include файла (file1.php в примера) си остава записана и достъпна. Така че ако в file1 се декларира $name = ‘Radoslav’; например във file2.php, $name ще е пак ‘Radoslav’. Затова е добре да се unset -ват всички ненужни глобални променливи, които не са нужни
Aug 21

Някои да не е чувал за MIT - Massachusetts Institute of Technology? Това е един от най-елитните университети в света.

Та, днес попаднах на това постче:

MIT’s Introduction to Algorithms: Lectures 1 and 2

тук има връзки към лекции от MIT на тема Алгоритми:

http://video.google.com/videoplay?docid=-2333306016564732003
http://video.google.com/videoplay?docid=6724701313234177393

и както автора казва ги има на http://ocw.mit.edu/OcwWeb/Electrical-Engineering-and-Computer-Science/6-046JFall-2005/CourseHome/index.htm - под CC лиценз :)

Тъй като в университета, където уча, за алгоритми беше отделен “цял” семестър, а и по принцип не чета много за тях из Интернет, се чувствам не в свои води. Да се надяваме това да се промени.

Jul 12

PHP tips - Array

Coding, Learn, PHP Comments Off

Аrray в PHP беше нещото което най-много ми хареса в php когато започвах да се занимава с програмиране. С времето обаче разбрах че array са много странно нещо, те са кръстоска между стандартния масив и hash таблици. По този въпрос има много материали в Интернет, където се казват плюсовете и минусите. Но това което искам се га да покажа е малко по различен поглед към Array.

Обикновено аз както и голяма част от PHP дивелопърите ползваме масивите или как нормален масив или като hash таблица, но много рядко като смесено нещо. Но при определени ситуации е доста полезно да се ползват като и двата типа на работа.

Например имам едни класове с който си дефинирам таблиците в базата данни и те генерират sql код и т.н.:


$this->createTable('product_brands', array(
	'name'	=> array('string', 'limit' => 7),
	'ord'		=> 'int',
	'visible'	=> 'boolean',
));

Което за мен и моите колеги е доста ясно и разбираемо. Още повече че самото “разчитане” на типа на всяка колона е много лесно:


function column($name, $options){
	$options = (array) $options;
	$type	 = isset($options['type']) ? $options['type'] : $options[0];

	// ....
}

Друго място на което ги ползвам е в един метод класа ми за работа с базиданни:


// WHERE visible = '1'
$db->where("'visible'='1'");
// WHERE category_id = '$id' AND visible = '1'
$db->where(array('category_id'	=> $id, 'visible' => 1));
// WHERE name LIKE '%{$name}%' AND category_id = '$id' AND visible = '1'
$db->where(array("name LIKE '%{$name}%'", 'category_id'	=> $id, 'visible' => 1));

Като where трябва да върне sql условие. А самия where метод е доста простичко написан:


function where($cond) {
	if (!$cond)
		return '';

	if (!is_array($cond))
		return 'WHERE ' . $cond . ' ';

	$vals = array();
	foreach($cond as $field => $value) {
		$vals[] = is_numeric($field) ? $value : $field . '=' . $this->quote($value);
	}

	return 'WHERE ' . join(' AND ', $vals) . ' ';
}

update: Сега се сетих, че може array да се ползват по подобен начин и в новата версия на tag функцията ми, с която генерирам html код:


// <a href="myLink">some</a>
tag(array('a', 'href' => 'myLink', 'some');
// <a href="myLink">some</a>
tag(array('a', 'some', 'href' => 'myLink');
// <span>text</span>
tag(array('span', 'text');
// <img src="image.png" alt="" />
tag(array('img', 'src' => 'image.png', 'alt' => '');

п.п. доста от тези примери ще изглеждат още по-добре ако се сбъднат желанията ми от PHP6 whishlist ( особено точки 4 или 7 ).

Jul 12

JRuby представлява JAVA имплементация на езика Ruby. Някои друг път ще пиша по-подробно защо хората правят такива неща и дали има смисъл от тях.

Как се инсталира JRuby за  Windows XP, много лесно :) . Въпреки че за хората който са свикнали да е само next -> next -> ok! няма да е толкова лесно.

  1. Слагате системната променлива JAVA_HOME, ако  я нямате дефинирана вече. Това става като дадете:
    My Computer -> Properties -> Advanced -> Environment Variables
    Давате New (като решите дали искате тази променлива да я има само за вашия акаунт или да я има за всичките възможни)
    Variable name: JAVA_HOME
    Variable value: пътя до JDK -a ви ( в моя случай е C:\Program Files\Java\jdk1.5.0_14 )
    ( не затваряйте
    Environment Variables панела, защото пак ще имаме работа в него)
  2. Изтегляте си от http://dist.codehaus.org/jruby/ последната версия на JRUBY ( за момента е тази http://dist.codehaus.org/jruby/jruby-bin-1.1.2.zip.
  3. Разахивирате съдържанието на изтегления архив в избрана от вас директория. Примерно C:\JRuby.
  4. Отворете Environment Variables панела (или се върнете от него ако не се го затворили при стъпка 1):
    My Computer -> Properties -> Advanced -> Environment Variables
  5. Добавете нова променлива:
    Variable name: JRUBY_HOME
    Variable value: директорията в която разархивирахте JRuby ( Примерната
    C:\JRuby)
  6. Променете вече съществуващата променлива PATH като добавите в нея пътя до папката bin на JRuby(C:\JRuby\bin). (пътищата там се разделят с ‘;’)
  7. Записвайте и затваряйте Environment Variables.

Това е!

Сега един бърз тест. Направете един файл което се казва test.rb в C: и напишете в него това:


include Java

import javax.swing.JFrame
import javax.swing.JLabel

frame = JFrame.new 'My first application';
frame.get_content_pane.add JLabel.new('JRuby works!')
frame.set_default_close_operation JFrame::EXIT_ON_CLOSE
frame.pack
frame.visible = true

Стартирайте cmd-то и напишете:
>jruby C:\test.rb