Typ wyliczeniowy – SplEnum i alternatywy

We wpisie Magiczne „cyferki” pisałem o używaniu stałych zamiast magicznych cyfr, które dla osoby nie znającej szczegółów projektu nic nie znaczą. Stałe zamiast cyfr są postępem, jednak pozostaje problem poprawności danych. Na przykład wartość 1 może mieć status obiektu firmy, osoby i wielu innych obiektów w aplikacji. W dodatku dla każdego obiektu może on oznaczać coś zupełnie innego. Z drugiej strony przy większej liczbie statusów w jednym obiekcie i mniejszej w innych można przez pomyłkę przypisać status innego obiektu, który jest poza zakresem danego obiektu.

Krótko mówiąc pozostaje problem ograniczenia możliwości ustawienia statusu tylko z określonego zakresu/typu. Typ jest tutaj istotną wskazówką – m.in. w Javie są tytułowe typy wyliczeniowe, które pozawalają zdefiniować nowy typ, dla określonego zbioru wartości, a język poprzez typowanie parametrów metody przypilnuje, żeby ustawiać statusy tylko dozwolonego typu.

W PHP również jest typowanie parametrów (tylko dla typów złożonych) i jak się okazuje w bibliotece SPL znajduje się nawet klasa dla typów wyliczeniowych – SplEnum. Jej budowa jest dość prosta a przykłady z manuala klarowne. Jest jednak jeden problem – nie jest standardowym elementem języka i wymaga doinstalowania rozszerzenia PECL na serwerze. Dla posiadaczy serwerów dedykowanych/VPS nie stanowi to problemu, jednak przy uruchamianiu aplikacji na hostingu współdzielonym zainstalowanie tego rozszerzenia zależy od administratora.

Jak wspominałem trochę wyżej budowa klasy jest dość prosta, więc zamiast zastanawiać się nad dostępnością/możliwością instalacji rozszerzenia na serwerze można napisać odpowiednik tej klasy samemu, poniżej przykładowa implementacja.

<?php

/**
 * My implementation of alternative SplEnum 
 * @link http://php.net/manual/en/class.splenum.php
 */
class MyEnum 
{
    /**
     * Value of object
     * @var mixed
     */
    private $value;
    
    /**
     * Name of const with default value
     * @var string
     */
    private $defaultName = '__default';
    
    /**
     * Reflection object
     * @var ReflectionClass
     */
    private $reflection;

    /**
     * Create object
     * @param mixed $initialValue
     * @throws UnexpectedValueException
     */
    public function __construct($initialValue = null)
    {
        if ($initialValue && in_array($initialValue, $this->getConstList())) {
            $this->value = $initialValue;
        } elseif (!$initialValue && $this->getDefault()) {
            $this->value = $this->getDefault();
        } else {
            throw new UnexpectedValueException('Value not a const in enum '. get_class($this));
        }
    }
    
    /**
     * Get array of class consts
     * @param boolean $includeDefault
     * @return array
     */
    public function getConstList($includeDefault = false)
    {
        $tmp = $this->getReflection()->getConstants();
        if (!$includeDefault && array_key_exists($this->defaultName, $tmp)) {
            unset($tmp[$this->defaultName]);
        }
        return $tmp;
    }
    
    /**
     * Get value of object
     * @return mixed
     */
    public function getValue()
    {
        return $this->value;
    }
    
    public function __toString()
    {
        return (string) $this->getValue();
    }
    
    /**
     * Get default value from class
     * @return mixed
     */
    protected function getDefault()
    {
        return $this->getReflection()->getConstant($this->defaultName);
    }

    /**
     * Get instance of class reflection
     * @return ReflectionClass
     */
    protected function getReflection()
    {
        if (!$this->reflection) {
            $this->reflection = new ReflectionClass($this);
        }
        
        return $this->reflection;
    }
}

Myślę, że kod jest czytelny i jasny. Dla przypomnienia odczytanie stałych zadeklarowanych w klasie jest realizowane z wykorzystaniem mechanizmu refleksji, który pozwala na analizowanie struktury/budowy obiektu w trakcie wykonania kodu.

Teraz bardzo prosty kod z przykładem użycia:

<?php
function processOption(Opcje $opcja)
{
    echo($opcja);
}

class Opcje extends MyEnum
{
    const __default = 2;
    
    const OPCJA1 = 1;
    const OPCJA2 = 2;
}

$obj = new Opcje();
processOption($obj);

Powyżej znajduje się definicja funkcji, która przyjmuje parametr typu Opcje i definicja tej klasy. Klasa ogranicza się jedynie do dziedziczenia po MyEnum i deklaracji stałych, cała funkcjonalność jest dziedziczona. Przykładowy kod wyświetli tylko 2, ponieważ taka jest wartość domyślna typu wyliczeniowego i typ zgadza się z oczekiwanym typem parametru funkcji.

W przypadku próby utworzenia obiektu z wartością spoza zdefiniowanych stałych zostanie rzucony wyjątek UnexpectedValueException, natomiast przy próbie przekazania do funkcji parametru innego typu:

<?php
processOption(2);
// wynik: Catchable fatal error: Argument 1 passed to processOption() 
// must be an instance of Opcje, integer given, called in...

Wygląda na to, że problem jest rozwiązany – nie można pomyłkowo przypisać innej wartości i tego nie zauważyć.

Dodatkowo, na polskiej planecie PHP pojawił się jakiś czas temu wpis z pomysłem implementacji typów wyliczeniowych w oparciu o koncepcję z Javy – odsyłam do tego wpisu (autorem jest Sebastian Malaca). Warto również przyjrzeć się komentarzom pod tym wpisem, ponieważ można tam znaleść kolejną/bardziej rozbudowaną alternatywę dla SplEnum.

PART – tester hostingu

Często jest tak, że aplikacja korzysta z jakichś bibliotek, modułów lub zakłada pewne opcje konfiguracyjne. Przykładem może być sprawdzanie MIME pliku z wykorzystaniem modułu Fileinfo. PHP w wersjach przeznaczonych na Windowsa wymaga ręcznej edycji php.ini i włączenia wczytywania tego modułu. Efekt jest taki, że w środowisku *nix wszystko działa, a po uruchomieniu tego samego kodu na Windowsie jest problem…

Mając chwilę wolnego czasu napisałem kawałek kodu, który pomaga znajdować tego typu problemy z zależnościami, konfiguracją przed uruchomieniem docelowej aplikacji – mowa o PHP Applications Requirements Tester (PART). Projekt znajduje się na moim koncie w serwisie GitHub.

Użycie jest według mnie bardzo proste – przykładowy plik z testami znajuje się również na GitHubie. Wystarczy utworzyć nowy plik PHP, dołączyć plik part.php, utworzyć obiekt PART i wywoływać na nim odpowiednie metody z testami środowiska. Taki zestaw umieszcza się w miejscu gdzie ma być aplikacja i uruchamiamy – w przeglądarce lub z shella. Mogą wystąpić różnice w konfiguracji między środowiskiem shell i webowym (ponieważ mają osobne pliki php.ini, więc najlepiej uruchamiać test z tego środowiska w którym będzie działała docelowa aplikacja.

Poniżej źródło przykładowego zbioru testów i wyniki:

<?php

require_once 'part.php';

$obiekt = new PART();
$obiekt->checkConfigHasValue('display_errors', 0)
    ->checkPDO()
    ->checkPDODriver('mysql')
    ->checkPHPVersion(5.4)
    ->checkDisableMagicQuotes()
    ->checkDefaultTimezone('Europe/Warsaw')
    ->checkNotWindowsServer();

Wynik konsolowy:

PHP Applications Requirements Tester - Report

Warning: The CLI environment may be different from web environment
Test name                                Result     Expected   Environment
================================================================================
Config [display_errors]                  OK         0                    
Extension loaded [PDO]                   OK                              
PDO driver [mysql]                       OK                              
PHP version                              OK         5.4 [>=]   5.4.9-4ubuntu2.3
Config [magic_quotes_gpc]                OK         0                    
Config [magic_quotes_runtime]            OK         0                    
Config [magic_quotes_sybase]             OK         0                    
Default timezone                         failure    Europe/Warsaw Europe/Berlin
Not Windows OS serwer                    OK                              

Warning: The CLI environment may be different from web environment

Wynik HTML:
PART wynik

Na załączonych raportach widać, różnicę pomiędzy uruchomieniem testu z konsoli i przeglądarki – w pierwszym wypadku display_errors jest wyłączone zgodnie z oczekiwaniami, w drugim przeciwnie.

Obsługa nieprzechwyconych wyjątków

Wyjątki są lepszą alternatywą dla obsługi błędów w aplikacjach od wywoływania trigger_error czy zwracania jakiegoś umówionego kodu w wypadku wystąpienia błędu. W związku z tym w ramach kontynuacji wątku z poprzedniego wpisu dziś kilka słów na temat wyjątków w PHP z nastawieniem na obsługę niewyłapanych przez catch wyjątków.

Obsługa wyjątków

Klasyczne podejście do obsługi wyjątków zakłada użycie throw i try/catch (od PHP 5.5 można również używać finally). Może to wyglądać w taki sposób:

<?php

$id = 1;
// kod wykonany bez wzgledu na wystapienie wyjatku
echo 'punkt 1' . PHP_EOL;
try
{
    echo 'punkt 2' . PHP_EOL;
    // kod wykonany bez wzgledu na wystapienie wyjatku
    if (is_int($id)) {
        echo 'punkt 3' . PHP_EOL;
        // zrob cos dla podanego ID
    } else {
        echo 'punkt 4' . PHP_EOL;
        throw new Exception('Błędne ID');
    }
    echo 'punkt 5' . PHP_EOL;
    // kod wykonany TYLKO jezeli nie bylo wyjatku
    
} catch (Exception $ex) {
    echo 'punkt 6' . PHP_EOL;
    // kod wykonany tylko w razie wystapienia wyjatku
    // np. "uratowanie zaistniałej sytuacji", zapis do logu, e-mail do admina
}
echo 'punkt 7' . PHP_EOL;
// kod wykonany bez wzgledu na wystapienie wyjatku

Dla wartości $id = 1; przebieg będzie wyglądał następująco, punkty: 1, 2, 3, 5, 7. Natomiast dla $id nie będącego liczbą całkowitą wykonanie podąży ścieżką: 1, 2, 4, 6, 7.

Powyższy przykład jest mocno uproszczony, ale można zaobserwować pewną rzecz – wyjątki pozwalają na łatwe poinformowanie o jakimś problemie jak również jego obsługę i w dodatku są luźno powiązane. W jednym fragmencie wykonanie kodu jest przerywane poprzez rzucenie wyjątku i nie ma potrzeby zajmowania się jego obsługą. Tym zajmuje się inny fragment kodu, który w bloku try/catch zamyka wywołania mogące sprawiać problemy.

Większego sensu nabiera to w momencie, kiedy w różnych warstwach aplikacji ma miejsce rzucenie wyjątku i jego złapanie. Dodatkowo wyjątki mogą być rzucane również z poziomu bloku catch, na przykład warstwa utrwalania danych łapie wyjątek związany z błędem zapisu do bazy, wykonuje cofnięcie transakcji i ponownie wrzuca wyjątek. Następnie jest on łapany w „wyższej” warstwie aplikacji i w ramach jego obsługi jest pokazywany komunikat dla użytkownika o „problemach technicznych z wykonaniem operacji”. Kolejnym przykładem jest stosowanie w aplikacji bibliotek zewnętrznych – autor biblioteki umieszcza w kodzie tylko rzucanie wyjątków. Natomiast złapaniem i obsługą tych wyjątków zajmuje się programista pracujący nad projektem, w którym dana biblioteka została wykorzystana.

Przechwytywanie nie wyłapanych wyjątków

W ramach ludzkiego błędu, zmiany w kodzie biblioteki lub innego powodu może się pojawić rzucenie wyjątku w miejscu, które nie było na to przygotowane. W efekcie zamiast strony/aplikacji w przeglądarce pojawia się komunikat:

Fatal error: Uncaught exception '[typ_wyjatku]' with message '[komunikat_wyjatku]' 
in [sciezka_pliku] on line [numer_linii]

Sposobem na takie sytuacje jest utworzenie i zarejestrowanie własnej funkcji obsługującej globalnie przypadki nie złapania rzuconych wyjątków – chodzi o funkcję set_exception_handler. Oprócz wywołania tej funkcji należy wcześniej zdefiniować funkcję, która będzie odpowiadała za faktyczną obsługę wyjątków. Poniżej przykład:

<?php
function obsluzWyjatek($wyjatek) {
  echo 'Wyjatek [' . get_class($wyjatek) .']: '. $wyjatek->getMessage() . PHP_EOL;
}
set_exception_handler('obsluzWyjatek');

echo 'punkt 1' . PHP_EOL;
throw new UnexpectedValueException('Podales zla wartosc');
echo 'punkt 2' . PHP_EOL;

Uruchomienie tego kodu spowoduje wyświetlenie:

punkt 1
Wyjatek [UnexpectedValueException]: Podales zla wartosc

Co istotne nie zostanie wykonany żaden kod po rzuceniu wyjątku (tutaj: wypisanie „punkt 2”) – w zwykłym podejściu „pole rażenia” wyjątku (czyli operacje, które nie zostaną wykonane jeżeli wyjątek się pojawi) jest określone blokiem try. natomiast w tym wypadku go nie ma, więc wykonanie całej reszty skryptu zostaje udaremnione. Wyjątkiem jest uruchomienie zdefiniowanej funkcji obsługującej nieprzechwycone wyjątki. Co się w niej znajdzie zależy od programisty, natomiast najważniejszy jest fakt, że można się przed takimi sytuacjami zabezpieczyć i zamiast błędu PHP pokazać użytkownikowi chociażby własną wersję „blue screen”. Można również nieco rozbudować obsługę wyjątków w funkcji w zależności od typu (w przykładzie został użyty wyjątek UnexpectedValueException). W przeciwieństwie do wielu instrukcji catch wyłapujących wyjątki o określonych, coraz bardziej ogólnych typach, funkcja obsługi jest jedna dla wszystkich typów.

Linki:

Własna obsługa błędów i zamiana na wyjątki

Błędy zdarzają się wszędzie i każdemu, natomiast raczej nie jest wskazane „chwalenie się” nimi przed użytkownikami aplikacji.

Konfiguracja

Podstawowyą kwestią jest ich ukrycie na środowisku produkcyjnym przed oczami użytkownika, tym bardziej że w stosie wywołań mogą się pojawić wrażliwe dane użytkownika/aplikacji/systemu/inne. Do ukrycia „wpadki” używa się właściwości display_errors (php.ini). Właściwość pozwala włączyć lub wyłączyć pokazywanie błędów oraz skierować je na odpowiednie wyjście:

  • Off – wyłączenie pokazywania błędów (do użycia na „produkcji”)
  • stderr – przekierowanie błędów na STDERR (działa tylko z CGI/CLI)
  • On/stdout – pokazanie błędów/przekierownie na standardowe wyjście, czyli pokazanie komunikatów w treści strony wyświetlonej użytkownikowi (przydatne w trakcie programowania)

Jednak ukrycie komunikatów błędów w żaden sposób nie rozwiązuje faktycznego problemu w aplikacji. Do tego potrzebne są komunikaty błędów i to tych znaczących. Do filtrowania komunikatów służy dyrektywa error_reporting. Jest to poziom raportowania błędów, które będą wyświetlane/pokazywane – możliwych wartości jest cała masa, a dodatkowo można je łączyć – lista stałych.

Obie dyrektywy (w zależności od uprawnień czy konfiguracji środowiska) mogą zostać ustawione na poziomie pliku php.ini, definicji vhosta, pliku .htaccess, czy w samym kodzie PHP. Ostatni wariant wygląda następująco:

<?php
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);

var_dump(ini_get('display_errors')); // pokaże: string '1' (length=1)
var_dump(ini_get('error_reporting')); // pokaże: string '22519' (length=5)

W PHP wartości dyrektyw można zmieniać i odczytywać funkcjami ini_set() / ini_get(). Dla dyrektywy error_reporting jest dedykowana funkcja – wywołana z argumentem zmienia wartość, natomiast bez argumentu zwraca obecną wartość, nie zmienia to jednak faktu, że można używać standardowych funkcji. Oczywiście nie wszystkie dyrektywy można zmieniać w powyższy sposób – tutaj znajduje się lista dyrektyw z oznaczeniem sposobu zmiany wartości i opis poziomów.

Wywołanie błędu

<?php
echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;

Do wywołania błędu z poziomu kodu służy funkcja trigger_error() – pierwszy argument to własny komunikat, natomiast drugi to typ błędu. W zależności od tego jak bardzo błąd jest poważny wykonywanie skryptu jest kontynuowane lub przerywane, co widać poniżej w wyniku wykonania powyższego kodu:

punkt 1

Notice: Test wywolania bledu in /home/cim/public_html/Testy/error.php on line 12
Call Stack
#	Time	Memory	Function	Location
1	0.0001	230360	{main}( )	../error.php:0
2	0.0002	231520	trigger_error ( )	../error.php:12

punkt 2

Fatal error: Test wywolania bledu in /home/cim/public_html/Testy/error.php on line 14
Call Stack
#	Time	Memory	Function	Location
1	0.0001	230360	{main}( )	../error.php:0
2	0.0003	231520	trigger_error ( )	../error.php:14

Po wystąpieniu błedu typu „Notice” wykonywanie przebiega dalej (np. pokazanie „punkt 2”), natomiast błąd typu „Fatal error” przerywa wykonanie (brak wyświetlenia „punkt 3”).

Przechwytywanie

Błąd został zgłoszony i przy odpowiedniej konfiguracji został wyświetlony na ekranie. Docelowo jednak efekt ma być zupełnie inny, błędy powinny być zgłaszane do odpowiednich osób. PHP posiada przydatną funkcję – error_log – która wspomaga wysyłanie e-maili, dodawnie komuniaktów błędów do systemowego logu, tworzenie własnego logu. Jednak można również samemu wysyłać e-maile (poprzez serwer SMTP) czy zapisywać do pliku.

W obu przypadkach należy w jakiś sposób przechwycić informację o wystąpieniu błędu. Realizuje się to poprzez utworzenie własnej funkcji i zarejestrowanie jej za pomocą set_error_handler().

<?php
define('LOG_SEPARATOR', ';');
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);

function obsluga($kodBledu, $komunikat, $plik, $linia, $zmienne)  {
    $elementy = array(
        date('H:i:s'),
        $kodBledu,
        $komunikat,
        $plik,
        $linia,
        //print_r($zmienne, true)
    );
    error_log(implode(LOG_SEPARATOR, $elementy) . PHP_EOL, 3, 'logi_'. date('Ymd') .'.log');
}

set_error_handler('obsluga');

echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania notice', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;

Powyższy kod spowoduje, że na ekranie zostanie wyświetlone tylko: „punkt 1, punkt 2, punkt 3”. Natomiast zarejestrowana funkcja przechwytująca informację o błędzie obsłuży w sposób „cichy” testowe wywołania błędów. Do funkcji przekazywane są takie informacje jak: poziom błędu, komunikat oraz plik i linia, w których błąd został znaleziony/zgłoszony i zmienne towarzyszące kontekstowi wywołania.

W tym przypadku dodatkowo dane zostały zapisane do pliku (generowanego osobno dla każdego dnia) z logami, gdzie poszczególne dane (oddzielone średnikami) zawarte są w jednym wierszu razem z godziną wystąpienia problemu. W 13 wierszu znajduje się przekazanie do pliku z logami zrzutu zmiennych, które w tej chwili jest nie aktywne, ponieważ zaburzałoby jednowierszowy podział dla zdarzeń. Jednak warto zapisywać również te dane, ponieważ pomagają w odnalezieniu źródła problemu.

Przy rozwiązywaniu problemu lub diagnozie błędu, który występuje w bardzo specyficznych okolicznościach przydatna jest funkcja debug_backtrace(). Zwraca ona tablicę ze stosem wywołań od uruchomienia skryptu do wywołania jej samej – dla każdego wywołania podane są szczegółowe informacje pozwalające prześledzić kolejne kroki wykonania skryptu.

Zamiana na wyjątki

Ostatnią kwestią, którą poruszę jest zamiana wystąpień klasycznych ostrzeżeń/błędów na wyjątki. W PHP istnieje specjalnie zdefiniowany w tym celu typ wyjątku – ErrorException. Sposób użycia został pokazany poniżej poprzez modyfikację poprzedniego przykładu.

<?php
define('LOG_SEPARATOR', ';');
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);

function obsluga($kodBledu, $komunikat, $plik, $linia)  {
    throw new ErrorException($komunikat, $kodBledu, 0, $plik, $linia);
}

set_error_handler('obsluga');

echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania notice', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;

Natomiast efekt uruchomienia będzie się prezentował następująco (tylko początek):

punkt 1
Fatal error: Uncaught exception 'ErrorException' with message 'Test wywolania notice' in ...

Funkcja przechwytująca błędy przekazała swoje parametry do wywołania wyjątku ErrorException, wyjątek został rzucony… i niestety zakończyło to się błędem, ponieważ wyjątek nie został złapany. Żeby temu zaradzić należałoby każde miejsce, w którym błąd jest zgłaszany ręcznie zamknąć w bloku try/catch i/lub zdefiniować globalną funkcję obsługującą nieprzechwycone wyjątki. To jest jednak temat na jeden z następnych wpisów.

Klasyczne błędy generowane przez interpreter lub zgłąszane ręcznie pozwalają na zakomunikowanie programiście, że coś jest nie tak i pomagają w rozwiązaniu problemu. Jednak spełniają rolę tylko informacyjną – zapisanie do logu, wysłanie powiadomienia na e-mail czy pokazanie użytkownikowi strony z informacją o błędzie aplikacji. Natomiast w oparciu o mechanizm obsługi błędów nie ma możliwości reagowania na konkretne i przewidywalne problemy (jeżeli pojawia się wywołanie trigger_error() to programista przewidział ewentualny problem). Rozwiązaniem jest wykorzystywanie wyjątków, które mogą być przechwytywane globalnie (tak jak błędy) oraz lokalnie dając programiście szanę na obsługę konkretnego zaistniałego problemu (a nie tylko w oparciu o poziom błędu).

Linki:

Dzień programisty

Dziś jest 28 dzień roku, czyli dzień programisty 😀

Święto wypada w 256 dniu roku – 13 września lub 12 września w roku przestępnym. Natomiast dziś jest to dodatkowo piątek, 13-go. Jednak nie bądźmy przesądni, bo wszystko za pewne będzie dobrze. Ja wszystkim programistom życzę wydajniejszego sprzętu, leprzych narzędzi, genialnych technologii/języków/frameworków oraz dobrego szefa i zgranego teamu. A poza programowaniem to każdy sam najlepiej wie, czego sobie życzyć 🙂

Może dziś w ramach świętowania wieczór off-line?

Zmiana include_path

Pliki projektu mogą być różnie rozlokowane w katalogach a ich łączenie odbywa się z wykorzystaniem funkcji include()/require() i ich wariantów *_once()1. Podawanie ścieżek bezwzględnych zupełnie mija się z celem ponieważ inna będzie na komputerze programisty inna na hostingu. Natomiast zmienianie ich przy przegrywaniu jest stratą czasu i narażaniem się na ewentualne problemy (przy zapomnieniu o zmianie czy błędzie w ścieżce).

Rozwiązaniem tego problemu jest dyrektywa konfiguracyjna include_path i używanie ścieżek względnych.

Bieżącą wartość dyrektywy można sprawdzić w następujący sposób:

<?php
print get_include_path(); // u mnie: .:/usr/share/php:/usr/share/pear

Znak kropki w wyniku odnosi się do bieżącego katalogu, natomiast dwukropek to separator ścieżek (można ustawić ich wiele). /usr/share/php i /usr/share/pear to ścieżki, które będą przeszukiwane przy próbie dołączenia plików z użyciem ścieżki względnej.

Jest już przykład odczytania wartość include_path, teraz czas na zmianę wartości i odpowiedź na pytanie: po co to robić?

Do zmiany wartości służy funkcja set_include_path() z pomocą wspomnianej funkcji do odczytu bieżącej wartości oraz stałej PATH_SEPARATOR.

<?php
// wariant 1
set_include_path('/jakas/sciezka/do/katalogu'); 
print get_include_path(); 
// pokaże: /jakas/sciezka/do/katalogu

// wariant 2
set_include_path(get_include_path() . PATH_SEPARATOR . '/jakas/sciezka/do/katalogu');
print get_include_path(); 
// pokaże: .:/usr/share/php:/usr/share/pear:/jakas/sciezka/do/katalogu

Wariant pierwszy pokazuje jak ustawić tylko swoje wartości include_path nadpisując przy tym poprzednie (należy uważać z takim użyciem, ponieważ usunięcie znaku kropki może spowodować wiele komplikacji). Wariant drugi jest dużo mniej inwazyjny i zamiast usuwania poprzednich wartości zwyczajnie dodaje do nich nową oddzielając ją za pomocą stałej (separatorem są znaki dwukropka).

Ostatnią kwestią jest przydatność możliwości zmiany tej dyrektywy. Jedną z sytuacji jest używanie pakietów PEAR, których nie ma globalnie na hostingu i jest jakiś problem z ich zainstalowaniem przez administratora. Rozwiązaniem może być utworzenie w obrębie konta własnego zbioru pakietów PEAR i dodanie ścieżki do tego katalogu do include_path. Późniejsze użycie tych pakietów jest takie samo jak tych dostępnych globalnie.


1 – najwygodniejszym sposobem dołączania plików z klasami jest użycie autoładowania (które ostatecznie również sprowadza się do dołączania plików), o którym również postaram się niedługo coś napisać

PHPCon 2013 i poprzednie edycje

W najbliższym czasie odbędzie się konferencja PHPCon 2013. A dokładnie to 25-27 października 2013 w Szczyrku. Jest to chyba największa w kraju konferencja związana z językiem PHP a dodatkowo przyjeżdżają też prelegenci z zagranicy. W ubiegłbym tygodniu zakończyło się głosowanie na tegoroczną agendę, z którą można się zapoznać tutaj. Wszyscy zainteresowani konferencją mogą uzyskać więcej informacji na stronie konferencji, a nawet zapisać się – rejestracja uczestników jest jeszcze otwarta.

Jednak oprócz informacji na temat tegorocznego PHPCon chcę też zwrócić uwagę na materiały konferencyjne z poprzednich edycji, ponieważ na prawdę są warte uwagi.

Linki:

  1. Strona tegoroczej edycji PHPCon
  2. Materiały z PHPCon 2010
  3. Materiały z PHPCon 2011
  4. Materiały z PHPCon 2012

NetBeans IDE 7.4 beta

NetBeans jest moim ulubionym IDE, jeżeli chodzi o język PHP i od kilku dni przyglądam się wersji beta kolejnego wydania – 7.4. Jak zwykle jest po kilka nowości dla różnych kierunków zastosowań IDE – HTML5, PHP, JavaScript, Java, Java EE i innych.

Nowości istotne dla programisty aplikacji internetowych (PHP/JavaScript/HTML5) to między innymi:

Z nowości, z których w tym czasie korzystałem, najbardziej przypadł mi do gustu rozwój pluginu obsługi Gita. Pojawiła się na przykład obsługa rebase czy amend i całe menu Git zostało zorganizowane na nowo – bardziej użytecznie. Na uwagę zasługuje również zmiana nazewnictwa opcji na bardziej analogiczne do poleceń „rdzennego”, konsolowego Gita. Teraz obsługa jest bardziej intuicyjna przy przesiadce z konsoli na plugin Git w IDE.

Polecane linki:

Magiczne „cyferki”

Co jakiś czas mam do czynienia z różnymi starszymi projektami napisanymi przez inne osoby. Jedną z bardziej irytujących mnie rzeczy są „magiczne” cyferki. Może jest ktoś (oprócz autora), kto patrząc na poniższy fragment kodu będzie wiedział jakich dokładnie użytkowników zwraca metoda getUzytkownicy()? Niestety ja się do takich osób nie zaliczam.

class Uzytkownik extends AppModel
{
    public function getUzytkownicy() {
        return $this->find('all', array(
            'conditions' => array(
                'Uzytkownik.status' => array(2, 3)
            )
        ));
    }
}

Fragment kodu jest wzorowany na klasie modelu dla CakePHP 1.3, ale pokazany problem jest niezależny od szkieletu programistycznego lub jego braku – to brak czytelności. Nie znająć frameworka z kodu można odczytać, że zwraca on kolekcję obiektów użytkowników o statusach… no właśnie – 2 i 3. Co to znaczy? To najczęściej wie autor i osoby, które musiały już tego poszukać. Pozostali dopiero będą szukali znaczenia tytułowych cyferek. Najczęściej w takich sytuacjach brakuje również komentarza, czy to wewnątrz metody czy PHPDoc dla metody.

Prywatnie rozwiązuję powyższy problem w taki sposób:

class Uzytkownik extends AppModel
{
    const STATUS_NIEPOTWIERDZONY = 1;
    const STATUS_POTWIERDZONY = 2;
    const STATUS_ZABLOKOWANY = 3;
    const STATUS_USUNIETY = 4;
    
    /**
     * Zwraca tablice uzytkownikow potwierdzonych i zablokowanych
     * @return array
     */
    public function getUzytkownicy() {
        return $this->find('all', array(
            'conditions' => array(
                'Uzytkownik.status' => array(
                    self::STATUS_POTWIERDZONY, 
                    self::STATUS_ZABLOKOWANY
                )
            )
        ));
    }
}

Kod z drugiego fragmentu jest o wiele bardziej czytelny i klarowny. Przede wszystkim nie wymaga dodatkowego przeszukiwania kodu i aplikacji, żeby dowiedzieć się co oznaczają poszczególne numery statusów – nazwa stałej jest wystarczająco jasna. Dobrym pomysłem jest również pisanie komentarzy dla metod i późniejsze ich utrzymywanie. Dlaczego utrzymywanie? Ponieważ jeżeli statusy w zapytaniu zostaną zmienione a komentarz nie to będzie wprowadzał w błąd podczas programowania (IDE wyświetli w podpowiedziach metod tylko treść komentarza). W takim wypadku można by stwierdzić, że brak komentarza dla metody jest lepszy niż błędy komentarz – przy braku będziemy się sugerować nazwą lub zajrzymy do kodu, żeby sprawdzić działanie.

Dodatkową kwestią jest też nazwa metody: getUzytkownicy(), można by ją zastąpić getPotwierdzeniIZablokowaniUzytkownicy(). Jednak w tym momencie nazwa metody robi się już trochę długa. Prywatnie metody tego typu staram się nazywać raczej odnosząc się do miejsca lub kontekstu ich wykorzystania. Na przykład getListaUzytkownikowDlaAdm(). Taka nazwa również jest dość długa, ale pozwala dużo lepiej zidentyfikować zastosowanie metody. Dodatkowo w jednej z aplikacji, które rozwijam/utrzymuję obiekt odwzorowujący proces ma 18 statusów wskazujących na różne etapy procesu. Tworząc metodę, której nazwą byłby zlepek np. nazw 6 statusów (których nazwy mogą i często są wieloczłonowe), które powinny mieć obiekty pokazywane na liście byłoby pomyłką.

Podsumowując mam mały apel:

  • nazywajmy klasy/metody/stałe/zmienne/etc w sposób bardziej odzwierciedlający to co robią;
  • piszmy i utrzymujmy komentarze (najlepiej PHPDoc, żeby można było z nich korzystać poprzez IDE i wygenerować dokumentację);
  • nie używajmy „magicznych” cyferek tylko stałych.

Dziękuję 🙂

Hello, World!

<?php
print "Hello, World!";
?>

Nazywam się Jacek Skirzyński i nad założeniem bloga zastanawiałem się już od jakiegoś czasu. Natomiast dopiero chwila wolnego w weekendowe popołudnie sprawiła, że ten blog faktycznie powstał a teraz można przeczytać pierwszy wpis.

Od kilku lat pracuję jako programista PHP a poza tym rozwijam się. Blog pomoże mi uporządkować wiedzę, przemyślenia, pomysły czy jakieś nowinki. Jest dużo lepszy niż notatki na karteczkach, które mają to do siebie, że potrafią się zgubić. Dodatkowo mam nadzieję, że wpisy będą przydatne nie tylko dla mnie, ale ktoś jeszcze z nich skorzysta.

PS moje pierwsze programy czy skrypty nie wyświetlały legendarnego „Hello, World!”, ale na blogu dałem się skusić na ten zwrot;)