Uwaga, blog przeniesiony

Posty na tym blogu już nie będą się pojawiać. Zapraszam gorąco pod nowy adres: blog.grzegorzpawlik.com



Subskrybuj ten blog...

środa, 10 grudnia 2008

Dobre praktyki programowania w CakePHP #1

Postanowiłem zbierać gdzieś drobne usprawnienia, przy programowaniu w cake'u, żeby móc później zebrać to w jakąś sensowną listę do zapoznania się "cake'owcom" w naszej firmie.
Padło na blog - zawsze to coś czym można się podzielić z całym światem.

Problem #1:
Mamy taką strukturę (przedstawię za pomocą modeli):
model Photo (id, name) i Tag (id, name),
Photo hasAndBelongsToMany Tag.

W jednym miejscu potrzebne było znalezienie Photos takich, które są powiązane z Tagiem, którego name = 'abc'.

Zostało to wykonane w sposób następujący:
$photos = $this->Photo->Tag->find(" Tag.name ='abc' " );
W widoku z kolei ktoś zrobił
foreach($photos['Photo'] as $photo); gdyż z relacji założył, że wyszukując tag, dostanie powiązane z nim Photos.

Jak widać, dobrał się do tych danych "od tyłu"...
Na pierwszy rzut oka wygląda na to, że jest ok. Ale po jakimś czasie okazało się, że zdjęcia powinny być w kategorii, czyli doszedł nam
Category
i Photo belongsTo Category (photo wzbogacił się o pole category_id).

I poprzednie zadanie musiało zostać zmodyfikowane. Otóż zamiast 'abc' ktoś mógł podać id dla Category w którym jest Photo. Nie można zmienić kodu po prostu dodając warunek, gdyż wyszukujemy tagi, a one nie mają żadnego cateogry_id dla Photos

Nie można zrobić drugiego szukania
$photos2 = $this->Photo->findAll("Photo.category_id = 50") i zrobić zwykłego array_merge, gdyż strulktura zwracanych tablic jest inna
1: Tag.Photo.{n}.id
vs
2: {n}.Photo.id

Więc trzeba robić pętle, która drugą tablicę zmieli i przerobi tak, żeby wyglądała jak pierwsza (i z automatu działała w widoku).
Nawet w tym momencie wydaje nam się, że wszystko gra (jeśli przymknęliśmy oko na nieeleganckie rozwiązanie ze zmianą struktury drugiej tablicy) tak naprawdę brniemy coraz głębiej.

Wyobraźmy sobie, że za dwa miesiące klient ma tak dużo Photos pasujących do kryterium, ze potrzebuje pagination + sortowanie (po nazwie zdjęcia). Jesteśmy w kropce, bo nie załatwimy pagination w zapytaniu sql za pomocą limit - gdyż mamy dwa zapytania. Z sortowaniem to samo. Jak dalej się uprzemy, że brniemy coraz dalej w grząskie tereny - skończymy implementując sortowanie na zcalonych dwóch tablicach - koszmar. Nie od teog są kontrolery!

Odpowiedź (nie idealna, ale też całkiem sensowna) :
Długo się głowiłem jak ten problem ugryźć.

Po pierwsze trzeba przyjąć, że nie próbuję exploitować cake'owych funkcjonalności i dobierać się do dany "wstecz" relacji. W naszym przykładzie - potrzebuję Photos, więc wywołuję metodę z modelu Photo. Takim exploitem jest wykorzystanie modelu (Tag), który jest w relacji z Photo (relacja w przód) i założenie, że Tag też będzie w symetrycznej relacji z Photo i wykorzystam ją do moich celów (tu właśnie jest to działanie wstecz).

Po drugie trzeba wbić sobie do głowy zasadę "Skinny Controller, Fat Model". Jeśli z zasady grzebię tylko w kontrolerach, a boję się ruszyć model - polegnę. Skończę tak, że modele potrafią tylko to co zaimplementowane zostało przez team Cake'a. A cała logika będzie się odbywać w kontrolerze, mimo, że nie cała do niego należy (choćby sortowanie, kryteria wyszukiwania).
Dlatego od samego początku, gdy poczujesz leniwca na ramieniu, który sugeruje, żeby dobrać się do danych od tyłu - przypomnij sobie, że to nie ładne rozwiązanie.

Należałoby raczej na samym początku zdefiniować metodę modelu
Photo::customFind($conditions = null)
W niej wysmarzyć prostego sql'a (napiszę go "na sztywno", w w następnych wskazówkach podam jak poprawnie budować query w modelach)
"Select * from photos as Photo
   Inner Join  photos_tags as pt on (pt.photo_id = Photo.id)
   Inner Join tags as Tag on (pt.tag_id = Tag.id)
WHERE $conditions";
Wtedy na samym początku w conditions bez problemu możemy podać Tag.name = 'abc'.

Po przyjściu nowego problemu wystarczy, ze conditions będzie:
Tag.name = 'abc' or Photo.category_id = 'abc'

Jak potrzebuję sortowanie - dodaję sobie parametr $orderBy i doklejam do mojego query.
Tak samo z pagination etc.

Zauważ, że w pierwszym wypadku Controler puchł, a Model był pusty (oprócz relacji). W przypadku drugim Controler powiększa się co najwyżej o dodatkowe parametry w wywołaniu metody $this->Photo->cunstomFind
Struktura zwracanych wyników jest pod kontrolą i nie trzeba zmieniać widoku.

Zamiast siedzieć 5 godzin nad nowymi problemami, wstukujemy kilka sprytnych linijek i idziemy na piwko.

Podsumowując, jedna z dobrych praktych programowaniu w cakePHP brzmi:
Nigdy nie dobieraj się do swoich danych z d*** strony

środa, 3 grudnia 2008

Hosting idealny, ciąg dalszy

W prawdzie nie miałem w planach spamować o tym na blogu, ale pojawił się komentarz w tym wpisie (Hosting idealny (php, postgreSQL, django, shell)?) i spojrzałem na ich stronę nowości i spodobała mi się ilość nowości dodanych w tym hostingu. Oto one:

e-learning :

tu akurat bez szaleństw, ale warte wzmianki(*):
drzewa genealogiczne (jak dla mnie raczej ciekawostka):
Tu z cyklu "pokaż mi inny hosting, który to potrafi":

Dlatego, jeśli szukasz naprawdę elastycznego hostingu, który ciągle się rozwija (i rozwija się w kierunku wskazywanym i sugerowanym przez użytkowników, czasem potencjalnych) to tylko vipserv.org

* Dlaczego lubię autoinstalacje? Tylko dlatego, że oszczędza mój czas. Czasem z chęcią przyjrzałbym się jakiemuś oprogramowaniu, ale nie chce mi się zakładać bazy, i przechodzić instalacji po to, żeby po 20 minutach to skasować. Jeśli instalacja oznacza dla mnie 2 kliknięcia - czasem się skuszę.

wtorek, 2 grudnia 2008

Zarządzanie wersjami oprogramowania

Najtrudniejsze zadanie z jakim się obecnie spotykam w mojej pracy to zarządzanie wersjami produktu.

Od jakiegoś czasu używamy Subversion i pewnie jest o niebo lepiej niż by było bez tego narzędzia, ale dalej jest cholernie ciężko. Szczególnie wtedy, gdy Ciągle wprowadzane są poprawki w bazie. Bywa tak, że mam trzy wersje do synchronizacji: developerską, do testów wewnętrznych i do testów klienta.

Druga zazwyczaj generuje typową ilość znalezionych bugów, a potem sporą ilość 'feature requests' (przegląda ją między innymi manager projektu, który ma dobre pomysły jak usprawnić pracę w systemie; sęk w tym, że poważnie utrudniają one pracę nad systemem).
Trzecia z kolei generuje szum pt. 'na stronie X, link Z jest w nie takim foncie jak potrzeba' i inne problemy ;)

*Razem tworzą żyzne środowisko, z którego co jakiś czas wylęgają się pomysły wymagające zmiany struktury bazy danych.

**Wszystkie trzy wersje są update'owane w różnych momentach, do różnych wersji repozytorium.

Z * i ** wynika, że powinno się zdarzać (i zdarza, a jak!), że na każdej wersji systemu jest inna wersja bazy danych.

Jeszcze nie koniec.

W teamie mam pewną ilość programistów, którzy nie rozwinęli jeszcze umiejętności polegającej na pisaniu kodu w taki sposób, aby był on jak najbardziej elastyczny (dzięki wszystkim bogom za CakePHP, bo przy przenoszeniu systemu na serwer klienta przepisywalibyśmy wszystkie linki... aż mnie ciarki przeszły). Jednocześnie nie mogę ich za to zdyscyplinować - przecież nie dostali żadnych wytycznych jak pisać kod (kto próbował coś takiego napisać, ten wie...)

No i to w sumie wystarczy, żeby przy końcówce projektu mieć poczucie braku kontroli.

Jednak jest pewne światełko w tunelu (nikt nie wyłączył z powodu kryzysu;)), o którym napiszę w następnym poście...
Niektóre wskazówki znajdziesz wponiższych postach:

Największy sklep internetowy w Polsce... agito pe el ...

...nie pozwala dokonać płatności kartą kredytową.

W ciągu sześciu lat istnienia, z dwuosobowej firmy oferującej podzespoły komputerowe i sprzęt fotograficzny, staliśmy się liderem wśród sklepów internetowych z elektroniką użytkową i wypracowaliśmy sobie mocną pozycję na rynku.

 Nie wiem jak Wy, ale ja nie kupię tam prezentów w tym roku ;)

czwartek, 13 listopada 2008

Hosting idealny (php, postgreSQL, django, shell)?

Wydaje mi się, że znalazłem dla siebie hosting idealny. Serio, to chyba najlepszy polski hosting za rozsądną cenę. Jakiś czas temu szukałem takiego, który dałby mi przynajmniej dostęp przez ssh do plików.
Wiele jest takich, które dają sftp, ale to trochę mało. Po wielu godzinach poszukiwań znalazłem.

Otóż, Panie i Panowie! Najlepszym hostingiem w Polsce jest ...
tutududu...
moim zdaniem oczywiście
VIPServ.org
(zaczynając od pakietu VIP2008). Wymienię listę wspaniałych rzeczy, które oferuje za cenę 9,99zł/miesiąc, oprócz podstaw takich jak php, mysql
- dostęp do konta przez SSH (z uwagi na zdarzające się programowanie w plenerze korzystając z publicznych hotspotów - dobrze choć zaszyfrować hasło ;))
- zainstalowany SVN i zintegrowany z nim bugtracker TRAC
- możliwość opłacania konta nawet na jeden dzień! (nie wiążesz się na cały rok)
- baza danych postgreSQL i sqlite3
- DJANGO!!!
And last but not least
- wspaniała obsługa techniczna!

Jak ta obsługa techniczna wygląda? Otóż w momencie kiedy przeglądałem ich ofertę nie było mowy o pythonie czy django. Dlatego wysłałem zapytanie i po równie krótkiej co przyjemnej wymianie maili - django zostało doinstalowane :D. Po niewielkiej współpracy z adminem udało się przezwyciężyć problem ze współpracą django z sqlite i wszystko śmiga :) Pewnie jest jeszcze kilka szczegółów, które trzeba będzie dopracować. Ale powiedzcie mi:
CZY WASZ HOSTING TO POTRAFI?

Podsumowując: VIPServ.org daje Ci dużą elastyczność przy niewygórowanych opłatach.
Będę teraz prywatnie używał go przez jakiś czas. Jeśli okaże się równie niezawodny co elastyczny - będę go z przyjemnością polecał klientom. Wam już teraz polecam do przetestowania (5dniowy okres próbny + możliwość zapłacenia za na przykład miesiąc).

Od dzisiaj do prywatnych projektów będę używał tylko tego

wtorek, 4 listopada 2008

Dziadostwo

Czasami korzystając z "nowoczesnych" usług internetowych się we mnie zbiera.

Dostanie się teraz usłudze sieci plus gsm o nazwie "Rachunek elektroniczny", którym spotykłem się niedawno. Pominę fakt, że jest nieintuicyjny, bo to oklepane i prawie każdy tak ma. Jak coś jest nieintuicyjne to albo przestaję korzystać z usługi, a jak nie mogę - przyzwyczajam się. Jak nieintuicyjne nie jest - po prostu tego nie zauważam.

Otóż gdy przyszło do zapłacenia pierwszego rachunku... kiepskawo. Śmigam po całym systemie na prawo i lewo w poszukiwaniu numeru rachunku. Znajduję pdf-a. Klikam. Pobieram. Otwieram. Zaznaczam numer, aby go skopiować do zlecenia w systemie banku (nota bene intuicyjnym i bez większych niedoróbek) i... pdf okazuje się zabezpieczony. Mogę sobie skopiwać obrazek z numerem rachunku.

Nie zrażam się, widziałem gdzieś ikonkę z możliwością płatności przy pomocy płatności(.pl). Klikam. Wchodzę. Wszystkie opcje poza płatnością kartą kredytową są zablokowane. A jak nie chcę płacić kredytem, to? Obok mizernie malutkiej lewej kolumny z piktogramami przedstawiającymi firmy obsługujące te transakcje jest szeroka kolumna chwaląca się jakich to banków ten system nie obsługuje. Jest tam nawet logo mojego banku. Co z tego, skoro są wyszarzone i nieaktywne?

Skończyło na tym, że jak za dawnych lat przepisałem numer konta z jednego miejsca w drugie (dobrze, że do perfekcji opanowałem sztukę takiego ustawiania rozmiarów okien, że bez problemu umieściłem je obok siebie).

Pozostaje mi satysfakcja, że nie dostaję rachunków papierowych i nie przyczyniam się wyrąbywania kolejnych hektarów puszczy amazońskiej... pod warunkiem, że drzewami z tej puszczy nie napalono w elektrowniach, które napędzają mój komputer, albo którykolwiek węzeł sieci po drodze.

ps. przypomina mi się podobna akcja w systemie e-bok UPC. Tam cały portal był we flashu (nienawidzę tego) i faktury też były w pdfach. W prawdzie nie były one zabezpieczone i mogłem skopiować tekst. Jednak były jakoś zawsze uszkodzone, więc ów skopiowany numer konta wyglądał nie lepiej niż to:
&^%$#$%^&*()(*&

środa, 15 października 2008

O współczesnej ekonomii

http://www.polityka.pl/rynek-w-cuglach/Lead33,1150,270751,18/

Pod tym linkiem znajdziecie wywiad pana Jacka Żakowskiego z Edmundem S. Phelpsem. Artykuł jest ciekawym głosem w ogólnej wrzawie wywołanym aktualnym kryzysem (wg niektórych finansowym, wg innych już gospodarczym, czy nawet globalnym).
Warto przeczytać nawet- a właściwie w szczególności- wtedy, gdy jedyna wiedza o ekonomii to ta czerpana z dzienników goniących za sensacją i polityków-ekonomów-hobbystów ;)

Gorąco polecam

poniedziałek, 6 października 2008

Nam obiektowo pisać (nie) kazano...

... czyli co się dzieje, kiedy ktoś kto nie wie dlaczego się używa kodu obiektowego, pisze takowy bo musi.
Jest sobie jedna, bardzo wazna metoda w bardzo ważnej klasie, w bardzo ważnym projekcie. Ciągnie się od linii 484 do majaczącej na horyzoncie (i zdecydowanie poza ekranem) linii 745.
Jest w niej o wiele za dużo. Sprawdza czy dane są poprwane, zlicza, podlicza, zapisuje do bazy - sprawdza czy dobrze zapisało, w razie czego robi rollback. Do tego na koniec wysyła maila.
No i bach! Okazało się, że w mailu trzeba wysłać jedną dodatkową informację i jak jej teraz szukać. Tak bardzo chciałbym mieć tą funkcję zrefaktorowaną. Nie mieć zagnieżdżeń, tylko ciąg wywołań prywatnych metod:
$this->zrobTo();
$this->zrobTamto();
$foo = $this->znajdzToITamto($bar);
...
$this->wyslijMaila($dane, $temat, $inneDane);

I tylko w trzech kropkach dodać $dane = array_merge($dane, $foo), albo coś równie banalnego. Ale nie. Najpierw 5 minut na znalezienie gdzie ten cholerny mail jest wysyłany. 10 na zlokalizowanie tablicy z której dane trzeba dołączyć do maila. Potem z 20 minut na poprawki + sprawdzanie czy wsztstko jest tak jak powinno.
No i to nieodparte poczucie. Ten szept na ramieniu mówiący "Możesz być pewien, że w kodzie od teraz masz jedną dziurę więcej (buahahahaha)".

Ale patrzę na motto tego bloga i myślę sobie - trzeba sobie radzić ze swoim kodem...

Co się dzieje, gdy dane są nie tylko w bazie?

Z tym problemem często spotykam się w pracy.

Standardowe zagadnienie - klient chce wrzucać obrazki na stronę, a my ze względu na bazę zapisujemy je jako pliki, a w bazie co najwyżej ścieżkę do niego.
No i niestety przeniesienie systemu na inny serwer (np. produkcyjny) to, oprócz kopiowania kodu i bazy, przenoszenie multimediów, które nie zawsze jest miłe i przyjemne.

Dlatego od jakiegoś czasu chodzi mi po głowie pewna koncepcja, która ten problem mogłaby rozwiązać.
Otóż pliki binarne, podczas uploadu należało by jednak zapisać w bazie. Do tego należałoby opracować komponent, który w przypadku żądania obrazka o id = 1 sprawdziłby odpowiedni katalog i po znalezieniu go - zwrócił jako odpowiedź. Z kolei przy jego braku w systemie plików - utworzył takowy na podstawie danych w bazie i w standardowy sposób zwrócił plik jako odpowiedź.

Świetnie by się do tego nadawały cake'owe behaviors. Do tego można by go sprząc z jeszcze jedną funkcjonalnością- plik mógłby być w bazie zapisywany (w razie podmiany zawartości) za każdym razem pod inną nazwą (np. id_kolejny_numer_wersji). Dzięki temu możnaby tym plikom ustawić nagłówki Expire z odległą przyszłością i korzystać z dobrodziejstw bufora przeglądarek...

czwartek, 2 października 2008

HP DeskJet 930c

W zasadzie nie piszę o sprzęcie jako takim, bo mnie on nie interesuje. Jednak muszę wyrazić mój zachwyt nad drukarką, którą niegdyś, za ciężkie pieniądze, kupił mój Tato (było to jakies 7 lat temu, gdzieś pod koniec liceum).
Nigdy na nią nie narzekałem (chyba, że napełniałem tusz samodzielnie), zawsze wydruki były świetnej jakości. Jednak to co przed chwilą zaszło totalnie mnie zaskoczyło.

Otóż nie uzywałem jej jakies 1,5 roku. Nic z nią nie robiłem oprócz przewożenia po różnych częściąch Krakowa przy okazji kolenych przeprowadzek i chowania jej w jakieś głębokie czeluście szafy.

Od jakiegoś czasu nosiłem się z zamiarem reanimowania jej. Odkładałem to co chwilę, gdyż spodziewałem się prawdziwej katorgi.
Po pierwsze nie miałem pojęcia czy pod Suse będą jakiekolwiek sterowniki.
Jeśli będą - pewnie dysze są tak saschnięte, że trzeba będzie je przeczyścić - jak włączę tą opcję pod Linuksem (muszę przyznać, że ten system, choć znam coraz lepiej - zawsze potrafił mnie zaskoczyć na przykład: http://webbricks.blogspot.com/2008/09/suse-11-subclipse-i-subversion-15.html)
I jeśli uda mi się ją odnaleźć- samoczyszczenie pewnie nie wystarczy i trzeba będzie zacząć zabawę z wilgotnymi husteczkami i czyszczeniem dysz.

Z takim nastawieniem wycieram warstewkę kurzu i szukam na stryszku kabli.
Podłączam...
Bang! System wykrył urządzenie, czy chcę skonfigurować?
Pytanie. Oczywiście!
Pierwszy test - druk maila z thunderbirda. Pierwszy zawód. Drukarka wypluwa kilka stron, a na każdej jedna linijka dziwnych znaków. Nie zrażam się jednak, przypomina mi się, ze w menu Suse widziałem jakiegoś managera hp. Odnajduję, włączam.
On mi mówi, że nie ma drukarek i że mam kliknąć "Konfiguruj". Robię to ochoczo! Konfiguracja jest kilkuetapowa, w każdym mam dokonać wyboru z pośród jednej opcji :D To lubię - nie obawiam się, że coś zchrzanię - po prostu nie mam wyboru.
Drukuję stronę testową i...
Moim oczom ukazuje się tekst ostry jak ŻYLETA! Serio. Jakbym kupił czarny kartridź wczoraj w sklepie. W prawdzie kolor coś nie chce wychodzić, ale co tam - jeszcze nie próbowałem uruchomić czyszczenia dysz, ani kalibracji!
Właśnie się za to zabieram... jestem dobrej myśli...

ps. Kilkukrotnie, gdy w przeszłości kupowałem, czy chociaż pytałem o kartridże do HP930C sprzedawcy mówili "dobra drukarka, niech pan się jej nie pozbywa". Mieli rację.

środa, 1 października 2008

Czego brakuje frameworkowi cakePHP?

Jakiś czas temu zetknąłem się z frameworkiem Django dla języka Python. Oprócz samego języka, który jest zdecydowanie bardziej nowoczesny w Django szczególnie urzekła mnie jedna rzecz.
Chodzi o sposób definiowania struktury dla bazy danych. Otóż w Django odbywa się to tylko w jednym miejscu - w plikach modeli.

Dlaczego jest to takie fajne? Posłużę się antyprzykładem z cakePHP.
Wyobraź sobie, że masz projekt i zarządzasz jego wersjami za pomocą popularnego SVN, czy CVS. Najczęściej pracujesz na lokalu i we wczesnych fazach projektu to dodasz pole w bazie danych w jakimś miejscu, do zmienisz indeks. Robisz commit kodu, który na nowej strukturze działa dobrze, ale struktura bazy danych nie podlega wersjonowaniu.
Zatem albo musisz zrobić eksport struktury do pliku który jest wersjonowany, albo (jak często odbywa się to w moich projektach) - robisz zmiany na pewnej głównej i ogólno dostępnej bazie (co by reszta mogła sobie ściągnąć nową wersję).

Pierwsze rozwiązanie sprawia, że przybywa Ci pracy. Wiem, że to nie dużo, ale zawsze jedna dodatkowa rzecz o której musisz pamiętać. A w przypadku gdy commit zrobiłeś po tygodniu pracy, bo wcześniej cały system się sypał - możesz zapomnieć o takich szczegółach jak dodanie pola na początku.
W przypadku jest jeszcze gorzej. Oprócz problemów opisanych powyżej dochodzi niebezpieczeństwo usunięcia całej bazy, brak możliwości powrotu z bazą do wcześniejszej wersji.

Z kolei w django możesz zapomnieć o phpMyAdminie i innych. Całą strukturę bazy danych masz zdefiniowaną w modelu i jednym poleceniem synchronizujesz jej wersję w silniku bazy z definicją w modelu.

Oczywiście do podejścia trzeba się przyzwyczaić.  W cake'u trzymając się konwencji wszystkie modele mogą wyglądać tak:
class TabelaWFormieLiczbyPojedynczej extends AppModel {};
W django - to co nie istnieje w modelu - nie istnieje w bazie.

Ciekaw tylko jestem jak to się sprawdza przy nietypowych sytuacjach, które nieraz wymagają nieco karkołomnych konstrukcji w projekcie bazy danych i specyficznych zapytań sql. Moja znajomość samego Django jest na ten moment za mała, żeby odpowiedzieć na to pytanie.

sobota, 6 września 2008

Suse 11, subclipse i subversion 1.5

Okazuje się, że nie bardzo się lubią na samym początku.
Po godzinach google'owania i nieudanych próbach kompilacji javahl, wpadłem na banalne rozwiązanie. Okazuuje się, że OpenSuse 11 dostarcza odpowiedni moduł javahl, który może przywrócić sprawną współpracę subclipse z subversion.
Oto co musiałem zrobić:
- w katalogu, gdzie mam zainstalowany eclipse znajeźć plik javahl.jar ( u mnie /configuration/org.eclipse.osgi/bundles/156/1/.cp/lib) i zmienić jego nazwę (powiedzmy na javahl.jar.old)
- podlinkować dobry moduł javahl w to miejsce:
ln -s /usr/lib/svn-javahl/svn-javahl.jar svnjavahl.jar
- zrestartować eclipse i w Window -> Preferences ...-> Team -> SVN w opcji SVN interface wybrać JavaHL (JNI)
Mnóstwo czasu spędziłem nad tym problemem, mam nadzieję, że innym też pomoże.

środa, 27 sierpnia 2008

firePHP zbawieniem twym (w przypadku nieprzejednanych klientów)

Jeśli nigdy nie musiałeś wprowadzać poprawek na systemie w wersji produkcyjnej - jesteś prawdziwym szczęściarzem.
Często jednak bywa tak, że musisz poprawić kilka "pierdół", więc robisz to bezpośrednio. Jednak jeśli Twój klient nie może zaakceptować informacji z debugowania na stronie, a Ty bez nich nie możesz efektywnie pracować - FirePHP przyjdzie Ci z pomocą.

FirePHP to rozszerzenie, do Firebug - rozszerzenia do Firefoxa :)
Pozwoli Ci wyświetlać debug z kodu PHP w konsoli firebuga właśnie. Jak go zintegrować z Cake (1.2) jest opisane na http://bakery.cakephp.org/articles/view/baking-cakes-with-firephp.

Poniżej efekt:


Przy wykorzystaniu tych narzędzi twój debug jest widoczny tylko dla Ciebie (no i jeszcze tych co mają zainstalowany ten dodatek... na 99% Twój klient nie ma).

Życzę Wam i sobie więcej takich narzędzi, które pojawiają się zanim uświadomimy sobie ich potrzebę.

ps. Radzę raczej użyć firebuga w wersji beta 7, gdyż w nowszych pojawia się błąd, w którym aby zobaczyć informacje w konsoli - trzeba najpierw przełączyć się na inną zakładkę, a później do niej wrócić. Zatem jeśli nie potrzebujesz poprawek z wersji beta 15 (taka była najnowsza w momencie pisania tego tekstu) - wybierz 7.

wtorek, 26 sierpnia 2008

Grzechy główne twórców stron

Dlaczego zdarza się, że strony budowane po to by zarabiać, nie zarabiają?

Otóż po pierwsze strona internetowa to nie rower - ten wystarczy złożyć go, pomalować i napompować koła, żeby zaczął jeździć. Ludzie, którzy mają małe pojęcie o działaniu internetu i marketingu internetowego, ale za to mający pieniądze i chcący je pomnażać myślą o stronie jak o takim rowerze. Pokutuje w ich głowach mit super-zarabiających stron, które jakby wyłoniły się pewnego razu pod jakąś domeną.

Dlatego zamawiąjąc na przykład katalog firm, który miałbym konkurować z taką panoramą firm, już cieszą się na krociowe zyski od firm, które tylko czekają na to, żeby zapłacić za kolejne miejsce w internecie w którym się pojawią.

Nawet nie przyszło im do głowy, żeby w jakiś sposób stać się lepszym niż chociażby główny konkurent w segmencie. Czasem wpadają na to, żeby się pozycjonować. Raczej nie myślą o tym, że siłą takiego katalogu jest ilość wpisów (kiedy trafię do ninego przez google - zacznę używać ich wyszukiwarki) i podcinają sobie gałąź na której siedzą wymagając, żeby najprostsze wpisy były płatne. Nie wiedzą co to znaczy zarabianie na bezpłatnych usługach.


Druga sprawa to oszukiwanie już zdobytych klientów na przykład poprzez umieszczanie w top searches arbitralnie wyznaczonych elementów, lub nawet reklam bez informowania o tym użytkowników. Jest to w pewnym stopniu przeciwieństwo poprzedniego podejścia (ale zamiast odchodzenia od komercjalizacji niemal każdego znaku na stronie, raczej ukrywanie tej komercjalizacji).

W tym wypadku przyszły właściciel strony jest już świadomy, że coś co nie jest za darmo nie jest mile widziane, jednak nie zastanowił się dlaczego internauci nie porzucili google za to, że wyświetla sponsorowane wyniki wyszukiwania (nawet wyraźnie o tym informując).

Otóż elementy komercyjne elementy (pojawiąjące się w danym miejscu bezpośrednio za sprawą pieniędzy, w przeciwieństwie do tych "naturalnych" manifestacji sieci ), jeśli nie liczyć migających banerów, nie są tak na prawdę opluwane przez każdego internautę bez wyjątku.

Chodzi o to, że link sponsorowany, jeśli jest wyraźnie oznaczony jako sponsorowany niesie ze sobą ważną informację, że ta firma naprawdę podejmuje działania marketingowe. Poważnie podchodzi do zdobywania klientów (i skoro wydaje forsę na ich zdobywanie, to pewnie poważnie będzie podchodzić do ich obsługi, żeby ta forsa nie była wyrzucona w błoto) i zależy jej na tym, żebyś poznał jej ofertę.

Zatem jeśli znajdę się w katalogu firm i wejdę w kategorię >przeprowadzki< raczej wejdę w kilka wykupionych pozycji na pierwszej stronie, żeby wiedzieć jaką ofertę mają firmy, które rzeczywiście zapłaciły za bycie na tej stronie (w odróżnieniu od tych, co mają darmowy wpis bo "jak za darmo, to czemu nie").
Ukrywając więc komercyjne treści tracimy ten efekt, do tego strona, które "nie ma" treści komercyjnych jest podejrzana - nikt za swoje nie będzie utrzymywał serwera, serwisu i pewnie pracowników... więc może wszystko jest sponsorowane, a właściciel żongluje sobie danymi jak chce... wracam do google ;)


Zatem istnieją dwa ekstrema:
- zbyt silne przywiązanie do pieniądza i próba wyciśnięcia go z każdego miejsca strony (przypomina próbę wyciskania cytryny, zanim ta dojrzeje)
- zbyt wielki strach przed byciem posądzonym o należenie do poprzedniego ekstremum i wstydliwe ukrywanie tego objawów.


Jak zawsze istnieje reguła złotego środka - znajdź go. Jeśli masz być właścicielem takiej strony - weź to sobie do serca. Jeśli taką stronę masz zaprojektować i zbudować - uświadom swojego klienta o co tak na prawdę chodzi.

poniedziałek, 4 sierpnia 2008

HttpSocket z cake 1.2 w cake 1.1.x

Jeśli potrzebujesz funkcjonalności zapewnianej przez HttpSocket(na przykład musisz pracować na danych w xml-u dostarczanych choćby przez kanał rss), a z jakichś powodów nie możesz korzystać/migrować na CakePhp1.2, zastosuj poniższą sztuczkę:

Z biblioteki CakePhp1.2 (cake/libs/) do katalogu vendors w Twojej aplikacji (1.1.x) skopiuj pliki
- socket.php
- http_socket.php

W pliku socket.php gdzieś na początku, przed definicją klasy jest linia
uses('validation');
zakomentuj ją (lub usuń) .
Z kolei w pliku http_socket.php linię
uses( 'socket', 'set');
Zamień na
vendor('socket');
uses('set');

Sposób działający pomiedzy wersją 1.2.0.6311 beta a 1.1.16.5421.
Powodzenia

czwartek, 19 czerwca 2008

Jak korzystać z requestAction w CakePHP

Ta bardzo przydatna funkcja nie jest zbyt dobrze opisana w dokumentacji, dlatego pozwolę sobie ją tu opisać.

Object::requestAction(string $url, array $extra);
Służy do łatwego wywoływania funkcji z jednego kontrolera w innym kontrolerze. Sprawa jest prosta, jeżeli wywoływana funkcja potrzebuje jedynie parametrów, które możemy przekazać w url-u (/posts/show/1).
Jednak sprawa komplikuje się, kiedy potrzebna metoda korzysta z danych przesłanych w formularzu. Komplikuje się o tyle, że w wyniku ubogiej dokumentacji, trudno odgadnąć czy jest to możliwe.
Jednak m.in. po to jest parametr $extra. Jeśli wasz kontroler czeka na dane z formularza ['User']['username'] i ['User']['password'], to można je "wcisnąć" do kontrolera poprzez requestAction w następujący sposób:

$data['data'][['User'] = array('username'=> 'ala', 'password' => 'makota');
$this->requestAction('/users/login', $data);

Dzięki tej sztuczce uda Ci się ominąć kilka sytuacji, w których wcześniej pisalibyście osobną funkcję.

Ps. Cake jest pełen takich nieudokumentowanych niespodzianek, które czekają na odkrycie - zachęcam do eksperymentów.
Pps. Szczególnie przydatność requestAction odczujesz, kiedy zaczniesz intensywniej korzystać z pluginów w CakePhp.

piątek, 13 czerwca 2008

Czy każdy może być programistą?

Właśnie teraz, w przerwie w pracy nad komponentem, który miałby automatycznie generować menu w panelu administracyjnym w zależności od zdefiniowanych (w bazie) uprawnień przypomniały mi się słowa jednego z wykładowców UO.
Próbował on odróżnić programistów od normalnych ludzi(sic!). Powiedział, że normalny człowiek, mając za zadanie wykopać 100 metrowy rów weźmie łopatę, tydzień będzie kopał i wykopie. Programista z kolei tydzień będzie myślał, aż wymyśli metodę. Drugi tydzień będzie tą metodę implementował. I po dwóch tygodniach rów będzie wykopany.
W tym momencie na sali zapanowała cisza, może nawet konsternacja, a po chwili doktor dodał "Ha! Ale my teraz takich rowów możemy wykopać 50!".

Kim zatem jest programista? Przede wszystkim jest leniem. Śpieszę dodać: leniem w dobrym tego słowa znaczeniu. Leniem, który nie znosi powtarzać zadania. Leń ów wie do czego służą maszyny, a w szczególności komputery. Jeśli jest coś co poddaje się algorytmizacji - leń-programista nie zrobi tego sam. Leń programista widzi, że wykopanie rowu to:
1. wbić łopatę
2. podnieść łopatę z ziemią
3. odrzucić ziemię w bok
4. sprawdzić, czy już 100 metrów
4a. jeśli nie - powtórz,
4b. jeśli tak - idź po wypłatę

Dlatego śmiem twierdzić, że im bardziej boi się pracy (w sensie "roboty") programista, tym lepiej to o nim świadczy. No i może to nieskromnie zabrzmi, ale drugi dzień pracuję nad tym dynamicznym menu, choć mógłbym w godzinę zrobić to "na sztywno"... i dobrze mi z tym (również dlatego, że przyjdzie następny projekt i nie będzie trzeba tego robić).

poniedziałek, 19 maja 2008

Bardziej przyjazne linki w CakePHP

Załóżmy, że mamy naszego nieśmiertelnego bloga.
Aby wyświetlić dany post, generujemy mniej więcej taki link za pomocą helpera html:
http://domena.pl/posts/show/23

Powiedzmy, że chcemy być SEO friendly i doklejamy na końcu tytuł postu:
http://domena.pl/posts/show/23/bardziej_przyjazne_linki_w_cakephp

Niby wszystko ładnie, pięknie, ale słowa posts i show w adresie zmniejszają siłę pozostałych słów, i strona będzie się pozycjonować w google gorzej np. na słowa linki + cakephp.

Lepszym byłby link:
http://domena.pl/23/bardziej_przyjazne_linki_w_cakephp

Można łatwo sprawić, żeby tak to działało, bez większych problemów.

Przede wszystkim edytujemy app/config/routes.php
i dodajemy do nich linię:

$Route->connect('/:id/*', array('controller' => 'posts', 'action' => 'show'));

dzięki temu zabiegowi ruter będzie nam linkował adresy w takiej postaci do kontrolera posts, metody show.

Teraz należy zmodyfikować metodę show, aby działała przy takim przekierowaniu:
1. umożliw wywołanie funkcji bez podania parametru id:

function show($id=null){

2. w wypadku nowego przekierowania id znajduje się w $this->params['id'] :

$id = (isset($this->params['id']))? $this->params['id']: $id;

Bardzo fajnie, ale teraz przestaje działać stary (standardowy) sposób przekierowania taki adres:
http://domena.pl/posts/show/23
zwróci błąd missing controller.

Łatwo temu zaradzić dodając jeszcze jedną linię w pliku routes.php:

$Route->connect('/static_pages/view/:id/*', array('controller' => 'static_pages', 'action' => 'view'));

Koniecznie powyżej tej poprzednio dodanej!

środa, 14 maja 2008

Benchmarking w Google Analytics

Czy jak zacząłeś używać Google Analytics i dostałeś pierwsze wyniki, że twój bounce rate jest na poziomie 90%, a średni czas na stronie to 30 sekund, zastanawiałeś się, czy to dużo czy mało? Teraz odpowiedź na to pytanie jest łatwiejsza.

Jakiś czas temu Google Analytics udostępniło ciekawą funkcję (w wersji beta oczywiście), tzw. benchmarking. Po uprzednim zgodzeniu się na zbieraniu przez Google (anonimowych) statystyk z Twojej strony dostajesz możliwość porównanie niektórych parametrów z ogółem stron (korzystających z Analyticsów oczywiście).

I możesz na przykład dowiedzieć, się, że

Twój bounce rate niemal dwukrotnie przewyższa średni bounce rate dla stron o podobnym rozmiarze
jak również:

Średni czas spędzony przez użytkownika na stronie jest o niebo lepszy od konkurenci ;)

Wiesz już nad czym się skupić, jeśli chcesz przyciągnąć klientów, nieważne czy to sklep internetowy, portal czy blog ;)

wtorek, 13 maja 2008

Co jest ważniejsze: funkcjonalność, użyteczność czy marketing?

Jeśli zadałeś sobie kiedykolwiek takie pytanie, to znaczy, że dałeś się wciągnąć w potencjalną świętą wojnę, między działami Twojej firmy.

Programiści pewnie forsowali funkcjonalność, projektanci użyteczność (ang. usability)*, dział marketingu - wiadomo. I wiesz co? Najfajniejsze jest to, że wszyscy oni mają rację!
Jak to możliwe? Ok, wzbogacę to zdanie i wszystko będzie jasne:
Wszyscy oni mają rację, z ich własnego punktu widzenia.
Ty jednak, jako menadżer zobligowany jesteś do widzenia, jeśli nie na ostatecznym poziomie, to chociaż z szerokiej perspektywy. Dlatego musisz wiedzieć (i jeśli chcesz - uświadomić swój zespół), że jest tak:

Bez funkcjonalności nie da się spełnić wymagań użyteczności, nie da się też prowadzić marketingu (produktu, który nie istnieje). Bez użyteczności oprogramowanie o najbogatszym zestawie funkcji jest koszmarne w użytkowaniu, a trudno prowadzić marketing do kiepskiego narzędzia (łatwiej to zrobić z kiepskim szamponem). Na koniec - najgenialniejsze systemy o interfejsie tak intuicyjnym, że przy pierwszym kontakcie każdy jest zaawansowanym użytkownikiem, bez jakiejkolwiek instrukcji jest bezużyteczne, jeśli nikt o tym nie wie.

Ubarwiając wpis pozwolę sobie na metaforę: po co Ci do wyścigów koń, który ma mocne serce, a nie ma nóg? Po co Ci taki, który ma nogi, ale słabe serce? Po co Ci taki, który ma mocne serce i nogi, ale nie ma głowy (innymi słowy - padlina)? Biznes to wyścigi, w których zarabiasz, kiedy jesteś w czołówce, nie tracisz w peletonie, a bankrutujesz w ogonku.


*w Polsce na szczęście coraz większą uwagę zwraca się na to kryterium jakości oprogramowania, do niedawna było to mizerne.

piątek, 9 maja 2008

Off-topic: kim Ty jesteś?

Jako stuprocentowy blogger mam podpięte Google Analytics, żeby wiedzieć kto mnie czyta. Czasem je przeglądam i zaskoczeniem były dla mnie statystyki dotyczące przeglądarek:


Całkiem inne proporcje niż w swoich statystykach pokazuje choćby W3Schools. Dlatego śmiem twierdzić, że odwiedza mnie tu awangarda polskiego Internetu.

Zatem gratuluję dobrych wyborów i zapraszam ponownie.

* przy okazji, skoro ten blog traktuje o technologiach internetowych, to poznaj kolejną z usług Google:
Google charts

środa, 7 maja 2008

Odkrycie miesiąca

Jakiś czas temu spędziłem dwa dni na poszukiwaniach narzędzia, w którym mógłbym projektować system przy pomocy UMLa i wygenerowac kod w PHP. Niestety bezowocnie. Kiedy wczoraj przez przypadek trafiłem na BOUML.
Byłem pogodzony z tym, że takie narzędzie nie istnieje, dlatego tym większe było moje zaskoczenie, że nie dość, że potrafi on generować kod PHP (i nie tylko o jesze C++, Python, Java) to potrafi zrobić reverse engineering istniejącego kodu. Z przyjemnością wrzuciłem do niego źródła CakePHP i stworzyłem diagram klas wrzucając istniejące w tym frameworku klasy :)
Już widzę jak przyjemnie będzie wplatać aplikacje we framework już w trakcie projektowania.

poniedziałek, 5 maja 2008

Odpowiednie narzędzia

W poprzednich postach( Czego tak na prawdę ode mnie chcesz [kliencie] ? i Funkcjonalność czy bajery?) napisałem o podstawowych niebezpieczeństwach jakie czyhają na nas w procesie tworzenia dedykowanego systemu dla klienta. W obu przypadkach w pewnym sensie ochroną przed tymi zagrożeniami będzie odpowiedni framework. Poznałem (lepiej lub gorzej) do tej pory trzy frameworki: CakePHP, ZendFramework i Django. Intensywnie pracuję z CakePHP i na nim oprę ten artykuł, pozostałe znam na tyle, żeby mieć pewność, że w podobny sposób pomagają w produkcji oprogramowania i żeby nie musieć opisywać każdego z osobna.

Głównym celem frameworku jest usprawnienie pracy programisty. Ma on dostarczać mechanizmy, które odciążają nas od żmudnego powtarzania nieciekawych czynności (łączenie z bazą, tworzenie milionowego formularza, pisanie kolejnego foreach żeby wydłubać coś z tablicy...). Nieciekawych oczywiście z punktu widzenia doświadczonego programisty, który robi to już długo - zaczynając przygodę z tą dziedziną wszystko jest ciekawe, ale ten stan po jakimś czasie mija.

Jeśli jesteś kompletnie zielony - odradzam zaczynanie przygody z programowaniem od poznania frameworku. Wydaje mi się, że postępując w ten sposób trudniej będzie Ci zrozumieć mechanizmy w nim zachodzące i zamiast mieć możliwość pełnego wykorzystania narzędzia, stanie się on dla Ciebie magiczną różdżką, której potencjału nie wykorzystasz. (oczywiście są wyjątki i możesz robić co chcesz).

Ja jednak nie będę skupiał się na dostarczanych przez framework bibliotekach, bo o tym już wiele napisano. Spróbuję za to napisać jak można go wykorzystać do przeciwdziałania trudnym tendencjom pojawiającym się w procesie produkcji i nie mających wiele wspólnego z samym programowaniem.

Na początek problem specyfikacji wymagań. Maksymalne starania Twoje i klienta, żeby dokładnie określić wymagania i tak nie zabezpieczą Cię w stu procentach. Zawsze po drodze okazuje się, że przydało by się coś innego. Coś mogło by działać lepiej i że pojawił się nowy pomysł. Tak jak z budowaniem samochodu - wkładając wysiłek w obliczenia będziesz pewny, że wycieraczki obejmą maksymalnie przednią szybę, ale dopiero jak się nim przejedziesz to stwierdzisz, że drążek zmiany biegów jest za krótki, a słupek boczny ogranicza Twoje pole widzenia. Gdybyś produkował samochody doświadczenie mógłbyś wykorzystać dopiero w następnym modelu. Ty jednak jesteś farciarzem jakich mało i produkujesz ciągi zer i jedynek, które możesz w każdej chwili zmienić/wyciąć/dodać! Mechanizm zwany w CakePHP Scaffold jest tym, co owo wycinanie, zmienianie i wklejanie czyni bezbolesnym w początkowej fazie produkcji.

W CakePHP scaffolding objawia się w dwóch formach. W pierwszej jest w stanie wygenerować fizycznie modele, kontrolery i widoki na podstawie struktury bazy danych (i podpowiedzi użytkownika). Jest to wykonywane przy pomocy skryptu bake.php znajdującego się w cake/libs/scripts (w wersji 1.19). Efektem tego jest powstanie wszystkich standardowych metod i widoków do operacji takich jak list/view/edit/add/delete. Z uwagi na to, że są one generowane fizycznie - możesz je zacząć zmieniać dostosowując je do swoich potrzeb. Jednak minusem jest to, że w razie zmiany struktury bazy trzeba albo na nowo wygenerować widoki i metody, albo edytować je ręcznie.
Drugą emanacją jest scaffold właściwy. Włącza się go w prosty sposób dodając w kontrolerze linię

var $scaffold;

(Tutaj uwaga - można włączyć scaffold 'globalnie' dodając tą zmienną w AppController, ale wtedy nie należy dodawać już jej w kontrolerach).
Efektem tego będzie każdorazowe generowanie metody i widoku dla standardowych operacji na podstawie struktury bazy danych (tej zdefiniowanej w modelach). Dziać się będzie to tak długo, aż nie usuniemy deklaracji zmiennej, albo nie zdefiniujemy w kontrolerze którejś z metod standardowych. W pierwszym przypadku scaffolding wyłączymy całkowicie dla danego kontrolera, w drugim nie będzie on działał dla metody, którą już zdefiniowaliśmy. Minusem jest to, że nie możemy dostosować widoku do naszych widzimisię (widok nie istnieje fizycznie na dysku)*, ale za to przy każdej zmianie struktury bazy danych widok jest uaktualniany bez naszej ingerencji (dodanie pola varchar zaowocuje pojawieniem się w formularzu pola input, text - textarea, enum - listy wyboru).
To rozwiązanie jest genialne w najwcześniejszej fazie produkcji oprogramowania. Wyobraź sobie, że po zaprojektowaniu samochodu od razu możesz się nim przejechać (fakt będzie brzydki, bez maski i tapicerki) i zobaczyć jak się nim jeździ i czy nie przydałby się większy bagażnik. Tak właśnie jest w tym wypadku: projektujesz bazę danych i już możesz pokazać klientowi podstawowe funkcjonalności**. Możecie z klientem UŻYWAĆ tej aplikacji i stwierdzić, że czegoś brakuje.
Oczywiście nie jest tak super cały czas - w pewnym momencie trzeba po prostu napisać trochę kodu, czasem aplikacji nie wpisują się tak łatwo w szablon listuj/wyświetl/dodaj/edytuj/usuń.
Jednak w 8 przypadkach na 10 sprawdza się doskonale. Dorzuć do tego dobrze zaprojektowaną, znormalizowaną bazę i początkowa faza, która obfituje w zmiany jest tak bezbolesna, jak to tylko możliwe.

* te pliki istnieją fizycznie na dysku, są cache'owane, ale nie nadaje się to do edycji (zostanie nadpisane prędzej, czy później). Do tego wydaje mi się, że istnieje możliwość edycji szablonu, który definiuje jak mają być tworzone widoki, jednak nie zajmowałem się tym do tej pory, więc nie będę o tym pisał.
** pod warunkiem, że nauczysz go patrzeć na system pod tym kątem. Jak klient chce tylko ładny interfejs i nie współpracuje to scaffold nie spełni się w 100%.

poniedziałek, 28 kwietnia 2008

Funkcjonalność czy bajery? Uzupełnienie

Kilka słów dodatkowo do posta http://webbricks.blogspot.com/2008/04/funkcjonalno-czy-bajery.html

Ostatnio odbyłem ciekawą rozmowę z kolegą z pracy na temat wyglądu
aplikacji podczas jej budowania. Słusznie zauważył, że dużo przyjemniej
pracuje się na stronie, która ładnie wygląda. Od siebie dodam, że zawsze
należy uprzyjemniać sobie pracę w dowolny sposób, byleby to
uprzyjemniania całkowicie pracy nie zastąpiło.
Innymi słowy: można postawić na biurku doniczkę z kwiatkiem, ale jak
postawimy ich 20 to już nam się laptop nie zmieści ;)

środa, 9 kwietnia 2008

Funkcjonalność czy bajery?

Ten wpis zacznę od wyjaśnienia tytułu. Chodzi mi o problem, który
pojawia się w początkowym procesie tworzenia projektu i podjęcie złej
decyzji (albo jej brak i zaufanie do intuicji) ciąży już na całym projekcie.
Funkcjonalność to coś, co aplikacja potrafi zrobić. W prostym
przykładzie bloga to możliwość dodania wpisu, edycji, dodania
komentarza, usunięcie ich etc.
Pod pojęciem "bajery" kryje się to co widać: ładne ikonki, dobra
organizacja elementów na stronie (menu, przyciski, linki), AJAX (tak, wg
mnie to bajer wizualny, ale o tym później), web 2.0-owe pregress bars i
indicators ;)

(Ten post jest kontynuacją http://webbricks.blogspot.com/2008/04/czego-tak-na-prawd-ode-mnie-chcesz.html)

W czym tak na prawdę tkwi problem: w zatraceniu się w pierdołach. Ty
budując aplikację (szczególnie od zera, gdy jest BARDZO niedojrzała)
musisz skupić się na funkcjonalnościach, które ma wypełniać. Jednak
bardzo łatwo ulec pokusie zbyt wczesnego upiększania aplikacji.
Nie podoba Ci się jak domyślnie wygląda tabelka, zamiast linka edytuj
fajnie by było mieć ikonkę, a treści mogłyby się dodawać bez
przeładowania strony... i zaczynasz tworzyć style tylko dla tej tabelki,
umieszczasz ikonki w niepełnym layoucie, implementujesz Ajaxa nie
wiedząc co jeszcze będzie w tym widoku. Efekt jest taki, że robisz
rzeczy na tym etapie niepotrzebne, które są pracochłonne i nie przynoszą
satysfakcji.
Do tego odrywasz się od głównego problemu, który brzmi "zbuduj
DZIAŁAJĄCĄ aplikację", który jest święty i nadrzędny. Bez osiągnięcia
tego celu, upiększenia na które straciłeś tyle czasu i energii, są
niepotrzebne. Poza tym, gdy nie masz doświadczenia i intuicji kogoś kto
ma pięcioletnie doświadczenie w budowaniu aplikacji, bardzo łatwo coś
popsuć (szczególne za pomocą wszędobylskiego Ajaxa <o tym poniżej>).

Nie jestem przeciwnikiem Ajaxa, uważam ten trick (bo przecież nie
technologię) za coś fantastycznego, jednak użyty w niewłaściwym momencie
może wiele popsuć. Jest jak czosnek, który poprawia smak smażonych
potraw, ale dodany za wcześnie jest gorzki i obrzydliwy. Tak samo zbyt
wczesna implementacja rozwiązań w Ajaxie sprawi, że projekt stanie się
trudny w trawieniu.
Dużo lepszym, IMHO, podejściem jest stworzenie aplikacji, która będzie
działać w "czystym" html-u. Łatwiej ją przetestować, a tym bardziej
debugować. Pozbyć się w tym czasie poważnych błędów, zaprojektować
interfejs i dopiero wtedy skupić się na tym, żeby aplikacja była miła
dla oka. Twierdzę, że w tym momencie nie będziesz implementował w
funkcjonalności nic ponad proste, wspomagające funkcje współpracujące z
requestami ajaxowymi. Jestem niemal pewny, że większość tych requestów
będzie mogła działać na standardowych funkcjach, które już
zaimplementowałeś (ew. po drobnej przeróbce).
Podejście od przeciwnej strony owocuje powstaniem osobnych,
specyficznych funkcji dla Ajaxa, obok standardowych. Poza tym
wykrywanie, namierzanie i usuwanie błędów jest trudniejsze.

Nie wiem czy Cię, drogi czytelniku, przekonałem? Jeśli nie, to nie
szkodzi. Jeśli jesteś programistą, to sam pewnie stwierdzisz, że przy
"przeajaxowaniu" projektu w początkowej fazie tylko Ci przeszkadza.
Jeśli jesteś menadżerem, to nie Ty się będziesz z tym problemem użerał
bezpośrednio (jeśli jednak zauważysz, że projekty jakoś dziwnie grzęzną
przy pewnym etapie produkcji, to zastanów się, czy to nie jest problemem).

I tu otarliśmy się o drugą stronę medalu - odbiorcę. Może nim być
klient, gdy masz własne zlecenie, albo szef/menadżer projektu, którzy
nie mają inżynierskich podstaw i inżynierskiego spojrzenia na świat (*).
Postępują oni wtedy jak typowy użytkownik: oceniają aplikację
emocjonalnie - czy się podoba, czy nie. Swoją drogą ja też tak robię,
zanim przekonam się do aplikacji, którą zaczynam używać. Uważam, że nic
w tym złego, jeśli to jest produkt finalny. Jednak na etapie budowy
aplikacji trzeba przedstawić im argumenty, dlaczego nie powinni się na
razie skupiać na wyglądzie.
Powiedz odbiorcy: "słuchaj, to jest wersja beta 1 systemu, który
zamawiałeś. Wiem, że wygląda topornie, ale nie skupiaj sie teraz na tym,
ani na pomysłach, że jakiś przycisk powinien być gdzie indziej. Skup się
proszę na rzeczach, które można za pomocą tego systemu wykonać i pomyśl,
czy to jest to czego chciałeś podając swoje wymagania."
Tu już ocieramy się o AgileDevelopment, więc doczytaj sobie o tym w
wikipedii.

Cały sens tego artykułu można zredagować tak: rdzeniem Twojej aplikacji
jest funkcjonalność. Ty jesteś specem, który nie pozwala sobie na
narzucenie sposobu myślenia klienta- dla jego własnego dobra. Klient nie
ma (bo po co miałby ją wypracowywać?) dyscypliny odpowiedniego patrzenia
na projekt. To Ty musisz mu ją narzucić. Jeśli stanie się na odwrót Ty
zaplączesz się w niuansach i komplikacjach, ktoś z Was dwóch za to
zapłaci, a oboje rozstaniecie się niezadowoleni.

Niedługo coś, bez czego duży projekt obyć się nie może - baza danych i
dlaczego nie może być "nienormalna".


*- nie twierdzę, że muszą mieć. Jest mnóstwo menadżerów, którzy są z
nimi z zawodu i nie znają technologii - wszystko jest ok, jeśli uważnie
słuchają pracowników liniowych.

wtorek, 8 kwietnia 2008

Czego tak na prawdę ode mnie chcesz [kliencie] ?

W czym problem? Otóż gdy przychodzi do nas klient, to mówi, że chce
stronę, gdzie po lewej stronie będzie miał kategorie, a po kliknięciu
owych pojawią się linki do artykułów, a po klinknięciu na nie pojawi się
strona, której treść będzie mógł edytować. Do tego w tych kategoriach
chce mieć galerię, po kliknięciu na którą mają się rozwinąć galerie
"taka" i "siaka", po kliknięciu na którą ma się pojawić galeria ze
zdjęciami. Ma być możliwość dodania i usunięcia zdjęcia z galerii, ale
też, jak się okaże potrzebne - dodanie galerii "owakiej". Menu ma mieć
tło niebieskie, a strona z tekstem - filetowy. Linki po najechaniu mają
się robić różowe, a o góry ma być logo firmy.

Jeśli nie miałeś jeszcze do czynienia z klientem, to może Cię to
zdziwić, ale tak właśnie klienci definiują swoje wymagania co do strony
(nie wszyscy oczywiście, ale znaczny odsetek). Zadziwiające jest też,
jak wiele używają słów i jednocześnie jak słabo definiują swoje
wymagania. Na tym etapie najważniejsze dla Ciebie informacje to :
- edytowalne strony
- kategorie dla stron
- galerie
Niezła esencja ;) To czego nie wiesz to na przykład:
- jak bardzo klient chce móc ingerować w wygląd stron, czy chce
wprowadzać treści w html-u, może wystarczy mu uproszczenie w postaci
czegoś a'la BBCode, czy potrzebuje WYSIWYG?
- czy to logo firmy, chce móc zmienić od czasu do czasu?
- czy galerie mają mieć swój opis?
- czy zdjęcia w galerii mają mieć swój podpis?
- czy kategorie mają mieć jeden poziom, czy może wiele (pod-kategorie,
pod-pod-kategorie itd.)?

Zauważ proszę, że pytań jest więcej niż rzeczy, które już wiesz. Jeśli
dasz sobie narzucić formę epopei preferowaną przez klienta, będziesz
miał wiele prozy do przeczytania, a ilość wiedzy będzie rosła w trakcie
czytania co najwyżej logarytmicznie - strata czasu.

Ty, kontaktując się z klientem jesteś analitykiem, więc powiedz mu, że
na początku należy się skupić na funkcjonalności, a nie na tym, gdzie
jaki link ma być. Poinformuj go, że wiesz iż dla niego to jest bardzo
ważne, ale w tym momencie te informacje bardziej przeszkadzają niż
pomagają. Ty jako profesjonalista, który napisze aplikację zgodnie z MVC
możesz ZAWSZE zmienić wygląd aplikacji później.
Dobrym sposobem ograniczenia zapędów klienta jest nauczenie go pracy z
diagramem przypadków użycia
(http://en.wikipedia.org/wiki/Use_case_diagram). Nie jest to trudne- w 5
minut można wytłumaczyć jak z tego narzędzia korzystać. Jednocześnie
narzędzie to wyklucza definiowanie wyglądu, interakcji, czy wymagań
technicznych. Umożliwia jedynie zdefiniowanie FUNKCJONALNOŚCI. A o to
nam właśnie chodzi.

Podsumowując: to Ty, jako analityk jesteś profesjonalistą i musisz
zaopiekować się klientem, który robi to po raz pierwszy. Korzyścią jest
oszczędność czasu, lepsze zrozumienie się z klientem, danie klientowi
impulsu, aby dobrze skupił się i sprecyzował wymagania, a to daje w
efekcie jego i Twoje zadowolenie. Również oszczędza wasz czas (nie
chodzi mi tylko o wyświechtaną frazę, że czas to pieniądz). W przypadku,
gdy popełnimy błędy my nie będziemy wiedzieć co zrobić (albo będziemy
wiedzieć i będziemy się mylić), klient będzie niezadowolony z efektu bo
nie tego chciał (i na pewno nie powie, że to jego wina) ktoś te błędy
będzie musiał naprawić i ponieść koszty.

W następnej części: funkcjonalność kontra bajery... może trochę o
podejściu Agile.

Kontynuacją tych rozmyślań jest post: http://webbricks.blogspot.com/2008/04/funkcjonalność-czy-bajery.html

sobota, 22 marca 2008

"Sprytny" redirect (cakePHP 1.2)

Załóżmy, że mamy mały system bloga (posty + komentarze).
Cake upiekł za nas widoki i metody. No i powiedzmy, że teraz w post/view/[id_posta] chcesz umieścić możliwość dodania komentarza. Oczywiście tworzysz formularz, którego action='/comments/add' i w CommentsController::add() wpisujesz na sztywno (hard-coding) $this->redirect('/posts/view/'.$comment['post_id']).

Wszystko pięknie i ładnie działa, ale powiedzmy teraz chciałbyś w widoku posts/list_all, w którym wyświetlasz skrócone wersje wszystkich postów dać możliwość szybkiego dodania komentarza. No i znów pakujesz do środka formularz z action='/comments/add' i fajnie działa, ale redirect jest do jakiegoś jednego konkretnego posta, a nie listy w której szanowny user kliknął 'submit'. Niby nic złego się nie dzieje, ale szanowny user odczuwa dyskomfort, jest wręcz zagubiony.

Ostatnio wymyśliłem i z powodzeniem zastosowałem mały trik, który ten problem rozwiązuje w sposób uniwersalny i (w miarę) elegancki.
Zazwyczaj, jeśli tworzę formularz do innego kontrolera, to najczęściej wtedy wiem, gdzie po przerobieniu tych danych powinien się odbyć redirect... najczęściej z powrotem. Zatem preparuję parametr action w następujący sposób:

action=<?php echo $html->url('/comments/add') ?>?r=/posts/list_all/"

A w AppController
    function beforeRender(){        if(isset($this->params['url']['r'])){             $this->redirect($this->params['url']['r']);          }     }

Prosta metoda, która zawsze sprawdzi mi czy przekazałem informację o przekierowaniu i w razie czego ustawi odpowiedni adres przekierowania. Nie muszę pamiętać o tym w każdej metodzie, w której jest prawdopodobne, że będę chciał zrobić redirect w różne miejsca. Po prostu implementuję to na początku projektu i cieszę się kolejnymi zaoszczędzonymi minutami :D

Smacznych jajec ;)

środa, 5 marca 2008

Less is better (o web usability słów kilka)

Wpadła mi do rąk książka Stevena Kruga pt. "Nie każ mi myśleć. O życiowym podejściu do funkcjonalności stron internetowych", którą gorąco wam polecam. Można ją "łyknąć" w jeden weekend i wiele się dowiedzieć ciekawego. Jeśli masz jakikolwiek wpływ na wygląd stron, które oprogramowujesz- warto znać konwencje, które sprawią, że Twoja strona będzie wygodniejsza w użyciu dla odwiedzających.

Czy wiedziałeś, że nawet zmiana nazwy "szukaj" (która jest znana użytkownikom) na "Szybkie wyszukiwanie" może mieć wpływ na odbiór (nie pozytywny) Twojej strony? To drobnostka, ale jeśli takich będziesz miał więcej na stronie- użytkownik może nie wrócić do Twojej witryny i wybrać konkurencję, której w internecie nie brakuje.

Oto link do strony autora tej książki http://www.stevekrug.com/

Preload obrazków w cakePHP

Dla tych, dla których DRY nie jest tylko angielskim słowem napisanym z niewiadomych przyczyn wielkimi literami.
Usprawnienie preloadu obrazków w cakePHP może przydać się, gdy chcesz wyświetlić typową galerię: miniaturki + duży obraz zmieniający się po kliknięciu w miniaturkę. Kiedy zaimplementujesz preload obrazków, to znaczy, że jesteś świadomym budowniczym stron internetowych. Wiesz, że możesz wykorzystać przeglądarkę do przyśpieszenia działania Twojej strony, a nie tylko jej biernego wyświetlania. Zatem do dzieła!

Moje podejście obiera się na tzw. elemencie. Element to (wg. manuala CakePHP) "częściowy layout". Czyli cegiełki, z których możesz stworzyć swoje strony.

app/views/elements/image_preload.thtml:
<?php $images_count = count($images); $i = 0;?>  <script type="text/javascript"> //<![CDATA[  var _images = new Array();  <?php $i=0; foreach($images as $image): ?>    _images[<?= $i ?>] = new Image();   _images[<?= $i++ ?>].src = '<?= 'http://'.$_SERVER['SERVER_NAME'].$this->webroot.'img/'.$subdir.$image['filename'] ?>';     <?php endforeach; ?>       //]]> </script>
O co chodzi? Wpisz w google "javascript image preload" to się dowiesz :)
Jak z tego skorzystać? Już śpieszę z wyjaśnieniami:
W pliku Twojego layoutu, na samym końcu (przed </body> ofcoz) dodaj:
 <?php if(isset($images_to_preload) && isset($images_to_preload_subdir)): ?>   <?= $this->renderElement('image_preload', array('images'=>$images_to_preload, 'subdir'=>$images_to_preload_subdir)); ?>  <?php endif; ?>
A w metodzie odpowiedniego kontrolera wygeneruj tablicę z nazwami plików, które chcesz załadować do cache przeglądarki i przekaż do layoutu:
   $this->set('images_to_preload', $content['Photo']);    $this->set('images_to_preload_subdir', 'photos/'); 
U mnie akurat model Photo ma pole filename, więc tak napisałem sobie element. Do tego dodałem możliwość przekazania z kontrolera podkatalogu w którym są obrazki w /app/webroot/img (teraz już nie wiem dlaczego, ale jak to pisałem, na pewno było to uzasadnione ;))

wtorek, 4 marca 2008

webservices cakePHP pogryzły się z serwerem SOAP

W poprzednich postach pokazałem sposób na stworzenie serwera soap we frameworku CakePHP, a ten po pewnym czasie przestał działać. Okazało się, że włączenie webserwices w /app/config/core.php spowodował problem. Otóż po tej operacji link_do_aplikacji/soap nie oznaczał już linku do SoapController::index(), ale do Controller::index().
Po prostu po właczeniu webservices oczekiwał czegoś w stylu:
link_do_aplikacji/soap/stuff, który wywołał by StuffController::index() i wyrenderował widok w /app/views/stuff/soap/index.thtml.
Niby nic, a może życie uprzykrzyć.

czwartek, 21 lutego 2008

CakePHP + nuSOAP serwer + autoryzacja othAuth

Autoryzacja serwera soap jest o tyle niewdzięczna, o ile nie została zaimplementowana w samym protokole. Do tego sam cake nie ułatwia nam sprawy. Jednak po wielu dniach zmagań udało mi się nagiąć tą materię i z chęcią się doświadczeniami podzielę (tak naprawdę po uporaniu się z tym problemem postanowiłem założyć ten blog).
Załóżmy, że serwer, który opisałem w poprzednim poście już stoi. Załóżmy też, że jest zrobione wszystko, co musiało być zrobione, aby autoryzacja othAuth działała w naszej aplikacji (jeśli nie- przed kontynuowaniem zapraszam tutaj )
Po pierwsze zmieńmy w /app/config/core.php definicję stałej CAKE_SESSION_COOKIE na 'PHPSESSID' bez tego nie uda nam się "odzyskać" sesji znając jej id.
Dalej... logowanie "normalnego" usera wygląda mniej więcej tak:
/app/controllers/user
function login()
{

if(isset($this->params['data']))
{

$auth_num = $this->othAuth->login($this->params['data']['User']);

return $auth_num;// potrzebne dla soap-owego logowania (1 jeśli udane)
}
}


Wracajmy teraz szybko do naszego oczka w głowie: /app/controllers/soap_controller.php.
W metodzie _defineTypes dodajmy definicję złożonych typów:

$this->server->wsdl->addComplexType (
'LoginData',
'complexType',
'struct',
'all',
'',
array(
'Login' => array('name' => 'Login',
'type' => 'xsd:string'), //
'Password' => array('name' => 'Password',
'type' => 'xsd:string'), //
)
);
/**
* odpowiedĹş na logowanie
*/
$this->server->wsdl->addComplexType (
'LoginResponse',
'complexType',
'struct',
'all',
'',
array(
'Result' => array('name' => 'Result',
'type' => 'xsd:int'), //
'SID' => array('name' => 'SID',
'type' => 'xsd:string'), //
)
);


A w metodzie _registerMethods() zarejestrujmy metodę SoapController.login:

$this->server->register('SoapController.login',
array(
'SoapParam' => 'tns:LoginData'),
array(
'return' => 'tns:LoginResponse') ,
$this->namespace,
$this->namespace . '#login',
'logowanie'
);


Teraz właściwe logowanie:

function login($login_data) {
$login_details['User'] = array('username' => $login_data['Login'],
'password' => $login_data['Password'],
'cookie' => "0");
$result = $this->requestAction('users/login', array('data' => $login_details));
return array(
'Result' => $result, 'SID' => session_id() );
}


Kilka słów wyjaśnienia: w pierwszej linii tworzymy trochę sztucznie tablicę $login_details, która jest taka jaką oczekuje metoda UsersController::login() (zgodnie z zasadą DRY nie będziemy tworzyć osobnej metody dla logowania przez soap). Wywołujemy akcję users/login przekazując do niej spreparowane dane i zwracamy zgodnie z definicją typu LoginResponse- tablicę z danymi.


To była ta łatwiejsza część. Teraz to nad czym spędziłem kilka intensywnych dni- jak odzyskać sesję??
Od razu sprostowanie: wygląda na to, że sesja "trzyma się" tak długo, jak długo klient się nie rozłączy (testowałem to za pomocą również nuSOAP + php i w okresie trwania skryptu - było ok). Jednak ja potrzebowałem możliwości podania przez klienta id sesji, którą otrzymał podczas logowania. Jak to zrobić?
Po kolei:
Dodajmy typ, który będzie służył do przekazania informacji na temat zalogowanego usera:
$this->server->wsdl->addComplexType (
'UserDetails',
'complexType',
'struct',
'all',
'',
array(
'Id' => array('name' => 'id',
'type' => 'xsd:int'), //
'Username' => array('name' => 'Username',
'type' => 'xsd:string'), //
'Email' => array('name' => 'Email',
'type' => 'xsd:string'), //
'GroupId' => array('name' => 'GroupId',
'type' => 'xsd:string'), //
'Error' => array('name' => 'Error',
'type' => 'xsd:string')
)
);


Zarejestrujmy metodę SoapController.check():

$this->server->register('SoapController.check',
array(
'data' => 'xsd:string'),
array(
'return' => 'tns:UserDetails') ,
$this->namespace,
$this->namespace . '#check',
'logowanie'
);


I zdefiniujmy ją:

function check($data="") {
$data = $this->requestAction('/users/soapCheck', array('data' => $data, 'SID'=>$data));
$this->log($data, LOG_DEBUG);

if(
$data){
$return = array ( 'Id' => $data['User']['id'],
'Username' => $data['User']['username'],
'Email' => $data['User']['email'],
'GroupId' => $data['Group']['id'],
'Error' => 'ewrifing ok');
}else{
$return = array ( 'Id' => -1,
'Username' => ''
'Email' => '',
'GroupId' => '',
'Error' => 'not logged in');
}

return
$return ;
}


Wyjaśnienia: oprócz danych jak przy logowaniu pojawiło się jeszcze pole w tablicy 'SID', to tędy przekażemy informację, że chcemy "wymusić" jakieś id sesji.
Teraz metoda UsersController::soapCheck():


function
soapCheck($data) {
return $this->othAuth->getData();
}


No i na koniec gdzieś musimy wymusić inne id sesji (no bo nie ma przeglądarki, ani kochanych cookies, które zrobią to za nas). W app/app_controller.php:

if(isset($this->params['SID'])){
session_id($this->params['SID']);
}


I ok... jaaasne. Nie działa, prawda? Dlaczego? A to dlatego, że w momencie kiedy klient SOAP łączy się z naszym ukochanym serwerem SOAP wygląda to tak:
request (soap/index) -> app_controller -> soap_contropper -> check -> request (/users/soapCheck) -> app_controller*->users_controller -> soapCheck ...

miejsce oznaczone gwiazdką oznacza moment, kiedy app_controller dostaje info o tym, że jest jakieś SID... tylko że wtedy to już jest za późno (jak to mówią ślązocy: "po ptokach" :P) $this->requestAction to moment, kiedy ciasteczka już nie są wysyłane i szanowna pani Sesja ma już głęboko w ... nosie fakt, że tam jakieś id jest przesyłane.
Nie lękaj się jednak, jest na to rada:
zmień w app_controller poprzednio dodany blok na:
if(isset($this->params['url']['SID'])){
session_id($this->params
['url']['SID']);
}


I teraz łącząc się z serwerem do linku /twoja_aplikacja/soap/ doklejaj parametr SID=. W takim wypadku już przy pierwszym request->app_controller id sesji trafia do "świadomości" Sesji i zazwyczaj rozpatruje je pozytywnie.

Nie jest to może najbardziej eleganckie rozwiązanie. Jednak najlepsze na jakie teraz mnie stać ;) Jeśli masz coś fajnieszego- pisz w komentarzach, z chęcią się podszkolę :)

wtorek, 19 lutego 2008

CakePHP + serwer SOAP (pierwsze kroki)

Mimo tego, że cake chwali się, że jest WebServices ready tyczy się to jedynie tak zwanego routingu (np. gdy użyjesz users/index - będzie "normalnie" czyli wyrenderuje widok /vews/users/index.thtml, a gdy "xml/user/index" - /views/users/xml/index.thtml) ale nie jest to prawdziwe WebSerwice. Nie wiem ile jest sposobów na zmianę CakePHP w prawdziwy serwer SOAP, ja znam jeden i Wam go pokażę.

1. Potrzebna nam biblioteka SOAP (jako prawdziwy programiści pozwalamy, żeby najtrudniejsze rzeczy pisali za nas inni). Ja wybrałem NuSOAP. Wrzuć pliki do /app/vendors/nusoap/*.
2. Stwórz kontroler do obsługi Soap. W moim przypadku soap_controller.php
/**
* Soap Serwer
* @package package
*/
vendor('nusoap/nusoap');
class
SoapController extends AppController {
var
$name = 'Soap';
var
$server;
var
$namespace;
var
$layout = 'blank';
var
$components = array('othAuth','Conf');
var
$othAuthRestrictions = null;

/**
* Inicjalizacja ustawień serwera
*
*/
function _init() {
$this->namespace = $this->Session->host;
$this->server = new soap_server();
$this->server->debug_flag = false;
$this->server->configureWSDL('MyWsdl', $this->namespace, 'http://'.$_SERVER['HTTP_HOST'] . $this->webroot . $this->params['controller'] ); // należy nadpisać endpoint, gdyż domyślnie ustawi się na /twoja_aplikacja/app/index.php
$this->server->wsdl->schemaTargetNamespace = $this->namespace;
$this->_defineTypes();
$this->_registerMethods();
}

/**
* Serwer endpoint handler
*
*/
function index(){
Configure::write('debug', 0);

$this->_init();

$HTTP_RAW_POST_DATA = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
$this->server->service($HTTP_RAW_POST_DATA);
exit();
}


/**
* define types required by this server
* przyda się później
*/
function _defineTypes() {

}

function
_registerMethods() {

$this->server->register(
'SoapController.hello', // method name
array('name' => 'xsd:string'), // input parameters
array('return' => 'xsd:string'),
$this->namespace,
$this->namespace . '#hello',
'document', // style
'encoded' // use
);

}

function
hello($name){
return array(
'return' => 'hello, '.$name);
}

}

?>

3. Do tego stwórz model, który nic nie robi :)
class Soap extends AppModel {
var $name = 'Soap';
var $useTable = false;
}
?>


Teraz objaśnienia:
SoapController::index() - to twój endpoint serwera SOAP. SoapController::_registerMethods() zajmuje się rejestrowaniem metod dostępnych przez serwer. W tym wypadku tylko jednej: SoapController::hello (notacja metod soap to Klasa.metoda).
Metoda hello przyjmuje string jako parametr i zrwaca 'Hello, '+ to co zostało jej przekazane.
Przykładowy klient (poza cake.php):
require('./nusoap/nusoap.php');

function
pr($var) {
echo
"<pre>";
var_dump($var);
echo
"&lt/pre>";
}

/* create client */
$endpoint = "http://localhost/meta/application_in_cakePHP/soap/index";

$mynamespace = "";
$client = new soapclient($endpoint);

$err = $client->getError();
if (
$err) {
// Display the error
echo '

Constructor error: '

. $err . '

'
;
// At this point, you know the call that follows will fail
}

$response = $client->call('SoapController.hello', array('name' =>"Greg"));
echo
"Call SoapController.hello";
if (
$client->fault) {
echo
'

Fault: '

;
print_r($response);
echo
'

'
;
} else {
// Check for errors
$err = $client->getError();
if (
$err) {
// Display the error
echo '

Error: '

. $err . '

'
;
} else {
echo(
'response:');
pr($response);
}
}

?>


I ładny response:
string(11) "hello, Greg"
Dodatkowo, gdy w przeglądarce wpiszesz ścieżkę do kontrolera Soap zobaczysz ładną dokumentację Twojego serwera. A dodając do ścieżki ?wsdl - dokument wsdl.

żródła:
http://www.scottnichol.com/nusoapprog.htm
http://dietrich.ganx4.com/nusoap/faq.php


Uwaga! blog przeniesiony

Posty na tym blogu już nie będą się pojawiać. Zapraszam gorąco pod nowy adres: blog.grzegorzpawlik.com
Komentowanie artykułów możliwe jest pod nowym adresem.