Постове 6 - 10 от 25 в категория PHP

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 файлове плюс няколко други неща, ще има още един пост.

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

Jul 18

В по-голямата част от PHP кода ми е OOP. Но все пак имам и няколко функции, като три от тях бих искал да споделя тук. Те са tag / func_array_cut / profile.

Функцията tag

Както се и казва функцията tag генерира html тагове. Тъй като в 99% от времето PHP връща html, е много важно да имам нещо с което бързо и лесно да си създавам тагове. Основно тази функция я използвам в различни помощни методи:

function tag($tag, $options, $content = null){
    if (is_string($options) && !is_string($content)){
        list($content, $options) = array($options, $content);
    }

    $attributes = '';

    if (is_string($options)){
        $attributes = ' ' . $options;
    } else if (is_array($options)){
        foreach($options as $key => $value){
            $attributes .= ' ' . $key . '="' . htmlspecialchars($value, ENT_COMPAT, 'utf-8') . '"';
        }
    }

    return '<' . $tag . $attributes . (is_string($content) || strlen($content) > 0 ? ">{$content}</{$tag}>" : ' />');
}

Таg може да се извиква по няколко начина, като основната ми идея беше да не се налага да се замислям кои аргумент какъв е – атрибут или текст.

// <input type="text" value="text" />
tag('input', array('type' => 'text', 'value' => 'text'));

// <input type="text" value="&quot;qoute&quot; text" />
tag('input', array('type' => 'text', 'value' => '"qoute" text'));

// <div>content in div</div>
tag('div', 'content in div');

// 3 начина за генериране на
// <a href="http://www.pixeldepo.com">Pixeldepo</a>
tag('a', array('href' => 'http://www.pixeldepo.com'), 'Pixeldepo');
tag('a', 'Pixeldepo', array('href' => 'http://www.pixeldepo.com'));
tag('a', 'href="http://www.pixeldepo.com"', 'Pixeldepo');

Функцията array_cut

Едно нещо което е изключително досадно в STRICT режима на PHP, е непрекъснатите проверки isset / empty за това дали в даден масив има даден ключ. Затова създадох array_cut, тя проверява дали в дадения масив го има ключа и ако го има му връща стойността, ако я няма връща $default стройност. Като същевременно ключът се изтрива от оригиналния масив.
Това като се замисля е първата функция която написах за PHP framework-а който използвам ControlDepo 3.

function array_cut(array &$array, $key, $default = null){
    if (!isset($array[$key])){
        return $default;
    }

    $value = $array[$key];
    unset($array[$key]);

    return $value;
}

Тази функция е изключително полезна и може да се използва в редица случаи, ето два примера:

// това може да се напише така:
$name = isset($_POST['name']) ? $_POST['name'] : '[no-name]';
// може да се напише така:
$name = array_cut($_POST, 'name', '[no-name]');

// използване на array_cut за примерна функция за генериране на url-та
function url($options){
    $url = '/' . join('/', array_filter(array(
        array_cut($options, 'controller'),
        array_cut($options, 'action', 'index'),
        array_cut($options, 'id')
    )));

    if ($query = http_build_query($options)){
        $url .= '?' . $query;
    }

    return $url;
}
// използване
// -> /products/view/1
url(array('controller' => 'products', 'action' => 'view', 'id' => 1));

// -> /users/index?page=1
url(array('controller' => 'users', 'page' => 1));

// -> /index?sort=name
url(array('sort' => 'name'));

Преди време си мислех, че в PHP5.3/6 смисълът от функцията ще се намали след като се добавиха ?: и  ifsetor. Но просто ?: не е толкова мощен колкото ми се искаше, а ifsetor няма да го има.

Функцията profile

Докато другите 2 функции ги използвам много (и също така цялото ControlDepo 3 ги ползва). Функцията profile() е просто бърз начин за замерване, докато работя. В действителност тази функция се използва чрез чрез друга моя функция p(), която просто вика profile(), но записва резултата в лога на request-a и така малко по-лесно може да го анализирам резултата:

function profile($scope = '__default__'){
    static $time = array();

    if (isset($time[$scope])){
        return microtime(true) - array_cut($time, $scope, 0);
    }

    $time[$scope] = microtime(true);
}

Тя се ползва просто така:

// имаме 2 нива на profile
profile('test');
sleep(1);

profile();

sleep(1);

echo profile(), "\n";
echo profile('test');

Надявам се тези 3 функции да са полезни на някои и ако някои има идеи как може да се подобри някоя от тях, ще се радвам да го сподели.

May 21

Наскоро имах да направя следното, елементарно нещо, в проект:

За дадена поръчка има отстъпка в зависимост от сумата:

  • до 1000 лева  отстъпка 0%
  • от 1000 – 3000 лева отстъпка 2%
  • от 3000 – 5000 лева отстъпка 3%
  • от 5000 – 10000 лева отстъпка 4 %
  • над 10000 лева  отстъпк 5%

Нищо сложно и преди съм правил такива неща, под 2 минути работа.
Тъй като беше края на работния ден, имах малко свободно време и си помислих, как бях написал кода за подобна задача преди време. Затова измъкнах от “прашните” архиви няколко проекта в които имаше подобни задачи и успях да синтезирам четири варианта, които съм писал преди време. Като ги видях така събрани на едно място един след друг, реших да пусна един пост, и дано някой да ги намери за полезен. И така започвам ги по ред на номерата.

Вариант 1 преди около 5 години

function getDiscountPercent($sum){
	$percent = 0;

	if ($sum >= 1000 && $sum < 3000){
		$percent = 2;
	} else if ($sum >= 3000 && $sum < 5000){
		$percent = 3;
	} else if ($sum >= 5000 && $sum < 10000){
		$percent = 4;
	} else if ($sum >= 10000){
		$percent = 5;
	}

	return $percent;
}

Това беше в началото на моя кариера, и като цяло е много добро решение, което доста хора биха използвали. Лесно се вижда какво става в кода и е доста по-просто е от варианти 2 и 3. Общо взето само излишното дефиниране на променливата $percent е по-дразнещо в случая.

Вариант 2 преди около 3 години

function getDiscountPercent($sum){
	$points = array(1000, 3000, 5000, 10000);
	$percent = 0;
	foreach($points as $key => $value){
		if (isset($points[$key+1])){
			if ($sum >= $value && $sum < $points[$key+1]){
				$percent = $key + 2;
				break;
			}
		}
	}

	return $percent;
}

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

Вариант 3 преди около 1 година

function getDiscountPercent($sum){
	return $sum < 1000 ? 0 : ($sum < 3000 ? 2 : ($sum < 5000 ? 3 : ($sum < 10000 ? 4 : 5)));
}

Само да вметна тук, че в оригиналната за проекта версия са само четири вида отстъпки, кода който съм показал сега приспособен за горната задача. Това е бързичко и кратко решение, поне аз лесно си го чета, но надали някой друг ще има същия успех.
Поне до колкото си спомням в този случаи в началото беше с 2 вида отстъпки, после се увеличиха до горе споменатите  четири вида отстъпки. Което и проблема на този вариант при повече варианти на отстъпките, става мазало.

Вариант 4 преди около 4~5 месеца

function getDiscountPercent($sum){
	if ($sum < 1000){
		return 0;
	}
	if ($sum < 3000){
		return 2;
	}
	if ($sum < 5000){
		return 3;
	}
	if ($sum < 10000){
		return 4;
	}
	return 5;
}

Това решение представлява просто разпънат Вариант 3, което от своя страна си е чист Вариант 1 без излишния шум. Има два аспекта тук, което е добре да се отбележат. Първо променливата от Вариант 1 – $percent, е премахната, за сметка на това в кода в момента в който се разбере колко е отстъпката , тя се връща което прави кода много ясен и бърз. Второто нещо, го имаше във Вариант 3(просто не се виждаше), е доста по-малкия брои логически операции, докато във Вариант 1 се проверяваха всички граници тук просто се гледа горната граница. Така това е най-доброто решение до сега, но винаги може и по-добре.

Финален вариант преди около няколко дни

function getDiscountPercent($sum){
	if ($sum < 1000)  return 0;
	if ($sum < 3000)  return 2;
	if ($sum < 5000)  return 3;
	if ($sum < 10000) return 4;

	return 5;
}

Това същност е кода който написах, и който започна този пост. Като функционалност си е едно към едно с Вариант 4, единствената разлика е че съм го подредил малко по-добре(според мен), така че от пръв поглед да виждам от каква сума каква отстъпка става.

Много ми е интересно след 2 – 3 години как бих написал подобен код  (да се надяваме на ruby ;) ). Ако някой има идеи как може още по-добре да се напише това нека ги сподели.

May 02

Вчера отделих целия ден да направя някои промени по блога, като основно имах доста забележки към темата (iTheme), която ползвам.

Първо започнах да правя малки промени по дизайна

  • сложих да се виждат таговете към даден пост
  • промених (с малко помощ от колега) изцяло извеждането на коментарите
  • формата за добавяне на коментар претърпя малки графични промени
  • тема вече е изцяло на български език (тук таме може да е останало нещо чуждоезично, но малко по-малко що променям)
  • доста по-добра 404 страница, за която използвах от Google Webmaster Tools - Enhance 404 pages
  • промени извеждането на постовете при търсене.

Аз в мрежата

После започнах промени по секциите

  • Добавих нова страница “Аз в мрежата“, в която има информация за част от различните ми регистрации из мрежата. Скоро ще има още връзки, просто на този етап нямам подходящи лога за тази страница (трябваше да има и страница “Аз съм”, но за нея не остана време :( )
  • Добавих “Подобни постове” към всеки пост, като за него използвам плъгина Contextual Related Posts
  • Изтрих от sidebar-a мета информацията и на нейно място сложих таговете в сайта
  • Преминах през Google Webmaster Tools и пооправих някой връзки в сайта, които не работеха, също така промених и няколко meta description-и и други дребни неща

Промени по кода

И след това започнах да работя по интересната част. Докато оправях темата забелязах, че колкото и да е красива от долу като код е просто … ужасна. Още повече самия WordPress не използва нещо като Smarty, и го кара на голо php (в този пост – “Be smart with smarty“, съм си казал мнението за Smarty). Всички шаблони на темата общо взето изглеждаха така:

<?php get_header(); ?>
	<div id="content">
		// кода за съответния шаблон
	</div>
	<div id="footer">
		<a href="...">WP Theme</a> &amp;
		<a href="...">Icons</a> by <a href="...">N.Design Studio</a>
	</div>
</div>
<?php get_sidebar(); ?>
<?php get_footer(); ?>

след малко refactoring в шаблон кода стана така, като преместих общите части в хедъра и футера:

<?php get_header(); ?>
	// кода за съответния шаблон
<?php get_footer(); ?>

Мина ми през главата, дали да не направя някои плъгин за WordPress, който позволява ползването на Smarty и/или layout файлове, подобни на тези които използвам от години в ControlDepo и които ги има в Rails, но в крайна сметка реших, че през лятото най-вероятно ще мина на някое мое блог решение.

След като подредих  и пренаписах шаблоните, се насочих към самата html структура, в която беше пренебрегнато ползването на ul елементи и други дребни неща който ме дразнеха. Но най-големия проблем от който най-вероятно страда доста SEOто на NeXt е че липсваше h1 елемент (т.е. имаше го но просто винаги беше просто NeXt), а той трябва все пак да е заглавието на самата страница.

JavaScript / CSS / Images

Поради използването на много плъгини се беше натрупало огромно количество външни javascript и css файлове, което много вреди на цялостното зареждане на сайта. Така че минах и събрах всичките css файлове в един all.css (останаха само print.css и css файловете от Highlight Source Pro плъгина, но там още не искам да пипам).

С JavaScript нещата седяха малко по сложно поради това че използвах plugin за live търсенето и за още 2-3 малки неща, решението беше да се отърва от всички плъгини и да си напиша собствени версии използвайки ControlDepo 3 Widgets.  От него използвах тези компоненти:

Отделих javascript-a в два файла – all.js и frontend.js ( както права във всичките си проекти напоследък ), Във all.js са библиотечните файлове – Prototype, Script.aculo.us, no.ie6.js, ControlDepo 3 Widgets а във frontend.js са скриптовете специално за блога ми livesearch, widgets и други малки javascript глезотии. По-важното е в случая е това че вече имам стабилна основа върху която да надграждам в бъдеще.

Всички javascript файлове са най-долу, така че няма смисъл да правя dom:loaded а мога директно да изпълнявам кода си. Също така вече и целия код е unobtrusive, и поне на този етап ако даден посетител няма javascript или му е изключен, всичко в сайта ще си работи.

Последната стъпка беше оптимизацията на снимките за това използвах просто ruby gem-a – smusher, който прекарва всяка снимка през smush.it. Така отстраних с около 40 KB размера на всички картинки от блога.

Финални думи

Като цяло за себе си съм си доволен от работата, която свърших по блога. Останаха някои неща за бъдещето:

  • live comment preview
  • най-сетне да имам about страница
  • да взема под IE да видя как се вижда NeXt
  • да се по съберат снимките в css spirites
  • почистване на css файловете, т.е. изтриване на излишните стилове и оптимизация
  • gzip на css / javascript файловете

п.п. от цялото тази работа около WordPress, ми хрумнаха две идеи за plugin, първия да е Smarty, а втория да е нещо подобно на sprockets което събира всички css / javascript във един файл gzip-ва го, добавя му etags и т.н. Незнам дали е възможно да се направи такова нещо с WordPress, ако някой има желание за нещо такова, с удоволствие бих му помагал.

Jan 30

Излезе PHP 5.3.0 beta 1 Johannes Schlüter обяви това вчера във php.internals чрез съобщение, което може да се намери тук:

php.internals: PHP 5.3.0beta1

Най-голямата промяна е че вече няма да има OOP в closures, като повече информация имат тук по въпроса. Другото важно нещо е че PHP5.3.0 е feature freeze фаза, т.е. няма да има промени, касаещи синтаксиса и функциолността на езика, а само bug fixes.

Колкото и да съм доволен от това че PHP 5.3 наближава, не мога да скиря и разочарованието си от основното нещо което се чакаше в PHP – namespaces. Или по-точно техния разделител, който в момента мога с ръка на сърцето да кажа че не струва. Cпоред мен това е най-голямата дизайнерска грешка в целия език и породи много дискусии и висички бяха на едно мнение че това не струва. Поне за мен предишния разделител се вписваше идеялно и даже в документацията си към проектите така си описвам класоветe Package1::Package2::Class ( при истинско име на клас Package1_Package2_Class)

// преди, се мислеше да е така, красиво нормлано
Package1::Package2::Class::saticMethod()

// сега вече е
Package1\Package2\Class::StaticMethod()

// удря малко на
// C:\foo\bar\baz

// а това е валиден, грозен код
PHP\Namespaces::$separator->sucks();

Разбирам защо е решено да се смени ::, заради конфликта с статичните методи, но все пак, е можело да се направи да се хвърлят E_ERROR при конфликт. И то май доста ще се използва “use” ключовата дума зада избягвам да ползвам сепаратора.

Но няма как такъв е живота и той продължава , добре че има и други езици :) / :(