Постове 1 - 5 от 18 с таг Coding

Mar 14

Наскоро се наложи да поработя по един доста стар PHP проект, по който не бях работил от години. И там видях нещо, което ведна опреличих като code smell (Всъщност видях много такива неща, но само на това ще обърна внимание).

$cart = CartManager::getCurrentCart();
$cart->setClientInfo('atype',    $user['account_type']);
$cart->setClientInfo('fname',    $user['fname']);
$cart->setClientInfo('lname',    $user['lname']);
$cart->setClientInfo('street',   $user['street']);
$cart->setClientInfo('postnum',  $user['postcode']);
$cart->setClientInfo('epost',    $user['mail']);
$cart->setClientInfo('city',     $user['city']);
$cart->setClientInfo('company',  $user['company']);
$cart->setClientInfo('orgnum',   $user['orgnum']);
$cart->setClientInfo('phone',    $user['phone']);
$cart->setClientInfo('country',  $user['country']);

Което ако идвате от  Java света може и да ви изглежда нормално, но мен ме дразни.

На първо време направих метода setClientInfo да връща $this зада може да използвам прост method chaining. И кода стана така:

CartManager::getCurrentCart()
    ->setClientInfo('atype',    $user['account_type'])
    ->setClientInfo('fname',    $user['fname'])
    ->setClientInfo('lname',    $user['lname'])
    ->setClientInfo('street',   $user['street'])
    ->setClientInfo('postnum',  $user['postcode'])
    ->setClientInfo('epost',    $user['mail'])
    ->setClientInfo('city',     $user['city'])
    ->setClientInfo('company',  $user['company'])
    ->setClientInfo('orgnum',   $user['orgnum'])
    ->setClientInfo('phone',    $user['phone'])
    ->setClientInfo('country',  $user['country']);

Малко по-добре, но пак ми изглеждаше не естествено. Затова просто добавих възможността да може да се предава масив като аргумент. И така стана доста добре:

CartManager::getCurrentCart()->setClientInfo(array(
    'atype',    => $user['account_type'],
    'fname',    => $user['fname'],
    'lname',    => $user['lname'],
    'street',   => $user['street'],
    'postnum',  => $user['postcode'],
    'epost',    => $user['mail'],
    'city',     => $user['city'],
    'company',  => $user['company'],
    'orgnum',   => $user['orgnum'],
    'phone',    => $user['phone'],
    'country',  => $user['country']
));

За нещастие в този проект, нямаше как да направя кода стане още по-чист, без да се налага пренаписване на голяма част от проекта:

CartManager::getCurrentCart()->setClient($user);
Nov 29

От много време не ми харесва как работя със key събития в javascript. Общо взето в по-голямата част от случаите  съм писал нещо такова:

document.observe('keyup', function(e){
	switch(e.keyCode || e.charCode){
		case Event.KEY_UP:		up();		break;
		case Event.KEY_DOWN:	down();		break;
		case Event.KEY_RIGHT:	right();	break;
		case Event.KEY_LEFT:	left();		break;
		case Event.KEY_SPACE:	fire();		break;
	}
});

Което е меко казано досадно и затова реших да направя нещо по въпроса. Реших да използвам custom събитията на Prototype.js, на които ставам все по-голям фен в последно време. И така, взех нормалните key събития – keyup / keydown / keyup и към тях добавих натиснатия бутон. Като за сега се поддържат – backspace, tab, return, esc, left, up, right, down, delete, home, end, pageup, pagedown, insert. И така горния код става така:

document.observe('keyup:up',	up);
document.observe('keyup:down',	down);
document.observe('keyup:right',	right);
document.observe('keyup:left',	left);
document.observe('keyup:space',	fire);

Кey custom събитията може да намерите тук. Ето и 2 малки демонстрации на това как работят – test1 и test2. Също така и в моя javascript playground в github ги има.

За момента само на Firefox и Safari съм ги пробвал, защото за тях ми трябваха. Но не виждам причина да не работят и под други браузъри.

Ако някои има идеи и предложения как това може да се подобри, ще се радвам да ги чуя :)

п.п. Докато работех с keypress, разбрах че имало проблем със keypress в Safari, за повече информация – http://ejohn.org/blog/keypress-in-safari-31/

Sep 23

От началото на месеца, голяма част от работата ми включваше почти изцяло пренаписване на повечето unit тестове на основните ми PHP библиотеки. Основната причина за това беше че повечето тестове бяха на повече от година, като през този период само съм добавял код за нови методи. И през това време тестове станах доста големи и тежки затова се наложи това малко “освежаване”

Основното нещо което исках да тествам по-добре този път бяха ‘protected’ методите на голяма част от класовете. Примерно основния ми Controller клас има само 2 публични метода и всичко друго е protected. Защото идеята на този клас  е да се ползва “отвътре”.

Преди какво правех? Просто си дефинирах един помощен decorator клас през който виках protected методите. Обаче това винаги ми се е струвало доста “мръсно” решение.  Затова реших потърся дали някой не се е сетил нещо по-добро от мен. И затова потърсих -  “php testing protected methods“. И от там намерих този пост – Testing protected Methods in Unit Tests от Frontalaufprall. Което на този етап ми се вижда перфектното решение. И така с много малко промени се получи този код с който тестването на protected методи става изключително лесно:

function getProtectedMethodsProxy($className){
	$proxyClassName = $className . 'PrivateMethodsProxy';

	if (!class_exists($proxyClassName, false)){
		eval('
			class ' . $proxyClassName . ' extends ' . $className . ' {
				public function __call($method, $arguments){
					$method = str_replace("_", "", $method);

					if (!method_exists($this, $method)){
						return parent::__call($method, $arguments);
					}

					return call_user_func_array(array($this, $method),  $arguments);
				}
			}
		');
	}

	if (func_num_args() == 1){
		return new $proxyClassName();
	}

	$class = new ReflectionClass($proxyClassName);
	return $class->newInstanceArgs(array_slice(func_get_args(), 1));
}

Начин на ползване:

$class = getProtectedMethodsProxy('Controller_Action');
$class->_redirect('http://next.pixeldepo.com');
// и вариант с параметри в конструктора
$class = getProtectedMethodsProxy('SomeClass', 1, 2, 3); // new SomeClass(1, 2, 3);
$class->_someVeryProtectedMethod('test');

Все пак eval не винаги е толкова лош :)

Aug 24

В последните ми проекти почти не използвам event handler-и, който да закачам за даден елемент. Вместо това използвам event delegation и от там само слушам за събития, които се случват на долу по DOM дървото. В един по-предишен пост описвам как делегирам submit. Но за нещастие има и други проблемни събития, които не слушат спецификация зa HTML събития – а именно focus / blur.

Рядко използвам focus / blur или когато ги използвам, не ги делегирам. Но преди 3-4 седмици един познат се допита до мен. Защото имаше страница с повече от 150 input елемента, като на всеки от тях добавяше прилично количество event handler-и. Като се сложи и това, че динамично се добавяха и изтриваха доста елементи, нещата не бяха никак розови.

Като за начало всички handler-и се насочиха към document елемента, и това доста ускори нещата. Обаче focus / blur все още изискваше да се слагат отделно на всеки. Но тогава дойде на помощ хака за делегиране на focus/blur и custom събитията на Prototype.js и от тези две неща се роди това доста елегантно решение ( има го и на gist ):

(function() {
    function focusInHandler(e){
        Еvent.element(e).fire("focus:in");
    }
    function focusOutHandler(e){
        Еvent.element(e).fire("focus:out");
    }

    if (document.addEventListener){
        document.addEventListener("focus", focusInHandler, true);
        document.addEventListener("blur", focusOutHandler, true);
    } else {
        document.observe("focusin", focusInHandler);
        document.observe("focusout", focusOutHandler);
    }
})();

Интересното е, че от 1-2 седмици ми се налага да използвам доста често новите focus:in / focus:out , което от една страна доста изчиства и ускорява кода ми. Но и от друга страна прави доста по userfriendly самите компоненти които правя, така че всички са доволни. Като даже ако имам време мисля да пренапиша и кода за делегиране на  submit. :)

Aug 17

Днес почетох този пост във veselin.bg, пробвах в един коментар да събера най-основните “стандарти” по който пиша PHP код. Обаче коментарът доста набъбна и реших да го направя като пост.

В PHP за разлика от повечето (да не кажа всички) програмни езици няма точно установен стандарт структориране и писане на кода. Донякъде това се дължи на самата история на езика и това, че написан на PHP3 код може да върви дори и на PHP6. От друга страна PHP се води най-лесния за учене език, точно защото каквото и да напишеш все ще ти излезе резултат. Тази страна на PHP колкото и позитиви да има, като цяло според мен е грешка на самия език и придава малко статус “аматьори” на PHP програмистите му.

В послените години започват да се оформя що годе няколко стандарта на писане, но като цяло поне според мен докато PHP5.3 – PHP6 не започне да се ползва масово нещата няма много да се променят.

Основния начин да се започне да се работи по стандарт е да се работи с някой PHP framework, защото всички те си имат свой достатъчно добре дефинирани “стандарти” и самия framework ви кара да пишете по неговия стандарт за да изглежда кода ви добре. И тъй като никой PHP framework неможа да ме спечели, аз си направих мой. Който вече от 1 година се ползва във всички проекти които правя за Pixeldepo ( и които са на PHP разбира се ) и съм страшно доволен от това как работи. В неговите coding conventions съм “взаимствал” доста идеи, главно от  Zend Framework и CodeIgniter.

Именуване на променливи и функции и методи.

Променливите и методите ми са със CamelCase, фунцкиите са с подчертавки. Което е малко старанно за повечето хора, но поне според мен има известна логика ( иначе нямаше да пиша по този начин де :) ). Самия PHP, като език ако се погледне дългия списък със фунцкии ще се види че основно те са с почертаване и когато се ползва фунция инстиктивно знам че е такава. Докато класовете  (повечето от тях) използват CamelCase стандарт подобно на Java и когато се работи с класове изглеждат някак естествено. По принцип използвам много малко фунции и обикновенно те са просто wrapper/shortcut към някой клас.

Не използвам долни черти за разделянето на private променливи фунции. Пример за променлива / фунция / метод

$name = '';
$totalPrice = "";

function url_for($params){ /* ... */ }

class Foo {
public function isValid(){ /* ... */ }
}

Именуване на класове

Начина на именуване на класовете в Zend Framework, ми допадна най-много, от висички останали които бях гледал в различни PHP-та. Общо взето идеята е в името на класа да се съдърже “namespece” му. И същевременно класа да се намира в директория, пътя на която отговаря на това име. Примери:


// намира се в ActiveRecord/Plugin/Attachable.php
class ActiveRecord_Plugin_Attachable {}

// намира се в Model/Validation/Errors.php
class Model_Validation_Errors {}

// намира се в Tool/Script/Genererate/Applition.php
class Tool_Script_Generate_Application {}

Това е много ползено за _autoload на класове и доста ще улесни бъдеща интеграцията на PHP5.3 – PHP6 namespace-овете. Само да вметна, че доста хора не го знаеха за autoload, най-добре да се ползва spl_autoload.

Като тук проблема с абстрактните класове го решавам, като ако класа е абстрактен се кръщава Base. Тъй като Zend имаха проблем със класове като Zend_Controller_Plugin_Abstract, който в щеше да стане просто клас Abstract от PHP6 и щеше да е в namespace Zend\Controller\Plugin.  А Abstract е ключова дума и неможе да има клас който се казва така. При мен такъв клас се казва просто Base.

С подобна структура на класовете е много удобна и въпреки че много хора казват, че с такива имена писането става трудно.  Има доста техники с който се избягва писането на целите имена. Като пример ще дам моята script функция която изпълнява script  класове.

script('generate:model'); // Прави клас Tool_Script_Generate_Model го изпълнява
script('db:dump:data'); // Прави клас Tool_Script_Db_Dump_Data и го изпълнява

// като могат много лесно се добавят плъгини:
script('your:custom:script'); // Това е клас Tool_Script_Your_Custom_Plugin

В заключение…

Основната идея на стандартите е не само вие да може да си разчетете кода след 1-2 месеца, но и други хора да могат да работят над него. Също така е важно и да не ви е толкова срам от него след време, което рядко ще стане, но е важно да се опита. Kода да изглежда естествено в средата в която работите било то PHP / Ruby / Java и т.н. За PHP това не е лесно, защото самия език не изглежда естествено понякога.

За стандарти за писане лично аз препоръчвам тези на Zend Framework и донякъде тези на CodeIgniter. Ако пишете изцяло собствени неща, ако не пишете по стандартие на системата в която сте.

To be continue…

Това е тема с продължение, която най-вероятно утре ще продължа :)

Тъй като исках да напиша още за коментари / гетъри и сетъри / php файлове плюс няколко други неща, ще има още един пост.

Ако имате някви коментари и / или съвети може да ги споделите. Също така ще се радвам и да чуя и по какви стандарти пишете и вие.