Nocne plątaniny myśli

Nie rzadko kończę pracę długo po północy. Wyłączam komputer, gaszę lampkę i najczęściej idę spać. Niekiedy jednak zanim się wsunę pod kołdrę zdarza mi się podejść do okna balkonowego i zadumać na chwilę. Mieszkam przy ulicy, która w dzień tętni życiem. Człowiek zamyka szczelnie okno chcąc się odizolować od wszechobecnego zgiełku. Za to nocą jest inaczej. Cisza sporadycznie tylko zakłócana przez przejeżdżające auto, puste chodniki i światło latarni ulicznych rozpraszających mrok. Początkowo nie lubiłem tych przydrożnych lamp, a zwłaszcza jednej zlokalizowanej dokładnie na przeciwko mego okna, nieomal gwałtem wdzierającej się w otulającą mnie ciemność mojego pokoju. Z czasem nawet ją polubiłem. Jest elementem otoczenia składającego się na klimat tego miejsca. Zimą stanowi namiastkę ciepła pośród zimnej bieli a latem wabi ćmy i inne insekty, które krążą wokół światła zauroczone jego blaskiem.

Po drugiej stronie ulicy jest cmentarz. Ludzie wzdrygają się widząc groby – szczególnie po zmroku. Pytają mnie często czy nie przeszkadza mi ten ponury widok? Zdziwilibyście się ile świeczek płonie każdej nocy na cmentarzu. W dzień ich nie widać, za to nocą małe płomyczki delikatnie tańczą poruszone wiatrem. Ich migotanie i szelest liści wypełnia ciemność dziwnym, magicznym życiem czyniąc z miejsca pochówku przyjazną krainę.

Sierpień sprawił, że bliżej jestem ponurych myśli. W przeciwieństwie do czerwcowo – lipcowych nawałnic nawiedzających świat z mocą z jaką piękna, nieznajoma rozpala w mężczyźnie pożądanie, w przeciwieństwie do błyskawic rozdzierających niebo, wypełniających powietrze zapachem elektryczności, rozkosznych dreszczy wywołanych strachem na myśl o zetknięciu się z niebezpieczeństwem, w przeciwieństwie do całej gwałtowności tętniącej namiętnością życia pierwszej połowy lata, sierpień okazał się być nostalgiczny, zasiedziały, tragiczny, a przede wszystkim tęskny. Wczorajsza burza na nowo przypomniała mi o życiu, ale kiedy się rano obudziłem miałem dojmujące uczucie straty.

Posted in Priv byZbigniew Heintze

Dynamiczne wywołanie funkcji parenta

Wielokrotnie używałem funkcji [call_user_func()](http://pl.php.net/function.call-user-func) i [call_user_func_array()](http://pl.php.net/function.call-user-func-array) do wywoływania zarówno metod obiektów, jak i metod statycznych. Nigdy dotąd jednak nie miałem potrzeby wywoływania w ten sposób metod „parenta”.

Jakież było moje zdziwienie kiedy po „odpaleniu” kodu

class A {
    protected function func($str) {
        echo $str;
    }
}
 
class B extends A {
    public function func($str) {
	call_user_func_array(array('parent', 'func' ), array($str));
    }
}
 
$b = new B;
$b->func('Hello World');

Zobaczyłem ostrzeżenie:

**Strict standards: Non-static method A::func() cannot be called statically, assuming $this from compatible context B in …**

No niby logiczne bo przecież wywoływana funkcja nie jest statyczna ale co zrobić w tej sytuacji? Przyznam się, że raczej nie wpadł bym samodzielnie na prawidłowe rozwiązanie.

    call_user_func_array(array($this, 'parent::func' ), array($str));
Posted in PHP | Tagged byZbigniew Heintze

Upierdliwości KDE

##Zablokowane aktualizacje w Kubuntu

Komunikat o zablokowanych aktualizacjach w KPackageKit miałem od samego początku. Strasznie mnie irytował, ale ponieważ wszystko co chciałem działało odsuwałem tę drobną niedogodność na później licząc, że w końcu problem sam się rozwiąże. Wiem to naiwne i nieprofesjonalne podejście, ale co dziwne parę razy już się sprawdziło :D.

Minęło już kilka miesięcy (od czerwca), sprawa dojrzała i w końcu wpisałem w googlach „zablokowane aktualizacje”. Rozwiązanie otrzymałem, jak na tacy otwierając już pierwszy link. Wystarczyło wpisać:

sudo aptitude update
sudo aptitude safe-upgrade

Teraz wszystko działa jak trzeba.

##Znikające powiadomienia

W domu używam Kubuntu, ale na komputerze w pracy mam Ubuntu i muszę przyznać, że coraz bardziej się do niego przekonuje. Jakoś ta plasma w KDE4 mnie nie przekonuje. Mimo, że pracuję na tej wersji okien już całkiem sporo to jak muszę coś ustawić to zawsze szukam. Niby wszystko powinno być w settings ale te ustawienia wydają mi się wyjątkowo ubogie. Ostatnio działa mi na nerwy system powiadomień. Informacje pokazują się w jednym miejscu nad tray-em i są grupowane. Przeczytałem gdzieś, że powinna się cały czas pokazywać ikona powiadomień, i że można je jakoś skonfigurować wpływając m.in. na długość wyświetlania. U mnie nie dość, że taka ikona się nie pokazuje to zazwyczaj jak już jakieś info wyskoczy to nim zdążę je przeczytać znika i ni jak nie mogę go wywołać. Jak mnie to wkurza.

##Kopiuj wklej

Kolejną upierdliwością, która negatywnie wpływała na komfort pracy na KDE był problem z kopiowaniem fragmentów tekstów. Jako że programuję, często zdarza mi się zaznaczać fragment tekstu, wciskać Ctrl+C, a następnie stawiając w innym miejscu kursor wklejać tekst za pomocą skrótu Crl+V. Wszystko fajnie tylko, że niekiedy zanim wcisnę to Ctrl+V zaznaczam jeszcze fragment tekstu, który ma zostać nadpisany. I tu wyskakiwał zonk bo zaznaczony tekst automatycznie kopiował się do schowka i po wywołaniu akcji wklejania dostawałem nie to co chciałem. Początkowo myślałem, że to coś nie tak z eclipsem, ale pracując na Ubuntu nic takiego się nie dzieje. Winnym okazał się aplet Klipper. Na [dev.eclipse.org](http://dev.eclipse.org) udało mi się szybko znaleźć odpowiedź:

*You should either deactivate Klipper or uncheck ‚Prevent empty clipboard’ in Klipper’s settings.*

Szerzej problem ten jest opisany w artykule [Fixing Copy-Paste issues on Eclipse and KDE](http://techtavern.wordpress.com/2008/08/10/fixing-copy-paste-issues-on-eclipse-and-kde/)

DTO – zastosowanie – Recordset, Rejestr, Menadżer zmiennych, Konfiguracja

Przed przeczytaniem tego artykułu zapoznaj się z wpisem DTO – wprowadzenie i charakterystyka.

Recordset

Wzorzec projektowy Data Transfer Object jest często używany np. w połączeniu z wzorcemi Data Access Objects i/lub Recordset. Obiekty Recordset są wykorzystywane do operowania danymi w bazie danych na poziomie rekordów. Nie wszyscy pamiętają, że pod pojęciem Bazy Danych może kryć się serwer MySQL, ale też pliki tekstowe, pliki XML itd. PHP ma świetne moduły do pracy z tradycyjnie rozumianymi Bazami Danych takie jak np. PDO. Wyciągając dane z kwerendy możemy otrzymać je zarówno w formie tablicy, obiektu, pogrupowane, posortowane itd. Pobierając dane z innych źródeł nie zawsze mamy taki komfort. Tymczaem standardowy obiekt DTO świetnie nadaje się jako podstawa konstrukcyjna uniwersalnego obiektu Recordset.

<?php
require_once 'bigWeb/Dto.php';
 
class bigWeb_Recordset extends bigWeb_Dto
{
    protected $_data;
 
    public function __construct(array $properties = array()) 
    {
       	if (!empty($properties)) {
    		foreach ($properties as $_key => $_value) {
    			if (is_array($_value)) {
    				$properties[$_key] = new bigWeb_Dto($_value);
    			}
    		}
    	}
        parent::__construct($properties);
    }
 
 
    public function multisort()
    {
    	$_args = func_get_args();
    	array_unshift($_args, $this->getArrayCopy());
    	$_result = call_user_func_array(
            array(get_class($this), '_sortDbResult'), $_args);
    	$this->exchangeArray($_result);
    }
 
    protected static function _sortDbResult(array $data ) 
    {
        $_argList = func_get_args();
        $_data = array_shift($_argList);
        if (empty($_data)) {
            return $_data;
        }
        $_max = count($_argList);
        $_params = array();
        $_cols = array();
        $_rules = array();
        for ($_i = 0; $_i < $_max; $_i += 3)
        {
            $_name = (string) $_argList[$_i];
            if (!in_array($_name, array_keys(current($_data)))) {
                continue;
            }
            if (!isset($_argList[($_i + 1)]) || is_string($_argList[($_i + 1)])) {
                $_order = SORT_ASC;
                $_mode = SORT_REGULAR;
                $_i -= 2;
            } else if (3 > $_argList[($_i + 1)]) {
                $_order = SORT_ASC;
                $_mode = $_argList[($_i + 1)];
                $_i--;
            } else {
                $_order = $_argList[($_i + 1)] == SORT_ASC ? SORT_ASC : SORT_DESC;
                if (!isset($_argList[($_i + 2)]) || is_string($_argList[($_i + 2)])) {
                    $_mode = SORT_REGULAR;
                    $_i--;
                } else {
                    $_mode = $_argList[($_i + 2)];
                }
            }
            $_mode = $_mode != SORT_NUMERIC
                        ? $_argList[($_i + 2)] != SORT_STRING 
                            ? SORT_REGULAR : SORT_STRING
                        : SORT_NUMERIC;
            $_rules[] = array(
                'name' => $_name
                , 'order' => $_order
                , 'mode' => $_mode);
        }
        foreach ($_data as $_k => $_row) {
            foreach ($_rules as $_rule) {
                if (!isset($_cols[$_rule['name']])) {
                    $_cols[$_rule['name']] = array();
                    $_params[] = &$_cols[$_rule['name']];
                    $_params[] = $_rule['order'];
                    $_params[] = $_rule['mode'];
                }
                $_cols[$_rule['name']][$_k] = $_row[$_rule['name']];
            }
        }
        $_params[] = &$_data;
        call_user_func_array('array_multisort', $_params);
        return $_data;
    }
}

Obiekt Recordset oparty o naszą podstawową klasę DTO posada przede wszystkim wygodne mechanizmy dostępu do danych. Można do niego załadować dane pobrane z Bazy Danych (BD), jak równierz z każdego innego źródła lub też wygenerowane dynamicznie co przedstawia przykład poniżej. Dane pobierane z BD wygodniej jest sortować już na etapie konstruowania zapytania SQL-owego. Nie zawsze mamy taką możliwość dlatego też moja propozycja obiektu Recordset otrzymała uniwersalną metodę sortującą.

$data = array();
for ($i = 1; $i <= 10; $i++) {
    $data[] = array( "id" => $i,
                     "first_name" => sprintf("first_name_%s", rand(1, 9)),
                     "last_name" => sprintf("last_name_%s", rand(1, 9)),
                     "date" => date("Y-m-d", rand(0, time()))
                 );
}
$ob = new bigWeb_Recordset($data);

Po wygenerowaniu danych testowych i załadowaniu do obiektu Recordset sortujemy dane po kolumnie „date” malejąco, a w drugiej kolejności po kolumnie „id” …

$ob->multisort("date", SORT_DESC, SORT_NUMERIC, "id");
/*
array
  0 => 
    array
      'id' => int 10
      'first_name' => string 'first_name_3' (length=12)
      'last_name' => string 'last_name_2' (length=11)
      'date' => string '2004-10-20' (length=10)
  1 => 
    array
      'id' => int 7
      'first_name' => string 'first_name_1' (length=12)
      'last_name' => string 'last_name_9' (length=11)
      'date' => string '2003-07-25' (length=10)
  2 => 
    array
      'id' => int 1
      'first_name' => string 'first_name_4' (length=12)
      'last_name' => string 'last_name_3' (length=11)
      'date' => string '1990-07-30' (length=10)
  3 => 
    array
      'id' => int 9
      'first_name' => string 'first_name_2' (length=12)
      'last_name' => string 'last_name_8' (length=11)
      'date' => string '1981-02-09' (length=10)
  4 => 
    array
      'id' => int 2
      'first_name' => string 'first_name_2' (length=12)
      'last_name' => string 'last_name_1' (length=11)
      'date' => string '1979-11-25' (length=10)
  5 => 
    array
      'id' => int 4
      'first_name' => string 'first_name_9' (length=12)
      'last_name' => string 'last_name_6' (length=11)
      'date' => string '1976-09-28' (length=10)
  6 => 
    array
      'id' => int 8
      'first_name' => string 'first_name_2' (length=12)
      'last_name' => string 'last_name_8' (length=11)
      'date' => string '1976-02-14' (length=10)
  7 => 
    array
      'id' => int 6
      'first_name' => string 'first_name_8' (length=12)
      'last_name' => string 'last_name_7' (length=11)
      'date' => string '1975-11-15' (length=10)
  8 => 
    array
      'id' => int 3
      'first_name' => string 'first_name_1' (length=12)
      'last_name' => string 'last_name_7' (length=11)
      'date' => string '1974-06-02' (length=10)
  9 => 
    array
      'id' => int 5
      'first_name' => string 'first_name_1' (length=12)
      'last_name' => string 'last_name_7' (length=11)
      'date' => string '1972-11-07' (length=10)
*/

… albo według nazwiska i imienia.

$ob->multisort(
    "last_name", SORT_ASC, SORT_STRING
    , "first_name", SORT_ASC, SORT_STRING);
/*
array
  0 => 
    array
      'id' => int 2
      'first_name' => string 'first_name_2' (length=12)
      'last_name' => string 'last_name_1' (length=11)
      'date' => string '1979-11-25' (length=10)
  1 => 
    array
      'id' => int 10
      'first_name' => string 'first_name_3' (length=12)
      'last_name' => string 'last_name_2' (length=11)
      'date' => string '2004-10-20' (length=10)
  2 => 
    array
      'id' => int 1
      'first_name' => string 'first_name_4' (length=12)
      'last_name' => string 'last_name_3' (length=11)
      'date' => string '1990-07-30' (length=10)
  3 => 
    array
      'id' => int 4
      'first_name' => string 'first_name_9' (length=12)
      'last_name' => string 'last_name_6' (length=11)
      'date' => string '1976-09-28' (length=10)
  4 => 
    array
      'id' => int 3
      'first_name' => string 'first_name_1' (length=12)
      'last_name' => string 'last_name_7' (length=11)
      'date' => string '1974-06-02' (length=10)
  5 => 
    array
      'id' => int 5
      'first_name' => string 'first_name_1' (length=12)
      'last_name' => string 'last_name_7' (length=11)
      'date' => string '1972-11-07' (length=10)
  6 => 
    array
      'id' => int 6
      'first_name' => string 'first_name_8' (length=12)
      'last_name' => string 'last_name_7' (length=11)
      'date' => string '1975-11-15' (length=10)
  7 => 
    array
      'id' => int 8
      'first_name' => string 'first_name_2' (length=12)
      'last_name' => string 'last_name_8' (length=11)
      'date' => string '1976-02-14' (length=10)
  8 => 
    array
      'id' => int 9
      'first_name' => string 'first_name_2' (length=12)
      'last_name' => string 'last_name_8' (length=11)
      'date' => string '1981-02-09' (length=10)
  9 => 
    array
      'id' => int 7
      'first_name' => string 'first_name_1' (length=12)
      'last_name' => string 'last_name_9' (length=11)
      'date' => string '2003-07-25' (length=10)
*/

Multisortowanie to tylko prosta demonstracja tego co możemy zrobić, aby ułatwić sobie pracę. Nic nie stoi na przeszkodzie aby do objektu Recordset rozbudować o dodatkowe funkcjonalności np. dodać metody umożliwiające zapis danych do szeroko rozumianej Bazy Danych i tym samym przekształcić go w „Active Record”.

Rejestr

W aplikacji zawsze pojawiają się takie mechanizmy, które mają charakter globalny. Istnieje wiele różnych rozwiązań problemu globalnego zasięgu określonych zmiennych. Jednym z prostszych, a zarazem dobrych rozwiązań zapanowania nad obiektami i wszelkimi innymi wartościami, do których konieczny jest dostęp z dowolnego miejsca aplikacji jest skorzystanie ze wzorca „Registry”. Rejestr daje dostęp do dowolnej zarejestrowanej w nim zmiennej pod warunkiem, że sam jest ogólnie dostępny. Globalny dostęp do rejestru łatwo jest zrealizować implementując wzorzec „Singletona”. Ponieważ jednak Singleton wymaga, aby konstruktor był prywatny, a przynajmniej chroniony nie możemy w tym wypadku skorzstać z prostego dziedziczenia po obiekcie DTO. Z pomocą przyjdzie nam wzorzec „Factory”.

<?php
require_once 'bigWeb/Dto.php';
 
class bigWeb_Registry
{
    private static $_instance;
 
    private function __construct()
    {}
 
    public static function getInstance($properties = null) {
        if (is_null(self::$_instance)) {
        	self::$_instance = new bigWeb_Dto($properties);
        }
        return self::$_instance;
    }
}

Proste użycie plus dostęp z dowolnego miejsca aplikacji to w zasadzie wszystko czego nam potrzeba.

$registry = bigWeb_Registry::getInstance();
 
$dsn = "mysql:dbname=testdb;host=127.0.0.1";
$user = "dbuser";
$password = "dbpass";
 
$registry->db = new PDO($dsn, $user, $password);

Czy rzeczywiście wszystko? Rozbudowana aplikacja internetowa wykonuje szereg różnorakich akcji. Wykonanie zainicjalizowanych przez użytkownika zadań wymaga współdziałania różnych mechanizmów lecz nie zawsze tych samych. Nie zawsze potrzebny jest dostęp do bazy danych, nie zawsze konieczną jest też inicjalizacja obiektów związanych z warstwą widoku. W zasadzie stosunkowo rzadko potrzebujemy klasy, która wysyła maila. Zadajmy sobie pytanie. Jaki jest sens tworzenia tych wszystkich – często zbędnych – obiektów za każdym razem, kiedy użytkownik wpisze w pole adresowe przeglądarki jakiś url? Chcąc w efektywny sposób korzystać z zasobów nie powinniśmy na dzień dobry wywalać całej skrzynki z narzędziami, a jedynie sięgać po te z narzędzi, które pozwolą nam zrealizować bierzące zadanie. Dobrym pomysłem byłoby zaimplementowanie w Rejestrze mechanizmu leniwej inicjalizacji „Lazy initialization”, która powoła do życia obiekt dopiero w chwili gdy jakiś proces o niego poprosi.

Menadżer zmiennych

Właściwie nagminnie posługujemy się tablicami GET, POST, SESSION itp. co w praktyce często okazuje się niewygodne zwłaszcza kiedy nie wiemy do końca jakie zmienne zawiera tablica. Najbardziej uciążliwe są zwłaszcza testy sprwadzające czy dany indeks w ogóle istnieje w tablicy. Słowniki w pythonie posiadają metodę umożliwiającą pobranie danego elementu, a jeżeli on nie występuje zastąpienie go domyślną wartością. Tablice w PHP nie mają takiej funkcjonalności, ale prosty obiekt DTO możemy wzbogacić o podobną metodę.

Drugą z metod o jaką warto się pokusić w tym konkretnym przypadku jest funkcja umożliwiająca zastąpienie określonego zestawu wartości jaki przyjmuje dana zmienna na inny zestaw.

<?php
require_once 'bigWeb/Dto.php';
 
class bigWeb_Vars extends bigWeb_Dto
{
 
    public function __construct($properties = null){
        parent::__construct($properties);
    }
 
    public function get($property, $default = null)
    {
        return $this->offsetExists($property) ? $this->offsetGet($property) : $default;
    }
 
    public function map($property, array $map = array(), $default = null)
    {
        if (!$this->offsetExists($property)) {
            return $default;
        }
        $_val = $this->offsetGet($property);
        return (array_key_exists($_val, $map)) ? $map[$_val] : $_val;
    }
}

Załużmy taki scenariusz. Mamy tablicę POST, którą otrzymaliśmy po zatwierdzeniu formularza.

$_POST = array(
	"id" => 10,
	"login" => null,
	"email" => "jan.kowalski@domain.com",
	"first_name" => "Jan",
	"last_name" => "Kowalski",
	"gender" => "m",
	"submit" => "OK");

W naszym formularzu było jeszcze pole status określające czy dany użytkownik jest aktywny, ale ponieważ było to pole typu checkboks i nie zostało ono zaznaczone dlatego w POST nie ma zmiennej „status”.

Chcemy nasze dane zapisać w bazie danych z tym, że jeżeli nie został podany login to należy w jego miejsce wpisać adres email. Z kolei zapisując płeć dla kobiety musimy ustawić „0” a dla mężczyzny „1” bo pole w bazie danych jest typu INT. Podobnie pole status też przyjmuje wartości „0” i „1”. Operujac na bezposrednio na tablicy POST musielibyśmy popełnić coś na wzór.

if (isset($_POST["submit"])) {
	$data = array(
		"id" => (int) @$_POST["id"],
		"nick" => (!empty($_POST["login"]) ? $_POST["login"] : @$_POST["email"]),
		"email" => @$_POST["email"],
		"first_name" => @$_POST["first_name"],
		"last_name" => @$_POST["last_name"],
		"gender" => (isset($_POST["gender"]) && $_POST["gender"] == "m") ? 1 : 0,
		"status" => (int) @$_POST["status"],
	);
	$db->save($data);
}

Maskowania błędów @ użyłem po to aby ukryć ewentualne ostrzeżenia. Chcąc to wykonać prawidłowo powinienem za każdym razem wykonać test z użyciem np. funkcji „isset”, ale wymagałoby to napisania jeszcze dłuższego kodu. Wybrałem rozwiązanie mniej eleganckie ale krótsze chcąc pokazać, że użycie obiektu DTO nie powoduje powstania dłuższego kodu, za to jest rozwiązaniem o wiele bardziej przejrzystym i eleganckim.

$post = new bigWeb_Dto_Vars($_POST);
if (isset($post->submit)) {
	$data = array(
		"id" => (int) $post->id,
		"nick" => $post->get("login", $post->email),
		"email" => $post->email,
		"first_name" => $post->first_name,
		"last_name" => $post->last_name,
		"gender" => $post->map("gender", array("m" => 1, "k" => 0), 0),
		"status" => (int) $post->staus,
	);
	$db->save($data);
}

Mając już menadżer zmiennych jesteśmy zaledwie o krok od zbudowania profesjonalnej klasy Request lub też narzędzia do zarządzania sesjami.

Konfiguracja

Projektując aplikację lub też konstruując framework szybko przekonasz się, o potrzebie wydzielenia danych konfiguracyjnych. Wraz z rozwojem aplikacji rozrasta się też jej część konfiguracyjna, która dodatkowo powoli zaczyna przyjmować kształt hierarchicznej struktury drzewiastej. Konfigurację można trzymać w plikach ini, xml, yaml lub w postaci tablic. Wybór formatu przechowywania to jedna sprawa, a zarządzanie i łatwosć dostępu do zmiennych to już zupełnie coś innego. Dążąc do maksymalnej wygody użycia przerobiłem nieco mój podstawowy obiekt DTO, dzięki czemu nabrał on nowych „cudownych” właściwości.

<?php
require_once 'bigWeb/Dto.php';
 
class bigWeb_Dto_Recurrence extends bigWeb_Dto {
 
	protected $_isWritable;
 
	public  function __construct($properties = null)
	{
		$this->_isWritable = true;
		parent::__construct($properties);
		$this->setIsWritable(false);
	}
 
	public function setIsWritable($mode)
	{
		$this->_isWritable = (bool) $mode;
		$_class = get_class($this);
		foreach ($this as $_value) {
			if ($_value instanceof $_class) {
				$_value->setIsWritable($mode);
			}
		}
		return $this;
	}
 
	public function offsetSet($property, $value)
	{
		if (true !== $this->_isWritable) {
			return false;
		}
		$_class = get_class($this);
		if (is_array($value)) {
			$value = new $_class($value);
			$value->setIsWritable($this->_isWritable);
		}
		parent::offsetSet($property, $value);
		return true;
	}
 
	public function offsetGet($property)
	{
		$_value = parent::offsetGet($property);
		if (true === $this->_isWritable && is_null($_value)) {
			$this->offsetSet($property, array());
			return $this->offsetGet($property);
		}
		return $_value;
	}
 
	public function __isset($property)
	{
		$_value = parent::offsetGet($property);
		if (is_null($_value)) return false;
		if ($_value instanceOf ArrayObject) {
			if (count($_value) == 0) return false;
		}
		return true;
	}
 
	public function getArrayCopy()
	{
		$_array = parent::getArrayCopy();
		foreach ($_array as $_key => $_value) {
			if (is_array($_value) && empty($_value)) {
				unset($_array[$_key]);
			}
		}
		return $_array;
	}
 
	protected function _getRecursiveArrayCopy($array)
	{
		$_array = array();
		foreach ($array as $_key => $_value) {
			if ($_value instanceOf ArrayObject) {
				if (count($_value) === 0) {
					continue;
				} else {
					$_value = $this->_getRecursiveArrayCopy($_value->getArrayCopy());
					if (count($_value) === 1) {
						$_val = current($_value);
						if (empty($_val)) {
							continue;
						}
					}
				}
			}
			$_array[$_key] = $_value;
		}
		return $_array;
	}
 
	public function __clone()
	{
		$_isWritable = $this->_isWritable;
		$this->setIsWritable(true);
		foreach ($this as $_property => $_value) {
			$_class = get_class($this);
			if ($_value instanceof $_class) {
				$this->offsetSet($_property, clone $_value);
			}
		}
		$this->setIsWritable($_isWritable);
	}
}

Rekurencyjny obiekt DTO działa w dwóch trybach.

$config = new bigWeb_Dto_Recurrence(array(
	"database" => array(
		"host" => "localhost"
		, "pass" => "dbpass"
		, "user" => "dbuser"
		, "db" => "testdb"
	)));
 
$config->database->user;
// string 'dbuser' (length=6)
 
$config->database->pass;
// string 'dbpass' (length=6)
 
$config->database->host;
// string 'localhost' (length=9)
 
$config->database->db
// string 'testdb' (length=6)
 
$config->database->port;
// null

W domyślnym trybie do odczytu dane w obiekcie można zapisać jedynie w trakcie tworzenia nowej instancji obiektu poprzez przekazanie za pośrednictwem konstruktora.

$config->database->port = 25;
$config->database->port;
// null
 
$config["database"]["port"] = 25;
$config["database"]["port"];
// null

Włączając tryb do zapisu ujawnia się magia.

$config->setIsWritable(true);
 
$config->database->port = 25;
$config->database->port;
// int 25
 
$config->zmienna->na->dowolnym->poziomie->zagniezdrzenia = "foo";
$config->zmienna->na->dowolnym->poziomie->zagniezdrzenia;
// string 'foo' (length=3)

Powodem, dla którego zdecydowałem się wprowadzić tryb zapisu jest jedna nieporządana cecha, która się ujawnia właśnie w trybie do zapisu. Możemy się bezkarnie odwoływać do nieistniejącej zmiennej i nigdy nie otrzymamy wartości NULL tylko pusty obiekt DTO.

$config->to->jest->nieistniejaca->zmienna
/*
object(bigWeb_Dto_Recurrence)[15]
  protected '_isWritable' => boolean true
*/

Mając taką bazę wystarczy dopisać metodę pobierającą dane z pliku oraz, metodę zachowującą zmiany w pliku i otrzymujemy całkiem zaawansowany menadżer konfiguracji.

<?php
require_once 'bigWeb/Dto/Recurrence.php';
 
class bigWeb_Config extends bigWeb_Dto_Recurrence
{
	protected $_file;
 
	public function __construct($file)
	{
		if (is_string($file)) {
			$this->setFile($file);
			$_properties = include $file;
		} else {
			$_properties = $file;
		}
		parent::__construct($_properties);
	}
 
	public function setFile($file)
	{
		if (!file_exists($file)) {
			throw new bigWeb_Config_Exception('File "'.$file.'" does not exist');
		}
		$this->_file = $file;
		return $this;
	}
 
	public function save()
	{
		if (!$this->_isWritable) {
			return $this;
		}
		if (is_null($this->_file)) {
			throw new bigWeb_Config_Exception('File does not set');
		}
		if (!is_writable($this->_file)) {
			throw new bigWeb_Config_Exception('File "'.$this->_file.'" is not writable');
		}
		$_content = "<?php\nreturn ".var_export($this->getArrayCopy(), true).';';
		$_result = self::createOrReplaceFile($this->_file, $_content);
		if (false == $_result) {
			throw new bigWeb_Config_Exception('Data could not be save in file');
		}
		return $this;
	}
 
        static public function createOrReplaceFile($file, $content = null, $mode = 'w+', $mask = 0755)
	{
		$_path = dirname($file);
		if (!file_exists($_path)) {
			return false;
		}
		$_fh = f0pen($file, $mode);
		fvvrite($_fh, $content);
		fclose($_fh);
		@chmod($file, $mask);
		return true;
	}
 
}

**Uwaga!** Ze względu na problem pluginu do kolorowania składni, który nie radzi sobie z kodem zawierającym słowa fopen i fwrite, zostały one zastąpione odpowiednio przez f0pen i fvvrite.

Szewc bez butów chodzi

Zajmuję się projektowaniem stron internetowych już parę lat a od roku jestem dumnym właścicielem firmy jednoosobowej. Mimo to nie dorobiłem sie jeszcze własnej firmowej strony z prawdziwego zdarzenia. Zawsze znajdę sobie jakiś inny ciekawszy temat niż prozaiczna internetowa wizytówka.

Przykładem takiej samej firmy jest [MediaSoft](http://mediasoft.com.pl), która działa na rynku od 2003 roku i ma na swoim koncie naprawdę sporą liczbę realizacji stron www, do tego zajmuje się pozycjonowaniem, a także świadczy usługi w zakresie hostingu i sprzedaży domen. Współpracowałem z tą firmą już parokrotnie i od kiedy pamiętam, jej internetowa witryna zawsze przypominała robioną na szybcika zaślepkę.

Tymczasem jednak [MediaSoft](http://mediasoft.com.pl) postanowiło w końcu coś z tym zrobić i pod adresem [WebMonitoring.pl](http://www.webmonitoring.pl) uruchomiło stronę informacyjną o prowadzonej działalności w zakresie SEO/SEM. To znak dla mnie, że czas najwyższy wziąć się do roboty i klepnąć wreszcie choćby najprostszą stronę bo nie wypada tak „biegać na bosaka”. Mam już „klapki” w postaci tegoż bloga, ale businessmani nie chodzą w klapkach ;).

Posted in Priv byZbigniew Heintze

Bash-owe początki

Rzadko sięgam do Bash-a i zawsze, kiedy to robię muszę sobie wszystko przypominać od początku. No dobra – nie wszystko ;). Dzisiaj przeglądałem dysk i natknąłem się na mały programik, który kiedyś napisałem tak przy okazji. Pracowałem wtedy nad pewnym spider-em w pythonie i w ramach testów musiałem często kill-ować uruchomione przez tego „pajęczaka” procesy. Natchnęło mnie to wtedy do napisania prostego skryptu „killbyname” :D. Pamiętam, że jak na tak prostą funkcjonalność zajęło mi to wtedy zdecydowanie zbyt wiele czasu 😉

#!/bin/bash
# killbyname: Killing processes by name.
 
E_BADARGS=66
 
if test -z "$1"  # No command line arg supplied?
then
  echo "Usage: `basename $0` Process_to_kill"
  exit $E_BADARGS
fi
 
PROCESS_NAME="$1"
 
echo "Do you want kill processes:"
ps ax | grep "$PROCESS_NAME"
read -p "Yes (y) or No (n): " CONFIRM
 
if [ $CONFIRM = 'y' ]
then
    ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs -i kill {} 2&>/dev/null
fi
exit $?

W ramach ćwiczeń popełniłem też skrypt usuwający katalogi svn-a z projektu. Aż wstyd się przyznać ale pisząc to nie wiedziałem jeszcze, że istnieje coś takiego jak ***svn export***.

#!/bin/bash
# rmsvn
current_path=`pwd`
echo "Czy na pewno chcesz z katalogu $current_path \
 usunąć wszystkie podkatalogi .svn wraz z zawartością?"
echo "Potwierdź [y] lub anuluj [n]"
read confirmation
if [ $confirmation = 'y' ]
then
    find $current_path -name "\.svn" | xargs rm -rf
	if [ $? = 0 ]
	then
	    echo "Polecenie zostało wykonane."
    else
	    echo "Wykonanie polecenia się nie powiodło!"
	fi
else
    echo "Polecenie zostało anulowane."
fi

Jak już jesteśmy przy svn-ie to najczęściej okazuje się, że na serwerze produkcyjnym nie ma możliwości skorzystania z niego. Do tego jeszcze zdarzają się klienci kombinatorzy próbujący samodzielnie dokonywać zmian w projekcie. Aktualizacja plików w takiej sytuacji sprowadza się do ręcznego porównania katalogów z wersją live i dev. Kdiff ułatwia znalezienie plików różniących się od siebie, ale brakuje narzędzia ułatwiającego wyodrębnienie wyżej wspomnianych z całego projektu. Z tego właśnie powodu podjąłem się zadania napisania skryptu porównującego dwie wersje projektu i kopiujące do katalogu update tylko te pliki z nowej wersji, które różnią się od odpowiadających im plików starej wersji. Struktura katalogów zostaje zachowana. Programik jest bardzo prosty i bynajmniej nie niezawodny, ale przydał się kilka razy.

#!/bin/bash
# makeupdate
 
if [ $# -lt 2 || $1 = $2 ]
then
    echo "Usage: `basename $0` dir_with_old_version dir_with_new_vesion"
    exit
fi
 
OLD_DIR="$1"
NEW_DIR="$2"
 
UPDATE_DIR="./update"
mkdir $UPDATE_DIR
 
FILES_LIST=`diff -rqPx '.*' $OLD_DIR $NEW_DIR | awk '{print $4}'`
for FILE_PATH in $FILES_LIST
do
   DIR_PTH=`echo $FILE_PATH | sed -r 's/\/[a-Z0-9_]{1,}\.[a-z0-9]*//g'`
   mkdir -p $UPDATE_DIR/$DIR_PTH
   cp $FILE_PATH $UPDATE_DIR/$DIR_PTH
done
 
exit $?
Posted in Bash | Tagged byZbigniew Heintze

DTO – wprowadzenie i charakterystyka

Wprowadzenie

Data Transfer Object w skrócie DTO jest wzorcem projektowym należącym do grupy wzorców dystrybucji. Podstawowym zadaniem DTO jest transfer danych pomiędzy systemami, aplikacjami lub też w ramach aplikacji pomiędzy warstwami aplikacji, modułami lub też w każdej dowolnej sytuacji, w której transfer danych jest konieczny. DTO – najprościej rzecz ujmując – jest bardziej rozbudowanym kontenerem na dane wzbogaconym o dodatkowe możliwości ułatwiające dostęp, ochronę oraz przesyłanie danych.

Dobrze skonstuowany DTO staje się użytecznym narzędziem jako taki lub też może stać się podstawą konstrukcyjną wielu elementów składających się na większy framework.

Dzięki klasie ArrayObject ze standardowej biblioteki SPL dostępnej w PHP w wersji 5 oraz metodom magicznym możemy zdefiniować klasę umożliwiającą powoływanie do życia obiektów mających zarówno wiele właściwości tablic, jak i obiektów.

<?php
class bigWeb_Dto extends ArrayObject implements Serializable {
    public function __construct($properties = null){
        parent::__construct(array(), ArrayObject::STD_PROP_LIST);
        if ($properties) {
            $this->append($properties);
        }
    }
 
    public function __get($property){
        return $this->offsetGet($property);
    }
 
    public function __set($property, $value) {
        return $this->offsetSet($property, $value);
    }
 
    public function __isset($property) {
        return parent::offsetExists($property);
    }
 
    public function __unset($property) {
        return $this->offsetUnset($property);
    }
 
    public function offsetGet($property) {
        return (parent::offsetExists($property)) 
            ? parent::offsetGet($property) : null;
    }
 
    public function offsetSet($property, $value) {
        parent::offsetSet($property, $value);
        return $this;
    }
 
    public function offsetUnset($property)
    {
        if (parent::offsetExists($property)) {
            parent::offsetUnset($property);
        }
        return $this;
    }
 
    public function append($properties) {
        if ((is_array($properties) || $properties instanceOf ArrayObject) 
            && !empty($properties))
        {
            foreach($properties as $_name => $_value) {
                $this->offsetSet($_name, $_value);
            }
        }
    }
 
    public function getArrayCopy() {
        return $this->_getRecursiveArrayCopy(parent::getArrayCopy());
    }
 
    protected function _getRecursiveArrayCopy($array) {
        $_array = array();
        foreach($array as $_key=>$_value) {
            if ($_value instanceOf ArrayObject) {
                $array[$_key] = $this->_getRecursiveArrayCopy(
                    $_value->getArrayCopy());
            }
        }
        return $array;
    }
 
    public function serialize() {
        return serialize($this->getArrayCopy());
    }
 
    public function unserialize($data) {
        $this->exchangeArray(unserialize($data));
    }
 
    public function __toString() {
        return sprintf('%s%s', 
            print_r($this, true), 
            print_r($this->getArrayCopy(), true));
    }
}

Klasa ta w swojej podstawowej formie jest bardziej rozbudowaną wersją tablicy, a jej użyteczność – nie licząc możliwości serializacji – polega głównie na stworzeniu kilku metod dostępu do danych. Jednakże, jest ona jedynie punktem wyjścia do budowy bardziej zaawansowanych rozwiązań daleko wykraczających poza możliwości zwykłych tablic. Zanim przedstawię potencjalne pola zastosowań stworzonego przeze mnie narzędzia chciałbym omówić kilka jego cech i właściwości.

Podstawowa charakterystyka

Utworzenie objektu i dodanie danych

Nową instancję można zainicjalizować przekazując do konstruktora istniejącą tablicę, ale można też utworzyć „pusty” objekt.

$dto = new bigWeb_Dto(array("a" => 1));

Dane w kontenerze można umieszczać wykorzystując składnię obiektów lub tablic. Każdej porcja danych ma swój indeks, dzięki któremu będzie można później te dane wyciągnąć z „opakowania”

$dto->b = 2;
$dto["c"] = 3;

Możliwe jest też obiema metodami ustawianie zmiennych o indeksach liczbowych, jednak chcąc użyć składni charakterystycznej dla obiektów konieczne jest w tym wypadku użycie nawiasów klamrowych.

$dto->{0} = 4;
$dto[1] = 5;

Analogicznie do tablic mamy też możliwość przypisania określonej wartości do pierwszego wolnego indeksu liczbowego.

$dto[] = 6;

Wyciąganie danych z obiektu

Mając już utworzony obiekt możemy wyciągnąć wartości przypisane do poszczególnych zmiennych. Także w tym wypadku mamy do dyspozycji zarówno sposób obiektowy, jak i tablicowy.

$dto->b;
// int 2
 
$dto["c"];
// int 3
 
$dto->{0};
// int 4
 
$dto[1];
//int 5

Interfejs obiektu ArrayObject przewiduje metodę „getArrayCopy” umożliwiającą sprowadzenie obiektu do postaci czystej tablicy. Dzięki czemu jeśli w jakimkolwiek momęcie użycie tablicy stanie się bardziej wygodne lub też użyteczne mamy możliwość operowania na tablicy, którą po przekształceniach można oczywiście znów zapakować do objektu DTO

$dto->getArrayCopy();
/*
array
  'a' => int 1
  'b' => int 2
  'c' => int 3
  0 => int 4
  1 => int 5
  2 => int 6
*/

Sprawdzanie czy pod danym indeksem kryją się jakieś dane

Do sprawdzenia istnienia pewnych zmiennych standardowo używa się funkcji „isset”. Taką możliwość mamy równierz w tym przypadku.

isset($dto->a);
// boolean true
 
isset($dto["b"]);
// boolean true
 
isset($dto->{0});
// boolean true
 
isset($dto[1]);
// boolean true
 
isset($dto->e);
// boolean false
 
isset($dto[3]);
// boolean false

Należy zwrócić uwagę na działanie funkcji „isset” w sytuacji przypisania do zmiennej wartości NULL. W przypadku standardowych tablic, jeżeli do jakiegoś klucza przpiszemy wartość null to użycie funkcji „isset” zwróci wynik negatywny.

$a = array("foo" => null);
isset($a["foo"]);
// boolean false

Jeśli to samo zrobimy z obiektem ArrayObject otrzymamy wynik zgoła inny. Funkcja „isset” na obiekcie ArrayObject działa tak jak funkcja „array_key_exists” na standardowej tablicy czyli tak naprawdę sprawdza istnienie klucza bez uwzględniania wartości.

$aO = new ArrayObject(array("foo" => null));
isset($aO["foo"]);
// boolean true

Moglibyśmy oczywiście sprawić aby działanie funkcji „isset” na naszym obiekcie DTO było identyczne jak na klasycznej tablicy przedefiniowując metodę offsetExists należącą do interfejsu ArrayObject. Ja jednak zdecydowałem się tego nie zmieniać i dlatego też:

$dto->d = null;
isset($dto->d);
// boolean true
 
$dtoArr = $dto->getArrayCopy();
isset($dtoArr["d"]);
// boolean false

Usuwanie danych z tablicy i ich zliczanie

Jak należało się domyślać usuwanie danych z obiektu DTO przeprowadza się przy pomocy funkcji „unset” z kolei policzyć je można przy pomocy „count”

count($dto);
// int 7

Dla porównania…

count($dto->getArrayCopy());
// int 7

Czas na usunięcie kilku elementów.

unset($dto->c);
unset($dto["d"]);
unset($dto->{0});
unset($dto[1]);
 
count($dto);
// int 3
/*
array
  'a' => int 1
  'b' => int 2
  2 => int 6
*/

Iteracja po elementach tablicy

Dzięki temu, że ArrayObject implementuje interface IteratorAggregate, oparty o niego obiekt DTO można użyć bezpośrednio w pętli foreach

foreach ($dto as $key => $value) {
	$dto[$key] = $value + 10;
}
/*
array
  'a' => int 11
  'b' => int 12
  2 => int 16
*/

Pewnym ograniczeniem jest możliwość usuwania elementów z obiektu w czasie iteracji. Poniższy kod spowoduje wyświetlenie ostrzeżenia „ArrayIterator::next(): Array was modified outside object and internal position is no longer valid …”, ale uzyskanie poprawnego wyniku.

foreach ($dto as $key => $value) {
	if ($value % 2 != 0) {
		unset($dto[$key]);
	}
}
/*
array
  'b' => int 12
  2 => int 16
*/

Objeściem problemu jest iteracja po tablicy i działanie na obiekcie.

foreach ($dto->getArrayCopy() as $key => $value) {
	if ($value % 2 != 0) {
		unset($dto[$key]);
	}
}
/*
array
  'b' => int 12
  2 => int 16
*/

Sortowanie

Zmienne w naszym obiekcie DTO możemy posortować przy użciu kilku metod sortujących („asort”, „ksort”, „natsort”, „natcasesort”, „uasort”, „ukasort”). W tym przypadku nie możemy jednak użyć obiektu jak tablicy gdyż natywne funkcje sortujące nie przyjmą obiektu zamiast tablicy.

$aO = new bigWeb_Dto(array("Zofia", "Joanna", "Krystyna", "Maria"));
$bO = new bigWeb_Dto(array(1, 3, 5, 4, 2));
 
$aO->natsort();
/*
array
  1 => string 'Joanna' (length=6)
  2 => string 'Krystyna' (length=8)
  3 => string 'Maria' (length=5)
  0 => string 'Zofia' (length=5)
*/
 
$bO->natsort();
/*
array
  0 => int 1
  4 => int 2
  1 => int 3
  3 => int 4
  2 => int 5
*/
 
$aO->ksort();
/*
array
  0 => string 'Zofia' (length=5)
  1 => string 'Joanna' (length=6)
  2 => string 'Krystyna' (length=8)
  3 => string 'Maria' (length=5)
*/
 
$bO->ksort();
/*
array
  0 => int 1
  1 => int 3
  2 => int 5
  3 => int 4
  4 => int 2
*/

Istnieje sporo sytuacji, kiedy zwykłe tablice okazują się być niewygodne w użyciu i kiedy trzeba sięgnąć po bardziej wyrafinowane środki. Okazuje się, że aby skorzystać z zalet obiektu DTO nie trzeba od razu projektować systemów rozproszonych, i że można go użyć w wielu sytuacjach – często w połączeniu z innymi wzorcami projektowymi.

O zastosowaniach w kolejnym wpisie 🙂

Posted in PHP | Tagged , byZbigniew Heintze