radmen's techblog

Feed Rss

Gdzieś w sieci spotkałem pytanie o treści: jak stworzyć plik o określonej wielkości (gdzie zawartość nie ma znaczenia) w PHP?. Pytanie proste, aczkolwiek znalazłem aż trzy możliwe sposoby na jego rozwiązanie.

1. dd

Systemy Uniksowe mają takie zgrabne narzędzie o nazwie dd. Wielokrotnie przydawało mi się, pomyślałem, że tym razem również z niego skorzystam.

Skrypt, generujący 500 MiB mieści się w jednej linijce i wygląda tak:

`dd if=/dev/urandom of=/tmp/sample.txt bs=1024 count=512000`;

Niestety to rozwiązanie jest najwolniejsze. Czas wykonania skryptu na mojej maszynie wirtualnej wyniósł:  67s

Nie jest to rozwiązanie w pełni PHPowe. Uznałem jednak, że można je tutaj dopisać, jako że w części wypadków ma się dostęp do powłoki systemowej i można korzystać z takich narzędzi.

2. Wypełnienie pliku

Zdaje się, że jest to najbardziej oczywisty sposób. Generujemy ciąg znaków odpowiedniej długości i zapisujemy to do pliku.

Najprostsza forma tego rozwiązania wygląda tak:

$size = 1024 * 1024 * 500;
file_put_contents('/tmp/sample.txt', str_repeat(' ', $size));

Czas wykonania: 10s
Całkiem nieźle, aczkolwiek jest jeden problem. Przedstawione rozwiązanie potrzebuje tyle wolnej pamięci ile chcemy danych ulokować w pliku (w tym przypadku było to 500MiB). Raczej kiepsko, aby skrypt potrzebował do wykonania tyle pamięci.

Rozwiązanie tego problemu jest proste – nie przechowywać w pamięci takiej ilości danych tylko od razu wrzucać je do pliku:

$size = 1024 * 1024;
$file = new SplFileObject('/tmp/sample.txt', 'w');

while($size--) {
  $file->fwrite(str_repeat(' ', 500));
}

Czas wykonania: 7s
Zużyta pamięć: 9 MiB

3. fseek

Jakiś czas temu pisałem o podstawowych operacjach na plikach. Wspominałem tam o funkcji fseek, oraz jej specyficznym zachowaniu.

W tym przypadku fseek’a, można użyć do tego aby przesunąć wskaźnik pliku na określoną pozycję, zapisać do niej jeden znak, a resztę załatwi FS (puste miejsca zostaną wypełnione „śmieciami”).

Przykładowy skrypt:

<?php
$size = 1024 * 1024 * 500; // 500 MiB
$file = new SplFileObject('/tmp/sample.txt', 'w');
$file->fseek($size - 1);
$file->fwrite(' ');

Czas wykonania: 0.025s

Widać różnicę prawda? ;)

Swego czasu zdarzyło mi się napisać prostą grę w Pythonie. Pamiętam, że podczas pisania kolejnych klas przyszła sytuacja, w której chciałem aby działanie pewnej metody było inne. Nie chciałem jej parametryzować, chciałem po prostu aby w tym jednym konkretnym miejscu zachowywała się inaczej.

Tutaj przyszło piękno Pythona (i nie tylko jego) – jest możliwość podmiany metod. Wystarczy tylko podmienić referencję do funkcji na którą wskazuje atrybut (czy też metoda).

Ostatnio naszła mnie taka myśl – czy w PHP mogę podmienić metodę na zupełnie inną? Czy jest możliwość „dopisywania” metod do obiektu? Od razu odpowiem – takie coś jest możliwe, chociaż jest to twór, który raczej emuluje działanie podobne do tego z Pythona.

Zanim wspomnę o tym jak rozwiązać problem chciałbym jeszcze naprowadzić na mój tok rozumowania. Punktem początkowym były pseudo-klasy, jakich używam w bibliotece MooTools. Po stworzeniu obiektu „metody” są tak naprawdę atrybutami z referencją na funkcję. Przy takiej budowie bez problemu mogę wykonać kod:

var example = new Class({
    test: function() {}
});

var obj = new example();
console.log(typeof obj.test); // function

Niestety w PHP to tak nie działa. Deklarowana metoda nie jest referencją funkcji. Z tego powodu próba wykonania podobnego kodu zakończy się porażką.

A gdyby tak przypisać do jednego z atrybutów klasy funkcję anonimową? Wtedy kod mógłby wyglądać tak:

$example = new stdClass();
$example->test = function() { };

$example->test(); // wrong..

Niestety, błąd. Dlaczego? Takie odwołanie sugeruje interpreterowi aby „szukał” metody, a jak wiadomo nie jest ona zapisana jako atrybut, także nie istnieje, także jest błąd ;)

Z pomocą przychodzi funkcja __call. Jeśli nastąpi próba uruchomienia metody, która nie jest zdefiniowana ta funkcja przejmie pałeczkę i odpali funkcję (przy użyciu call_user_func) przypisaną do wybranego atrybutu. Proste? ;) Okazuje się, że nawet tutaj trzeba zastosować mały „hack”, ale przejdźmy do konkretów.

Przykład klasy, która może uruchamiać pseudo-metody (kod został nieco uproszczony, chętnych odsyłam na Githuba):

class PyClass {

    public function __call($method, $args) {
        $func = $this->$method;

        array_unshift($args, $this);
        call_user_func_array($func, $args);
    }
}

Oraz przykład wykorzystania:

class Person extends PyClass {

    public $name;

    public function __construct($name) {
        $this->name = $name;
    }
}

$frank = new Person('Frank');
$frank->sayHello = function(Person $self) {
    echo "Howdy! I'm {$self->name}!\n";
};

$frank->sayHello();

Teraz niestety napiszę trochę o wadach takiego rozwiązania.

Największym problemem jest to, że deklarowanie tych pseudo-metod automatycznie odcina dostęp do metod i atrybutów klasy, które mają zasięg private/protected. Co więcej, anonimowe funkcje są tak naprawdę obiektem, także odpada korzystanie z $this jako odwołania do klasy, w której zdefiniowano „metodę”. Z tego powodu __call przekazuje w pierwszym argumencie wywołania funkcji referencję $self. Czy widać tutaj podobieństwa z Pythona? ;)

Jest jeszcze jedna, dość poważna wada – takie rozwiązanie straszliwie zaciemnia kod, oraz może utrudniać jego późniejszą analizę.

Podsumowując – w PHP jest możliwość tworzenia dla obiektu pseudo-metod, które wywołaniem nie będą różnić się niczym od oryginału. Niestety nie ma mowy o tym aby takie metody mogły działać dokładnie tak samo jak te „pełnoprawne”. Dodatkowo takie rozwiązanie zaciemnia kod.

W takim razie, czy jest sens korzystania z tego? Wydaje mi się, że jest, ale tylko w jednym celu - Dependency Injection Container. Sami zdecydujcie, czy warto korzystać z takiego potworka ;)

W pracy zdarza się odkryć różne „kwiatki”. Z niektórymi warto się podzielić. Na pierwszy ogień pójdzie metoda na to jak zdekodować jeden z parametrów $_GET, tak aby przypominał swój oryginał:

<?php
$str = implode(' ', explode('+', $_GET['str']));
?>

Pamiętajcie – używanie (raw)urldecode jest zbyt mainstream ;)

Czasami zachodzi potrzeba wygenerowania losowego id (np dla rekordów tymczasowych). Jest nawet dedykowana do tego celu funkcja – uniqid(). Jednakże często nie jest ona wykorzystywana (a powinna). Powodem jest jej słaba wydajność, przez co wielu woli zrezygnować z użycia tej funkcji na rzecz innych rozwiązań.

Chyba najprostszym z nich jest wykorzystanie funkcji microtime(), która zwraca czas uniksowy z dokładnością do mikrosekund. Wydaje się, że każdorazowe odpalenie tej funkcji zwróci różne wartości prawda? Poniżej przedstawiam prosty kod wraz z wynikiem jaki otrzymuję:

<?php
echo microtime()."\n";
echo microtime()."\n";
?>
// radmen@devel:~$ php microtime.php
// 0.52810300 1321880773
// 0.52831200 1321880773

Jak widać zwrócone wartości są różne.

Czy jest tak zawsze? Niestety nie. Pamiętam, że od dawien dawna wynik zwracany przez microtime w tej formie mało komu odpowiadał. Najczęściej zwrócony string rozbijano po spacji po czym obie wartości były sumowane (patrz „Example 1″ w manualu). Później dodano argument do wywołania $get_as_float, który robi dokładnie to samo o czym wcześniej wspomniałem.

Niestety tutaj wyniki są już nieco inne:

<?php
echo microtime(true)."\n";
echo microtime(true)."\n";
?>
// radmen@devel:~$ php microtime.php
// 1321881083.88
// 1321881083.88

Skąd taka różnica? Najpewniej jest to błąd wynikający z operacji dokonywanych na liczbach zmiennoprzecinkowych. Tutaj cała sprawa nie jest za dużym problemem – wykonałem funkcje jedna za drugą, także czas ich odpalenia można uznać za „jednakowy”.

Z tego powodu postanowiłem sprawdzić, jak się ta funkcja zachowa w symulowanym „środowisku”. Założenie jest proste – użyję microtime jako generatora unikalnego id, w prostej pętli.

<?php
for($i = 0; $i < 10; $i++) {
  echo microtime(true)."\n";

  // czekamy 1ms na kolejna iteracje
  usleep(1000);
}
?>
// radmen@devel:~$ php microtime.php
// 1321881405.97
// 1321881405.97
// 1321881405.98
// 1321881405.98
// 1321881405.98
// 1321881405.98
// 1321881405.98
// 1321881405.98
// 1321881405.99
// 1321881405.99

Widać wyraźnie, że moje „id” wcale nie są takie unikalne.

Czy to stanowi problem? Podam przykład z „życia” – podpinaliśmy flashowy uploader plików. Każdy wrzucany plik miał id wygenerowane przez microtime, a jego informacje były zapisywane w sesji. Później działy się pewne cuda (głównie po stronie klienta – JS), a na samym końcu robiliśmy zapis tych plików. Okazało się, że jeśli użytkownik zaznaczał podczas uploadu wiele plików część z nich otrzymała to samo id..

Wniosek – nie używać microtime jako funkcji generującej unikalne id wewnątrz jakiejkolwiek pętli (bądź kolejki). W tej sytuacji lepiej korzystać z uniqid, lub z innego rozwiązania (nawet zwykły licznik wystarczyłby do tego celu).

firebug

Firebug jest jednym z tych narzędzi, bez którego nie wyobrażam sobie pracy. Każdy, kto korzysta z niego wie co potrafi, także nie będę tutaj o tym pisać. Jednak czy wiecie o tym, że Firebug posiada pokaźną gamę rozszerzeń? Wśród nich znajduje się pewna perełka – FireFile.

FireFile jest rozszerzeniem, które pozwala na zapisywanie edytowanych w konsoli Firebug’a CSSów. Dzięki niemu nie trzeba przeskakiwać pomiędzy edytorem, a przeglądarką i kopiować zmienionych CSSów. To rozwiązanie znacznie zmniejsza czas jaki trzeba poświęcić na edycję CSSów (zwłaszcza poprawek jakiś pierdółek).

Niestety, jak to bywa, są pewne ograniczenia. FireFile widzi tylko te reguły CSS, które są przeznaczone dla Firefoksa. Oznacza to, że gdy jakiś element posiada reguły specyficzne dla przeglądarek (z prefiksem -webkit, -o, -ms) zostaną one zignorowane, a po zapisie do pliku wyrzucone z arkusza stylów. Takie zachowanie wydaje mi się raczej oczywiste – Firefox nie interpretuje CSSów przeznaczonych dla innych przeglądarek, także nie są one nawet dostępne w Firebugu. Nie jest to jedyna wada tego rozszerzenia – zdarza się, że pomijane są niektóre reguły dotyczące stricte Firefoksa (np. @-moz-keyframes).

Jeśli dość mocno wykorzystujemy CSS3 polecam przeniesienie wszelkich vendor-specific properties do osobnego arkusza CSS. Jeśli nic w nich nie będzie zmieniane FireFile nie zmodyfikuje ich zawartości.

Czy warto korzystać z tego rozszerzenia? Myślę, że odpowiedź na to pozostawię Wam.

Do pracy wykorzystuję maszynę wirtualną. Hostem jest Windows 7, a na maszynie wirtualnej (emulowanej przez VirtualBox) zainstalowałem Debiana. Serwer jest typową konfiguracją LAMP, z tym tylko wyjątkiem, że pliki projektów znajdują się na hoście w katalogu współdzielonym.

Póki robiłem na tym serwerze małe rzeczy nie zauważyłem jednego mankamentu tej konfiguracji – po modyfikacji plików graficznych zmian nie było widać na edytowanych stronach. Myślałem, że była to kwestia jakiegoś cache. Nie myliłem się, chociaż sprawa jest nieco bardziej złożona.

Dla hosta katalog współdzielony jest zwykłym katalogiem. W maszynie wirtualnej aby go zamontować (konkretnie pod Linuksem) trzeba mieć zainstalowany sterownik do specyficznego filesystemu (dla VirtualBox jest to vboxfs). Sterownik ten po prostu pobiera dane z katalogu tak jakby to był zwykły zasób LAN, czyli robi to „po sieci”. W tej konfiguracji Apache w jakiś przedziwny sposób keszuje takie pliki (chociaż normalnie nie stosuje żadnego cache’a).

Nie udało mi się znaleźć dokładnych wyjaśnień problemu, ale jak wspomniałem udało mi się znaleźć rozwiązanie.  Wystarczy do pliku z konfiguracją Apache’a (w moim przypadku /etc/apache2/httpd.conf) dopisać:

#Disable image serving for network mounted drive
EnableSendfile off

Po restarcie serwera problem powinien się rozwiązać.

Zainteresowanych odsyłam do wątku How clear Apache cache - jeden z ostatnich postów przedstawia rozwiązanie problemu.

Czy wiesz jaki będzie wynik wyrażenia (true && 2) w JavaScript? Jeśli uważasz, że będzie to TRUE jesteś w błędzie ;)
Chciałem to zjawisko wyjaśnić dokładniej, jednak ostatnio znalazłem ciekawy artykuł poruszający tą kwestię. Jeśli angielski nie jest Ci obcy polecam wpis How Logical AND and OR Operators Actually Work in Javascript.

Operacje na plikach są chyba jedną z pierwszych rzeczy, które poznaje się podczas nauki jakiegokolwiek języka. Miałem dokładnie tak samo podczas nauki PHP. Większość kursów dotyczących obsługi plików zaczyna się od stwierdzenia (wracam pamięcią do czasów PHP 4), że wszystkie funkcje obsługi plików (oprócz tej otwierającej plik) jako parametr pobierają tzw. „wskaźnik do pliku” (ang. “file pointer”).

Sprawa banalna co nie? Otwiera się wskaźnik do pliku, wykonuje jakieś operacje, po czym ten wskaźnik zostaje zamknięty. Wraz z rozwojem PHP sprawy stały się jeszcze prostsze. PHP 5 wprowadził file_get_contents i file_put_contents. Właściwie od tego czasu przestałem używać wskaźników do plików, aż w końcu zapomniałem o podstawach.

Learn more

PHPCon 2011

10.26.2011, No Comments, PHP, by .

Czy znacie jakieś konferencje dotyczące PHP, które odbywają się w kraju? No właśnie, ja też nie znam. A właściwie nie znałem niczego innego poza PHPCon’em. Rok temu nie miałem przyjemności dostać się na tą konferencję. Na szczęście w tym roku udało się nadrobić zaległości.

To co zachęciło mnie na wybranie się na drugi koniec Polski to przede wszystkim agenda. Wydawała się być naprawdę porządna, a tematy poruszały nader ciekawe kwestie. Rzeczywistość niestety okazała się nieco inna. Wiele z prelekcji (niestety głównie polskich) była merytorycznie nijaka. Odniosłem wrażenie, że prelegenci jakoś nie specjalnie przygotowali się do tego co mieli przedstawiać.
Na szczęście sytuację ratowali zagraniczni prelegenci. Widać naprawdę sporą różnicę (oraz zdecydowanie większe obycie) w sposobie prowadzenia wykładów.

Wyjątkowo dokuczliwa była audiencja. Ludzie w trakcie wykładów potrafili dość głośno rozmawiać między sobą, przez co ciężko było się skupić nad tematem prelekcji. Wiele osób również z uporem maniaka chodziło w tę i z powrotem po sali, co chwila trzaskając drzwiami.

Na szczęście tego typu konferencje to nie tylko wykłady. Jest też coś takiego jak kuluary i dopiero w nich miałem okazję przeprowadzić kilka naprawdę interesujących rozmów. Najciekawszą z rozmów okazała się ta, którą miałem przyjemność prowadzić z @Davidem Coallier, oraz Grześkiem z MegiForge. Dowiedziałem się kilku dość ciekawych rzeczy, a nawet miałem okazję poznać kilka tajemnic związanych z rozwojem PHP ;)

Pozostając w temacie interakcji międzyludzkich. Niestety odniosłem wrażenie, że nie ma czegoś takiego jak „polska społeczność PHP”. Owszem są programiści, ale ciężko stwierdzić aby była to społeczność żyjąca swoim życiem. Brakowało mi atmosfery panującej chociażby podczas FrontTrends. Brakuje „wiary”. Czegoś co by sprawiło, że programiści nakręcają siebie nawzajem do rozwijania języka i tworzenia nowych zajebistych rzeczy.
Bardziej jestem skłonny stwierdzenia, że na tej konferencji byli zwykli klepacze kodu. Ludzie, którzy nie potrafili pojąć najprostszych prawd („po co używać dwóch monitorów?”, „jaki może być zysk z korzystania ze skrótów klawiszowych?”)..

Równie negatywne mam odczucia w stosunku do projektu liga.php.pl, którego zarys został przedstawiony. Z tego co zrozumiałem ma to być projekt (działający w formule cyklicznego konkursu), który ma na celu wyłonienie tzw „ligi” programistów PHP w kraju. Idea może i ciekawa, aczkolwiek później okazało się, że cała inicjatywa tak naprawdę ma być jedynie czymś w rodzaju narzędzia do head hunting’u.

Na konferencję jechałem z małym kompleksem dotyczącym własnych umiejętności. Muszę przyznać, że szybko się z niego wyleczyłem. Czy warto było jechać? Myślę, że tak. Co prawda wykłady były raczej słabe, ale rozmowy w kuluarach wniosły pewną świeżość. Choćby dlatego warto jechać.
Mam nadzieję, że kolejna edycja odbędzie się w bardziej dostępnej lokalizacji. Jeśli nie to niech przynajmniej hotel będzie wyremontowany, a drzwi nie będą wypadać po byle pchnięciu ;)

<?php
abstract class Blog {

  private $mAuthor;

  private $mBlogCategories;

  public function __construct($pAuthor, array $pBlogCategories) {
    $this->mAuthor = $pAuthor;
    $this->mBlogCategories = $pBlogCategories;
  }

  abstract public function helloWorld();

}

// let's see how it will end..
$blog = new Blog('Radosław Mejer', array(
  'PHP',
  'JavaScript',
  'IT',
  'Programming',
));

$blog->helloWorld();
?>

Tym oto słowem wstępu po raz kolejny podejmuję się pisania bloga technicznego. Z czasem zobaczymy czy moje postanowienie zostanie prawidłowo realizowane.