Постове 1 - 5 от 10 с таг tips

Apr 04

Понеделник 29.03.2010

Ruby Quick Tip: Regular Expressions in Case Statements – Как може да се използват регулярни изрази в case конструкциите в Ruby.

Вторник 30.03.2010

Odd Ruby Methods – В Ruby могат да се предефинират операторите, в поста има няколко интересни примера и размисли.

Fred Wilson’s 10 Golden Principles of Successful Web Apps – Доста внимателно го слушах и съм напълно съгласен с човека. Много добре обяснява точка по точка, какво е задължително да се гледа когато се прави уеб приложение.

Сряда 31.03.2010

Hopping in the cloud – Поредната страхотна статия от Giant Robots Smashing Into Other Giant Robots. В статията се описва как са прехвърлили своето Hoptoad приложение в “облакът” на Engine Yard. Там се описват част от промените по цялостната архитектура на приложението, които са направили.

Четвъртък 01.04.2010

Много внимавам на първи Април, всяка година е пълно с “лъжливи мини”. Някои са доста доста лесни за разгадаване като – MooJo и Objective-Sprout. Докато примерно Github: Announcing SVN Support, два дни си мислех че е шега. А пък  “svn checkout http://svn.github.com/schacon/simplegit.git” си работи :)

Sproutcore and NodeJS are stars and comets – Две от любимите ми теми – NodeJs и SprouteCore. Какво просто да кажа :)

JavaScript Style – Chris Wanstrath доста нагледно е показал какъв е JavaScript стила в света.

Петък 02.04.2010

Microsoft Gets More Involved with jQuery – От една страна, това е добре за тези които работят с Microsoft технологии, че поне на готово ще имат една добра JavaScript библиотека. Но от друга от Microsoft, като се замисля, нищо хубаво не съм видял. И много се надявам да не я развалят много или да се опитат да я наложат на всички насила.

Federico Cargnelutti – Most Visited Posts of 2009 – Много харесвам този блог, според мен е един от най-стойностните PHP блогове. Това е малко закъснял списък с най-посещаваните постове за миналата година.

Mar 24

Днес прегледах поста – Image Resizing Made Easy with PHP от Nettuts+. Доста полезен пост, за начинаещи. Но както винаги имах няколко забележки относно от кода. Мислех да ги запиша като коментар, но той се оказа доста дълъг. Затова реших да го напиша като пост.

Като за начало ще е добре да видите началния код от тук ( променената от мен версия е тук). Така промените метод по метод.

openImage

private function openImage($file)
{
    // *** Get extension
    $extension = strtolower(strrchr($file, '.'));

    switch($extension)
    {
        case '.jpg':
        case '.jpeg':
            $img = @imagecreatefromjpeg($file);
            break;
        case '.gif':
            $img = @imagecreatefromgif($file);
            break;
        case '.png':
            $img = @imagecreatefrompng($file);
            break;
        default:
            $img = false;
            break;
    }
    return $img;
}
// става:
private function openImage($file){
    if (!is_file($file)){
        throw new Exception("File {$file} doesn't exists");
    }

    switch(pathinfo($file, PATHINFO_EXTENSION)){
        case 'jpg':
        case 'jpeg': return imagecreatefromjpeg($file);
        case 'gif':  return imagecreatefromgif($file);
        case 'png':  return imagecreatefrompng($file);
    }

    throw new Exception("Invalid image extension for {$file}. Acceptable image types are jpg,jpeg,gif,png");
}

Като за начало тук PHP има вградена функция pathinfo, която извикана с PATHINFO_EXTENSION константа като втори аргумент връща какво е разширението на файла.

Също така няма смисъл от дефинирането на променливите $extension  и $img защото реално се използват един път. $extension за switch-a, а пък $img само да се  return-е.

Също така @ е нещо което НЕ ТРЯБВА да се използва!  Освен че е страшно бавно (ако някой се интересува ще обясня вътрешно какви глупости прави и защо е толкова бавна операция).  От друга страна не е много редно да се крият грешките в програмите.

За това тук съм добавил два Exeption-а. По-принцип не обичам да ги ползвам, но в този случаи е наложително.  Трябва да се провери, първо дали файла който искаме да променяме съществува и второ дали от позволените типове.

resizeImage

public function resizeImage($newWidth, $newHeight, $option="auto")
{
    // *** Get optimal width and height - based on $option
    $optionArray = $this->getDimensions($newWidth, $newHeight, $option);

    $optimalWidth  = $optionArray['optimalWidth'];
    $optimalHeight = $optionArray['optimalHeight'];

    // *** Resample - create image canvas of x, y size
    $this->imageResized = imagecreatetruecolor($optimalWidth, $optimalHeight);
    imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $optimalWidth, $optimalHeight, $this->width, $this->height);

    // *** if option is 'crop', then crop too
    if ($option == 'crop') {
        $this->crop($optimalWidth, $optimalHeight, $newWidth, $newHeight);
    }
}
// става:
public function resizeImage($newWidth, $newHeight, $option='auto'){
    list($width, $height) = $this->getDimensions($newWidth, $newHeight, $option);

    $this->imageResized = imagecreatetruecolor($width, $height);
    imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $width, $height, $this->width, $this->height);

    if ($option == 'crop'){
        $this->crop($width, $height, $newWidth, $newHeight);
    }
}

List е доста подценявана PHP функционалност. Но за да може да се работи с нея промених и getDimensions да връща просто масив с два елемента на позиция 0 и 1. ( По-долу ще обясня за нея)

После промених имената на променливите $optimalWidth и $optimalHeight. От една страна са доста дълги и докато се пишат трябва да се натиска и shift заради W и H. От друга те се състоят от две думи, като едната е “optimal” която не ни трябва, даже и грешна. Защото се превеждат “оптимална ширина” и “оптимална височина” докато реално те са просто “ширина” и “височина” (т.е. $width и $height).

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

getDimensions

private function getDimensions($newWidth, $newHeight, $option)
{

   switch ($option)
    {
        case 'exact':
            $optimalWidth = $newWidth;
            $optimalHeight= $newHeight;
            break;
        case 'portrait':
            $optimalWidth = $this->getSizeByFixedHeight($newHeight);
            $optimalHeight= $newHeight;
            break;
        case 'landscape':
            $optimalWidth = $newWidth;
            $optimalHeight= $this->getSizeByFixedWidth($newWidth);
            break;
        case 'auto':
            $optionArray = $this->getSizeByAuto($newWidth, $newHeight);
            $optimalWidth = $optionArray['optimalWidth'];
            $optimalHeight = $optionArray['optimalHeight'];
            break;
        case 'crop':
            $optionArray = $this->getOptimalCrop($newWidth, $newHeight);
            $optimalWidth = $optionArray['optimalWidth'];
            $optimalHeight = $optionArray['optimalHeight'];
            break;
    }
    return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getDimensions($width, $height, $option){
   switch ($option){
        case 'portrait':   return array($this->getSizeByFixedHeight($height),  $height);
        case 'landscape':  return array($width, $this->getSizeByFixedWidth($width));
        case 'auto':       return $this->getSizeByAuto($width, $height);
        case 'crop':       return $this->getOptimalCrop($width, $height);
        case 'exact':
        default:           return array($width, $height);
    }
}

Първо имената на променливите $newWidth и $newHeight на  $width и $height. Причините ги обясних по-горе за $optimalWidth и $optimalHeight.

Промених и формата на масива който се връща от getDimensions от асоциативен масив с ключове optimalWidth и optimalHeight на прост масив с индекси 0 и 1. Кода става доста по четим и по-горе в resizeImage метода може да се ползва List.

Премахват се и излишните променливи $optimalWidth и $optimalHeight, защото реално ни трябва просто масива с двата елемента. И от съкращаването на формата му се вижда че тези две променливи са още по-излишни.  Когато се намерят техните стойности веднага може да се върнат в масив, вместо да се чака да се стигне най-долу.

И накрая премествам ‘exact’ да бъде и default случая в switch.

getSizeByFixedHeight /  getSizeByFixedWidth

private function getSizeByFixedHeight($newHeight)
{
    $ratio = $this->width / $this->height;
    $newWidth = $newHeight * $ratio;
    return $newWidth;
}

private function getSizeByFixedWidth($newWidth)
{
    $ratio = $this->height / $this->width;
    $newHeight = $newWidth * $ratio;
    return $newHeight;
}
// става:
private function getSizeByFixedHeight($height){
    return ($this->width / $this->height) * $height;
}

private function getSizeByFixedWidth($width){
    return ($this->height / $this->width) * $width;
}

Много мразя дефинирането на излишни променливи. Защото докато се чете кода трябва да се помни какво има във съответната променлива.

Когато се погледне примерно getSizeByFixedHeight в оригиналната версия, кода се чете:

- Дефинирам $ratio който е ширината на снимката разделена на височината и
- Дефинирам нова височина, която е новата ширина умножена по $ratio ( мисля и се сещам какво има в $ratio )
- Връщам новата височина

Докато моята версия се чете просто:

- Разделям оригналата ширината на снимката на височината, полученото го умножавам по $width и връщам стойността.

getSizeByAuto

private function getSizeByAuto($newWidth, $newHeight)
{
    if ($this->height < $this->width)
    // *** Image to be resized is wider (landscape)
    {
        $optimalWidth = $newWidth;
        $optimalHeight= $this->getSizeByFixedWidth($newWidth);
    }
    elseif ($this->height > $this->width)
    // *** Image to be resized is taller (portrait)
    {
        $optimalWidth = $this->getSizeByFixedHeight($newHeight);
        $optimalHeight= $newHeight;
    }
    else
    // *** Image to be resizerd is a square
    {
        if ($newHeight < $newWidth) {
            $optimalWidth = $newWidth;
            $optimalHeight= $this->getSizeByFixedWidth($newWidth);
        } else if ($newHeight > $newWidth) {
            $optimalWidth = $this->getSizeByFixedHeight($newHeight);
            $optimalHeight= $newHeight;
        } else {
            // *** Sqaure being resized to a square
            $optimalWidth = $newWidth;
            $optimalHeight= $newHeight;
        }
    }

    return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getSizeByAuto($width, $height){
    if ($this->height < $this->width){
        return array($width, $this->getSizeByFixedWidth($width));
    }

    if ($this->height > $this->width){
        return array($this->getSizeByFixedHeight($height), $height);
    }

    if ($height < $width){
        return array($width, $this->getSizeByFixedWidth($width));
    }

    if ($height > $width){
        return array($this->getSizeByFixedHeight($height), $height);
    }

    return array($width, $height);
}

Колкото по надълбоко влизат вложените структури, толкова по-грозен е кода. Реално тук всяка ситуация може да се изнесе в отделен if и просто ако този if се изпълни се връща резултат веднага и така докато се стигне до края.

getOptimalCrop

private function getOptimalCrop($newWidth, $newHeight)
{

    $heightRatio = $this->height / $newHeight;
    $widthRatio  = $this->width /  $newWidth;

    if ($heightRatio < $widthRatio) {
        $optimalRatio = $heightRatio;
    } else {
        $optimalRatio = $widthRatio;
    }

    $optimalHeight = $this->height / $optimalRatio;
    $optimalWidth  = $this->width  / $optimalRatio;

    return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
}
// става:
private function getOptimalCrop($width, $height){
    $ratio = min($this->height / $height, $this->width /  $width);
    return array(
        $this->width  / $ratio,
        $this->height / $ratio
    );
}

Мin е  друга малка PHP функция с чиято помощ този код се по изчиства. Защото реално на нас ни трябва минималната стойност от двете зависимости.

Тук пиша на два реда връщането на масива, отново заради четимостта.

saveImage и crop

private function crop($optimalWidth, $optimalHeight, $newWidth, $newHeight)
{
    // *** Find center - this will be used for the crop
    $cropStartX = ( $optimalWidth / 2) - ( $newWidth /2 );
    $cropStartY = ( $optimalHeight/ 2) - ( $newHeight/2 );

    $crop = $this->imageResized;
    //imagedestroy($this->imageResized);

    // *** Now crop from center to exact requested size
    $this->imageResized = imagecreatetruecolor($newWidth , $newHeight);
    imagecopyresampled($this->imageResized, $crop , 0, 0, $cropStartX, $cropStartY, $newWidth, $newHeight , $newWidth, $newHeight);
}
public function saveImage($savePath, $imageQuality="100")
{
    // *** Get extension
             $extension = strrchr($savePath, '.');
                $extension = strtolower($extension);

    switch($extension)
    {
        case '.jpg':
        case '.jpeg':
            if (imagetypes() & IMG_JPG) {
                imagejpeg($this->imageResized, $savePath, $imageQuality);
            }
            break;

        case '.gif':
            if (imagetypes() & IMG_GIF) {
                imagegif($this->imageResized, $savePath);
            }
            break;

        case '.png':
            // *** Scale quality from 0-100 to 0-9
            $scaleQuality = round(($imageQuality/100) * 9);

            // *** Invert quality setting as 0 is best, not 9
            $invertScaleQuality = 9 - $scaleQuality;

            if (imagetypes() & IMG_PNG) {
                 imagepng($this->imageResized, $savePath, $invertScaleQuality);
            }
            break;

        // ... etc

        default:
            // *** No extension - No save.
            break;
    }

    imagedestroy($this->imageResized);
}
// става:
private function crop($optimalWidth, $optimalHeight, $width, $height){
    $x = ( $optimalWidth  / 2) - ( $width  /2 );
    $y = ( $optimalHeight / 2) - ( $height /2 );

    $crop = $this->imageResized;

    $this->imageResized = imagecreatetruecolor($width , $height);
    imagecopyresampled($this->imageResized, $crop, 0, 0, $x, $y, $width, $height , $width, $height);
}

public function saveImage($savePath, $imageQuality="100"){
      switch(pathinfo($savePath, PATHINFO_EXTENSION)){
        case 'jpg':
        case 'jpeg':
            if (imagetypes() & IMG_JPG){
                imagejpeg($this->imageResized, $savePath, $imageQuality);
            }
            break;

        case 'gif':
            if (imagetypes() & IMG_GIF){
                imagegif($this->imageResized, $savePath);
            }
            break;

        case 'png':
            if (imagetypes() & IMG_PNG){
                // Scale quality from 0-100 to 0-9
                // Invert quality setting as 0 is best, not 9
                $invertScaleQuality = 9 - round(($imageQuality/100) * 9);
                imagepng($this->imageResized, $savePath, $invertScaleQuality);
            }
            break;
    }

    imagedestroy($this->imageResized);
}

Промените по saveImage и crop са просто стилистични, така че няма да се спирам много подробно на тях.

Финални думи

Като започнах да чистя кода си обещах, че няма да променям имената на методите. Но ако питате мен от публичните методи – resizeImage и saveImage бих махнал “Image” и да станат само resize и save. Защото класа най-често би се използвал с променливи кръстени $resizer, $resize, $image и подобни и затова rеsize и save биха достатъчно ясно.

Като цяло урока от Nettuts+ е много добър. Въпроса че според мен в уроците за начинаещи, които главно ползват Nettuts+, трябва да има максимално добър код. Без излишен “шум”.

Надявам се този малък анализ да е бил полезен на някого. Приемам всякакви критики и съвети, под формата на коментари :)

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 09

Доста често ми се е налагало да пиша нещо такова

if ($something){
  $a = 1;
  $b = 'a';
} else {
  $a = '2'
  $b = 'c';
}

За което обикновено ми трябват 5-6 и повече реда, което ме дразни. Но вече на 2-3 места ползвам следния синтаксис:

list($a, $b) = $something ? array(1, 'a') : array(2, 'b');

Може и да не е по-бързо и за някои хора да е “леко” не четимо, но аз си го харесвам. От една страна веднага се вижда, че от операцията $a, $b излизат със нови стойности. Две, вижда се кои са тези стойности. Три доста по-кратко и cбито е.

По принцип list e доста подценявана функционалност в php и доста често грешно използвана. Преди много време я използвах за router в моите проекти и се държеше много добре:

list($controller, $action, $id) = explode('/', $url);

Друг доста малко известен факт, е че всъщност list може да се използва и с ArrayAccess обекти. Още малко трикове има тук.

Надявам сe някои да намери това за полезно.

Aug 09

В един от последните ми проекти ми се налагаше да генерирам много html елементи с javascript. Като за това основно използвах new Element() и Element#insert от Prototype. Като въпреки че като цяло двата метода са много изчистени и правят кода доста добре четим, винаги може и по-добре.

В началото написах един прост builder, но като малко го поизползвах тук и там, се оказа , че като код е доста по-малко, но не и като четимост. Затова написах и тези две малки добавки към Element#insert които ми спестиха доста писане и направиха кода още по-четим и добър. И същевременно показаха (за пореден път) няколко от добрите страни на Prototype.

Element.insert(element, { into: content });
Element.insert(element, [content, content, content]);

Element#insert(element, { into: content });

Тук просто добавих нов insert position, into. Общо взето е ясно какво прави той добавя елемента в друг елемент. Защото поне на 20 места имах нещо такова(или много подобно:

var element = new Element('div', {/* атрибут */});
document.body.appendChild(element);

// като с into това просто става
var element = new Element('div', {/* атрибут */}).insert({into: document.body });

За тези, които са запознати как работи точно от вътре Element#insert, няма да е никак трудно да разберат как съм добавил ‘into’. Prototype използва един object, който да съхранява всички insert функции – before, after, top, bottom. Той е
Element._insertionTranslations, и когато бива извикан Element#insert(element, {position: content}) се вика грубо казано Element._insertionTranslations[position](element, content). Заради това добавянето на нов insert метод е много лесно и става просто така:

Element._insertionTranslations.into = function(element, node){
	node.appendChild(element);
};

Внимание! Element._insertionTranslations не е част от Public Api-то на Prototype и затова може да се промени в бъдещи версии!

Страничен ефект

Element#insert(element, { into: content }) има много интересен ефект, за който не се бях сетил когато го писах. Но когато случайно “открих” бях много доволен.
Element#insert може да приема не само html елементи, но и нормални javascript обекти, които имат метод toElement ( тои трябва да връща html елемент). И така примерно имам клас подобен на Custom Select класа ми от ControlDepo 3 Widgets, който има един контейнер, към когото може да се добавят елементи. Чрез into могат да се правят такива неща:

var list = new (Class.create({
	initialize: function(ul){
		this.element = $(ul);
	},
	toElement: function(){
		return this.element;
	}
}))('some');

Тук, за декларацията на класа, ползвам прост номер описан, по-добре тук.

Element.insert(element, [content, content, content]);

Идеята тук е да може да се предава масив от елементи, които да се добавят в element.

var ul = new Element('ul');
ul.insert(li1);
ul.insert(li2);
ul.insert(li3);
// ... и т.н.

// като това понякога го пиша и така
[li1, li2, li3].each(Element.insert.curry(ul));

// с новата версия става просто
ul.insert([li1, li2, li3]);

Такава функционалност може да стане по 2 начина. Първо като пиша директно в кода на Element#insert ( или си направя мой си fork на Prototype). И втория вариянт е Function#wrap.

Колкото и да се изкушавам от първия вариант реших само да пусна един билен ( ticket ) на Prototype и да се надявам да го добавят като функционалност.  И след това си написах каквото ми трябваше чрез Function#wrap:

Element.addMethods({
    insert: Element.insert.wrap(function(insert, element, insertation){
        if (!Object.isArray(insertation)) return insert(element, insertation);

        element = $(element);
        insertation.each(insert.curry(element));
        return element;
    })
});

Тези двете добавки ги има в git.github – http://gist.github.com/164751. Там ще ги актуализирам ако правя промяна.

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