Programista z doświadczeniem zarówno w projektach biznesowych (CRM, B2B) jak i web 2.0.
Zwolennik zwinnego podejścia do wytwarzania oprogramowania hubert@dzbanyit.pl
Ostatnio miałem nieprzyjemność integrowania płatnego komponentu ExtJs. Chodzi konkretnie o komponent kalendarza, który wyglądem oraz funkcjonalnością naśladował Google Calendar. Niestety kod był napisany fatalnie, powiązania pomiędzy poszczególnymi elementami kalendarza były bardzo zawiłe co strasznie utrudniało integracje z istniejącym systemem. Śledzenie wywołań poszczególnych metod i ich parametrów przysparzało sporo problemów. Dodatkowo dodawanie linijki console.log(arguments) wewnątrz cudzych funkcji nie jest eleganckie (choć i tak często to robię…)
Chcąc ułatwić swoją pracę i jednocześnie rezygnując z niepotrzebnego ingerowania w obcy kod stworzyłem bardzo prostą w działaniu funkcję do monitorowania wywołań metod wskazanych klass i obiektów (W przypadku biblioteki ExtJs sprawuje się ona znakomicie).
/**
* Klasa monitoruje wywołania metod klas i obiektów
*
* @author Hubert Marzec
* @param {Object} source - klasa/obiekt
* @param {string} className - nazwa klasy
* @param {array} methodList - wyszczególniona lista metod
*/
function classLogger(source, className, methodsList){
if (source &&
(typeof source == "object" || typeof source == "function")) {
source = source.prototype || source;
className = className || '';
methodsList = methodsList || [];
var name, method;
var vLog = (typeof(console) !== 'undefined'
&& console != null) ? console.log : function(){};
for (name in source){
method = source[name];
if (typeof method == "function" && (methodsList.length ?
(inArray(methodsList, name)): true )){
source[name] = function(name, method){
return function(){
var vResult = method.apply(this, arguments);
vResult = vResult || '';
vLog(className + '->' + name,
arguments, ' = ',
vResult
);
return vResult;
};
}(name, method);
}
}
}
function inArray(pArray, value) {
pArray = pArray || [];
for(var i=0, all=pArray.length; i < all; i++) {
if (pArray[i] == value) {
return true;
}
}
return false
}
}
Działanie jest dość proste: wybrane lub wszystkie (gdy nie wyszczególniono konkretnych) metody danego obiektu/klasy zostają opakowane przez dodatkową funkcje, która jest odpowiedzialna za wyświetlanie nazwy, parametrów wejściowych oraz wyniku funkcji oryginalnej.
Całość jest wyświetlana przy pomocy konsoli przeglądarki. W przypadku gdy konsola nie jest zaimplementowana, logi nie są wyświetlane. Przykładowe sposoby użycia classLoggera:
var mainWindow = new Ext.Window({
title: 'okno'
});
classLogger(Ext.Panel, 'Ext.Panel');
classLogger(mainWindow, 'mainWindow');
Będzie mi bardzo miło, jeżeli ten fragment kodu się komuś przyda. Jednocześnie jestem otwarty na uwagi i waszą opinię.
Zend Framework to oczywiście najlepszy framework php ;) ale ma bardzo duże problemy co do wydajności. Przy prostych stronach można to jeszcze przeżyć, ale dla aplikacji, które są przewidziane na duże obciążenie staje się to już drzazgą w tyłku.
Na pierwszy ogień pójdzie funkcja Zend_Loader::registerAutoload() - korzystanie z niej jest bardzo wygodne, ale wpływa bardzo negatywnie na szybkość i ilość pamięci wykorzystywanej przez skrypt.
Przeprowadźmy test
Aby test miał więcej wspólnego z rzeczywistością zostanie wykonany na już przygotowanym frameworka do pracy. Z grubsza będą to:
Inicjacja i sprawdzenie autoryzacji na podstawie Zend_Acl (lista praw trzymana w pliku php)
Wykryciu i ustawieniu odpowiedniego locale (Zend_Locale)
Ustawienia widoku
Nawiązanie połączenia z bazą danych MySql
Ustawieniu routes na podstawie plików ini
Inicjacji Zend_Mail_Transport_Smtp
Akcja indexAction() jest pusta, nie jest wykonywane żadne zapytanie do bazy danych. Do profilowania kodu użyłem zmodyfikowanego Profiler’a z Magento Ecommerce. Kod został podzielony na odpowiednie bloki: application, setup, dispatch, action. Na mojej maszynie mam:
PHP 5.2.4
XDebug 2
Apache 2
MySql 5
Samych parametrów mojego komputera nie będę podawał, bo nie ma to sensu, nie będziemy zwracać uwagi na same liczby, a raczej na różnice procentowe.
Nas najbardziej interesuje pierwszy rząd ‘Application’ obejmująca całą aplikacje. Najbardziej przykuwającą oko rzeczą jest pożerana pamięć 5MB.
Teraz przygotujemy plik require.php, który dołączamy jak najwcześniej w bootstrapie. Plik zawiera dołączenia wszystkich klas aplikacji (Zend i nasze własne), które były potrzebne do uruchomienia strony testowej.
Pomimo prawie 3-krotnie zmniejszonego czasu (0.25 vs 0.09), pamięć wykorzystana przez skrypt zmalała z 5 MB do 326 KB, to ponad 10-krotnie mniej!!! Nawet sobie nie wyobrażacie jaka to ulga dla serwer.
Listę plików ładowanych podczas działania aplikacji można otrzymać dzięki informacji jakie klasy zostały załadowane (w ZF jedna klasa to jeden plik), a tę informacje dostajemy dzięki funkcji get_declared_classes().
Wniosek
Na etapie tworzenia aplikacji spokojnie możemy ze względu na wygodę używać Zend_Loader::registerAutoload(), zaś już w czasie wdrażana zdecydowanie sugeruję rozwiązanie podobne do mojego.
Prosty projekcik (to nie znaczy, że nie pracochłonny): formularz z ponad 130 polami, podzielonymi na około 75 kroków. Ktoś miał się zając przygotowanie formularza: html, css i walidacją na poziomie javascript!? Ja miałem to obsłużyć w php (jak będzie o tym w kolejnym wpisie, bo problem jest nawet ciekawy).
No i otrzymuje z pewnym opóźnieniem nie gotowy ‘formularz’. A tam lista pytań z nazwami pól: q1, q2, q3…q45…q99… a często miedzy nimi q9a, q9b, q9c WTF? Formularz był nie kompletny i musiałbym jeszcze między q9a a q9b wsadzić jeszcze 20 pytań. Gdybym sam umieścił w bazie danych pola o takich wartościach - to potem stałbym się bohaterem podobnego wpisu, a programista zajmujący się tym później projektem wyzywałbym mnie od dzbanów.
Nie pozostało mi nic innego jak samemu wszystko robić od nowa.
Wniosek: czasem lepiej zrobić wszystko od początku niż korzystać z czyiś rozwiązań.
Chciałem ustawić kilka wirtualnych hostów pod apache, przy czym zachowująć możliwość przeglądania głównego drzewa katalogów poprzez 127.0.0.1.
Największy problem był w tym, że działał tylko jeden wirtualny host, jak dodawałem kolejny wszystko się sypało. Strasznie długo szukałem rozwiązania tego problemu. Żadne (nie)oficjalne materiały nie pomagały. Ale udało się. Mam nadzieje, że oszczędzi wam to dużo czasu.
Rozwiązanie
Konfiguracje opieram na apachu 2.x dostępnym w super paczce xampp i system win xp. Przy innym zestawieniu nazwy plików mogą się różnić, ale filozofia jest taka sama. Otwieramy plik \apache\conf\extra i umieszczamy kod podobny do tego:
Bardzo polecam obejrzeć prezentacje Marka Hołyńskiego jaką przeprowadził na Bootstrapie 8.5. Dowiecie się z niej między innymi o historii startup’ów, o różnicy w rozwiązywaniu problemów w akademicki i biznesowy sposób. Poznacie również co się stało z pewnym geniuszem z Grecji.
Tytuł może być trochę mylący. Bo nie jest możliwe przesyłanie plików poprzez XMLHTTPRequest! Wszystkie znane Wam z sieci ajaxowe uploady bazują na pewnych trickach, o których zamierzam opowiedzieć.
IFrame
Tak to ten sam stary dobry IFRAME, kiedy web nie miał jeszcze numerka, a na stronach www rządził kolor szary i niebieskie linki. Minęło trochę czasu i ten znacznik został zapomniany, a nawet wyklęty. Jednak bez ten trick nie będzie mógł działać.
Przepis jest prosty. Formularz z polem input type="file" umieszczamy w pływającej ramce. Kiedy naciśniemy przycisk wyślij - zacznie się uplodowanie pliku, ale wszystkie pięknie w ‘tle’. Można dodać indykator tak, aby użytkownik myślał, że to ajax.
Plusy
Bardzo proste rozwiązania
Minusy
Brak paska postępu
Można uploadować tylko jeden plik naraz
Flash upload
Kolejne rozwiązanie bazuje na flashu i javascript. Cały proces uploadu przechodzi przez ‘niewidzialny’ obiekt flasha. Sposób jest prawie idealny… problem tkwi w tym, że flash uruchamia własną sesje. A to jest dużym problemem (np. autoryzacja). Rozwiązanie tego problemu to przejęcie sesji, w php można to uzyskać poprzez session_id($passedId).
Moim zdaniem najlepsza implementacją uploadu z wykorzystanie flashu jest biblioteka swfupload. Jest napisane w czystym javascripcie, dlatego można ją integrować z dowolnym innych kodem js. Ma sporą społeczność i jest ciągle rozwijana
Test jednostkowy (unit test) to fragment kodu, który sprawdza inny fragment kodu - to najprostsza definicja jaką udało mi się znaleźć. Poza podstawowym celem jakim jest eliminacja błędów, dzięki używaniu testów jednostkowych zyskamy m.in
redukcje kosztów
bezproblemowy refactoring
wyższą jakość kodu
Eliminacja błędu jest tym bardziej kosztowna im projekt jest dalej posunięty w czasie. Szczególnie eliminacja błędów na etapie wdrożenia zabiera dużo czasu i zasobów. Co gorsze wpływa negatywnie na nasz profesjonalizm w oczach klienta, a może nawet mieć swoje skutki w postaci kar umownych, czy nawet zerwania umowy.
PHP nie jest idealne i od czasu do czasu pojawia się biały ekran śmierci. I nic nie pomoże error_reporting(E_ALL), dalej widzimy pusty ekran. Jedyne co pozostaje to stare dobre echo 'dupa', które trzeba umieszczać w kolejnych sekcjach kodu, szukając miejsca wystąpienia błędu.
Sam udoskonaliłem tę metodę do postaci echo 'd'. Czasami jednak potrzebowałem więcej informacji o zmiennych. Z pomocą przyszedł print_r umieszczony między znacznikami pre, co pozwalało na wyświetlanie zagnieżdżonych tablic. Jednak to nie było to, czego szukałem.
W końcu znalazłem dBug autorstwa Kwaku Otchere. W zagnieżdżonych tabelkach dBug wyświetla tablice, obiekty, xml’a. Wszystko pokolorowane zgodnie z typami danych.
Wystarczy krótki kod new dBug($yourVariable) i wszystko ładnie się wyświetla. Ponieważ mimo wszystko ten zapis był dla mnie za długi i pozostał sentyment do echo ‘dupa’. Stworzyłem pomocniczą funkcję d():
/**
* Debug
* @see echo 'dupa';
* @param mixed $var
*/
function d($var) {
new dBug($var);
}
Użycie pojedynczej litery dla nazwy funkcji kłóci się z ogólnie przyjętymi zasadami nazewnictwa, jednak w przypadku funkcji pomocniczej można zrobić wyjątek. W javascripcie również używam takiego samego strutu d(), co jest bardzo wygodne i przyspiesza pisanie kodu.
Ludzie tworzą pewne standardy, aby ułatwiać życie sobie i innym. W przypadku języków programowanie java czy php, standardy nazewnictwa i kodowania są jasno zdefiniowane (PEAR2 Standards, Zend Framework Coding standard) i wystarczy ich jedynie przestrzegać. W przypadku baz danych jest już gorzej…
Dlatego też postanowiłem wypracować standard, który będę używał w swoich projektach. Cześć rzeczy może budzić kontrowersje, dlatego bardzo liczę na waszą reakcje. Wpis ma status ‘open’ i na pewno będę wprowadzał do niego zmiany.