Xis’ blog - Mój wirtualny schowek

Zrób sobie przerwę

Dość intensywnie ostatnio pracujÄ™ (dużo pracy, a do tego taka ilość ciekawych rzeczy dookoÅ‚a, że nie sposób nie poÅ›wiÄ™cić na ich badanie wieczora lub dwóch), do pracy tej używam rzecz jasna komputera. OdbiÅ‚o siÄ™ to niestety negatywnie na moim samopoczuciu, możnaby rzec, ze dopadÅ‚o mnie coÅ› na wzór RSI, tyle że dotyczyÅ‚o wzroku i bólów gÅ‚owy. Już tak mam, że kiedy “wkrÄ™cÄ™” siÄ™ w jakÄ…Å› czynność, coÅ› mnie zainteresuje, potrafiÄ™ pracować bez przerw i kompletnie nie kontrolujÄ™ tego ile czasu zajmuje mi praca. Skupiam siÄ™ wtedy na wykonywanej czynnoÅ›ci maksymalnie, zapominajÄ…c o Å›wiecie poza niÄ…. PostanowiÅ‚em wiÄ™c coÅ› w tej sprawie zrobić i – oprócz zdroworozsÄ…dkowego odstawienia na jakiÅ› czas komputera – znaleźć narzÄ™dzie, które pozwoli mi nieco zadbać o swoje zdrowie w pracy z nim. ZnalazÅ‚em dwa programy, ktore umożliwiajÄ… kontrolÄ™ nad przerwami.

Pierwszy z nich to EyesRelax. Programik ten umożliwia ustawienie odstÄ™pu czasowego miÄ™dzy przerwami i – uruchomiony w tle – odlicza nam czas na do odpoczynku, gdy ten nadejdzie, EyesRelax zasÅ‚ania ekran komputera biaÅ‚ym tÅ‚em. CiekawÄ… jego funkcjÄ… jest Parent Mode, która pozwala na caÅ‚kowite zablokowanie komputera na czas przerwy (odblokowanie może nastÄ…pić dopiero po podaniu hasÅ‚a). Programik ekstremalnie prosty, ale speÅ‚niajÄ…cy dokÅ‚adnie swoje zadanie. Niestety jednak, nie do koÅ„ca, bowiem jedna jego wada spowodowaÅ‚a, że zaczÄ…Å‚em rozglÄ…dać siÄ™ za innym rozwiÄ…zaniem. EyesRelax ma jakiÅ› błąd, która sprawia, że biaÅ‚e plansze nie zawsze zasÅ‚aniajÄ… pulpit, czÄ™sto samoczynnie wÄ™drujÄ… sobie w tÅ‚o i – jeÅ›li używa siÄ™ zmaksymalizowanego okna jakiegoÅ› programu – użytkownik nie ma pojÄ™cia, że wÅ‚aÅ›nie w tej chwili powinien zrobić przerwÄ™. Ja niestety potrzebujÄ™ narzÄ™dzia, które – nawet brutalnie – wyrwie mnie z amoku programowania i każe odpocząć :) Dlatego zaczÄ…Å‚em szukać dalej.

ZnalazÅ‚em WorkRave. To narzÄ™dzie bardzo pozytywnie zaskoczyÅ‚o mnie iloÅ›ciÄ… możliwoÅ›ci konfiguracji, oraz samÄ… koncepcjÄ… walki z RSI. Program ten oferuje trzy sposoby zabezpieczenia przed przepracowaniem – i wszystkie można stosować naraz:

  • Mikroprzerwy – krótkie odpoczynki pozwalajÄ…ce “odetchnąć”, przede wszytskim oczom i szyi,
  • Odpoczynki – dÅ‚uższe przerwy na np. spacer, Å›niadanie, ćwiczenia, albo po prostu zrobienie sobie herbaty,
  • Kontrola caÅ‚kowitego czasu pracy – skutecznie powstrzymuje przed niechcianymi nadgodzinami.

Poza samym odliczaniem czasu, WorkRave mile zaskakuje możliwoÅ›ciÄ… dodania wstÄ™pnych “ostrzeżeÅ„” o tym, że zbliża siÄ™ przerwa, a także bibliotekÄ… porad dotyczÄ…cych ćwiczeÅ„ (akomodacji oczu, mięśni szyi, rÄ…k itd.) które możemy robić w trakcie odpoczynku – ilość proponowanych ćwiczeÅ„ jest caÅ‚kiem spora, a same ich opisy dość szczegółowe (z obrazkami ;) ). Ponadto warto zauważyć, że program można przełączać w różne tryby pracy (Normalny, Cichy i Zawieszony), dziÄ™ki czemu nie jest on nachalny np. gdy oglÄ…damy na komputerze film. NarzÄ™dzie to posiada też opcjÄ™ pracy w sieci, ale nie udaÅ‚o mi siÄ™ jej jeszcze przetestować.

GorÄ…co zachÄ™cam do używania takich kontrolerów czasu pracy, niech najlepszym dowodem ich skutecznoÅ›ci bÄ™dzie fakt, że skoÅ„czyÅ‚y siÄ™ moje problemy z oczami i bólami gÅ‚owy, ba – kiedy dokÅ‚adnie stosujÄ™ siÄ™ do zaleceÅ„ WorkRave, dzieÅ„ w pracy mija mi tak, że wracam do domu w ogóle nie bÄ™dÄ…c zmÄ™czonym!

Tagi: , ,

Komentarze (2)

Grails – DataTable z filtrem i pagerem

Mojej przygody z Grails ciÄ…g dalszy. ZauważyÅ‚em, że framework ten posiada bardzo wiele fajnych wtyczek. JednÄ… z nich jest grails-ui, który korzystajÄ…c z dobrodziejstw Yahoo! User Interface Library pozwala na wstawianie ciekawych komponentów na stronÄ™ za pomocÄ… zaledwie jednego znacznika GSP. Jednym z takich komponentów jest DataTable, bazujÄ…cy na yui:dataTable, pozwalajÄ…cy np. na pobieranie danych za pomocÄ… żądaÅ„ asynchronicznych AJAX w formacie JSON (lub XML, wedle życzenia), dzielenie wyników na podstrony (pager), mechanizm skórek itd. Znacznik GSP gui:dataTable posiada wiele opcjonalnych atrybutów, którymi można skonfigurować naszÄ… tabelÄ™ z danymi (ilość maksymalna wierszy na jednej podstronie, format danych, kolumny itd.). Mam jednak wrażenie, że autor wtyczki nie pomyÅ›laÅ‚ o możliwoÅ›ci dołączenia do naszej tabeli filtrowania wyników. Owszem, w Sieci można znaleźć przykÅ‚ady filtrowania dla ‘goÅ‚ego’ yui:dataTable, ale wiÄ™kszość z nich, albo nie wspiera ciÄ™cia na podstrony, albo filtruje wyniki dopiero po stronie klienta (a z serwera pobiera peÅ‚nÄ… listÄ™ danych). Dla mnie to niestety za maÅ‚o.

Oto przykład na to, jak korzystając ze znacznika GSP gui:dataTable zaimplementować dataTable, który daje się połączyć z prostym filtrem wyników.

Do przedstawienia przykładu wykorzystam dane dostępne na stronie z przykładami użycia dataTable w YUI.  Załóżmy więc, że mam bazę danych restauracji (pizzerii).

d12

Użyję więc klasy dziedzinowej (ang. domain class) Restaurant o postaci:

class Restaurant {
    String title
    String address
    String city
    String state
}

ListÄ™ tÄ™ chcÄ™ mieć dostÄ™pnÄ… pod adresem http://localhost:8080/PagedDatatable/restaurant/list, czyli potrzebny mi bÄ™dzie kontroler RestaurantController, a w nim metoda list. W metodzie tej, kontroler nie ma zbyt wiele roboty – po prostu serwuje statycznÄ… stronÄ™, toteż jej postać jest taka:

    def list = {
    }

Niezbyt skomplikowane, prawda? :) Pusta metoda sprawi, że kontroler od razu zabierze się za renderowanie wyniku, strony GSP, która wygląda tak:

<g:javascript library="yui" />
<gui:resources components="['dataTable']"/>
 
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="layout" content="main" />
    <title>Restaurant List</title>
  </head>
  <body>
    <div class="yui-skin-sam" style="width: 600px;">
      <label for="fltr[title]">Title:</label><input type="text" id="fltr[title]" value="">
      <label for="fltr[city]">City:</label><input type="text" id="fltr[city]" value="">
      <button id="filterButton">Filter</button><br/>
 
      <gui:dataTable
        id="myDataTable"
        controller="restaurant"
        action="listAsJSON"
        rowsPerPage="15"
        sortedBy="title"
        columnDefs="[
        [key:'title', label:'Title'],
        [key:'address', label: 'Address'],
        [key:'city', label: 'City'],
        [key:'state', label: 'State']
        ]"
        />
 
    </div>
 
    <script>
      YAHOO.util.Event.onDOMReady(function () {
 
        updateFilter  = function () {
          GRAILSUI.myDataTable.customQueryString =
            "title="+YAHOO.util.Dom.get("fltr[title]").value+"&"+
            "city="+YAHOO.util.Dom.get("fltr[city]").value;
 
          var state = GRAILSUI.myDataTable.getState();
          // gui:dataTable odwołuje się do własności sorting obiektu state,
          // podczas gdy yui oferuje jedynie własność sortedBy (czyżby bug?)
          // kopiujemy własność, by uniknąć błędu
          state.sorting = state.sortedBy;
          // reset pagera, zawsze po filtrowaniu wracamy do pierwszej strony
          state.pagination.recordOffset = 0;
          query = GRAILSUI.myDataTable.buildQueryString(state);
 
          GRAILSUI.myDataTable.getDataSource().sendRequest(query,{
            success : GRAILSUI.myDataTable.onDataReturnReplaceRows,
            failure : GRAILSUI.myDataTable.onDataReturnReplaceRows,
            scope   : GRAILSUI.myDataTable,
            argument: state
          });
        };
 
        YAHOO.util.Event.on('filterButton','click',function (e) {
          updateFilter();
        });
      });
    </script>
 
  </body>
</html>

Na poczÄ…tku strony, deklarujemy użycie wtyczki YUI! oraz komponentu dataTable – w ten sposób Grails zadba o to, by nasza strona zostaÅ‚a wyposażona w linki do odpowiednich skryptów i arkuszy stylów. W znaczniku używam opcjonalnego atrybutu id, jest to spowodowane faktem, że bÄ™dÄ™ siÄ™ odwoÅ‚ywaÅ‚ do naszej tabeli i muszÄ™ wiedzieć jak. Gdybym nie użyÅ‚ id, dostaÅ‚bym tabelÄ™ z unikalnym, losowym identyfikatorem. Nasza tabela wywoÅ‚uje asynchronicznie metodÄ™ listAsJSON kontrolera RestaurantController (o niej za chwilÄ™). Ważne jest, żeby w zdarzeniu onDOMReady powiÄ…zać klikniÄ™cie w przycisk Filter z funkcjÄ… updateFilter(), w której konstruujemy parametry żądania i wstawiamy je do wÅ‚asnoÅ›ci customQueryString naszej tabeli. NastÄ™pnie obchodzimy jeden bug(?) gui:dataTable, resetujemy pager (na wypadek, gdybyÅ›my zaÅ‚adowali wynik z mniejszÄ… iloÅ›ciÄ… podstron niż obecna) i przeÅ‚adowujemy naszÄ… tabelÄ™.

Gotowe :)

Na koniec tylko metoda listAsJSON naszego kontrolera:

def listAsJSON = {
 
        def pagingConfig = [
            max: params.max ?: 15,
            offset: params.offset ?: 0,
            sort: params.sort ?: 'title',
            order: params.order ?: 'asc'
        ]
 
        def resultFilter = {
            or {
                if(params.title && params.title != ''){
                    like("title", "${params.title}%")
                }
                if(params.city && params.city != ''){
                    like("city", "${params.city}%")
                }
            }
        }
 
        def c = Restaurant.createCriteria()
        def list = c.list(pagingConfig, resultFilter)
        def totalRec = list.totalCount
 
        response.setHeader("Cache-Control", "no-store")
        def data = [
            totalRecords: totalRec,
            results: list
        ]
        render data as JSON
    }

Korzystamy tu z dobrodziejstw dwóch ciekawych możliwości oferowanych przez Grails. Pierwszą z nich są Criteria, pozwalające na budowanie filtrów naszego zapytania (za pomocą domknięć mechanizm ten staje się jeszcze atrakcyjniejszy niż jego pierwowzór pochodzący z Hibernate). Każda klasa dziedzinowa posiada możliwość stworzenia obiektu klasy Criteria za pomocą metody createCriteria(). Drugą ciekawostką jest konwerter JSON, ładowany za pomocą importu:

import grails.converters.JSON

Konwerter ten, w locie, zamieni naszą mapę wyników na format JSON, zrozumiały dla naszej tabeli.

d22

Gotowe. Miłego filtrowania :)

Tagi: , , ,

Komentarze (2)

Grails i kompozytowe klucze w Hibernate

Grails to bardzo ciekawy framework przygotowany dla jÄ™zyka Groovy. Od razu zdobyÅ‚ mojÄ… sympatiÄ™ za to, że – nie tylko nazwÄ… – przypomina architekturÄ™ Ruby on Rails, czy też Symfony dla PHP, realizujÄ…c najważniejsze polityki nowoczesnych frameworków: Convention Over Configuration i Do Not Repeat Yourself. Zawsze ubolewaÅ‚em (i w sumie nadal ubolewam) nad brakiem takiego rozwiÄ…zania dla ‘czystej’ Javy (może Ty znasz jakiÅ› przykÅ‚ad?), dlatego tym chÄ™tniej zainteresowaÅ‚em siÄ™ tajnikami Grails.

Przyznam, że raczej sceptycznie reagujÄ™ na różne ‘liberalne’ jÄ™zyki programowania, np. fakt, że jakÄ…Å› operacjÄ™ można wykonać używajÄ…c różnych konstrukcji jÄ™zyka (bo wg mnie zaciemnia to nieco obraz samego kodu i utrudnia jego pielÄ™gnacjÄ™), albo dynamiczcne typowanie (zawsze przeklinaÅ‚em PHP za tÄ™ ‘przypadÅ‚ość’), jednak daÅ‚em Groovy’emu szansÄ™, za jego głównÄ… zaletÄ™: peÅ‚nÄ… kompatybilność z JavÄ… – Groovy jest de facto wykonywany przez maszynÄ™ wirtualnÄ… Javy, wiÄ™c kompatybilność ta jest oczywista.

Umówmy siÄ™ – należę do tych bardziej konserwatywnych programistów wierzÄ…cych w ‘twardÄ…’ skÅ‚adniÄ™, a w Groovy nie umiem (jeszcze) nic poza “Hello World”, toteż bardzo zależaÅ‚o mi na frameworku, w którym bÄ™dÄ™ mógÅ‚ wykorzystać już istniejÄ…ce ziarenka Javy.  Samego Groovy’ego też chÄ™tnie zgłębiÄ™, ale najpierw chcÄ™ zobaczyć co mogÄ™ zrobić z Grailsami wykorzystujÄ…c istniejÄ…cy już kod Javy.

Grails – jak RoR, czy Symfony – posiada wÅ‚asny mechanizm mapowania obiektowo-relacyjnego (gdzieÅ› tam głęboko napÄ™dzany przez Hibernate), zwany GORM, pozwalajÄ…cy na mapowanie klas Groovy’ego z bazÄ… danych. Ja jednak, przez zaÅ‚ożenie, chcÄ™ mapować ziarna encji Hibernate napisane w Javie. Grails pozwala na to bez problemów, kilka prostych czynnoÅ›ci i już możemy wykorzystać przygotowane wczeÅ›niej encje w Grails.

No, może nie całkiem bez problemów.

Aby zmapować klasy encji wystarczy, zgodnie z opisem na stronie Grails, wyedytować plik DataSource.groovy, dodać mapowanie w hibernate.cfg.xml i wreszcie skopiować swoje klasy javy do katalogu src/java. Po wykonaniu tych czynności możemy już wygenerować na podstawie encji nowy kontroler i widok, wklepując w konsoli:

grails generate-all pelna.nazwa.naszej.Klasy

Grails wygeneruje dla nas wszystkie niezbÄ™dne klasy kontrolerów (w jÄ™zyku Groovy, rzecz jasna), elementy widoku (strony GSP), sÅ‚owem mamy już caÅ‚y CRUD. Problem jednak pojawia siÄ™ w przypadku, gdy nasza encja wyposażona jest w klucz zÅ‚ożony, a schemat naszej bazy danych – jak w moim przypadku –  nie może zostać zmodyfikowany, bo jest wykorzystywany przez inne programy.

Grails teoretycznie pozwoli na mapowanie takich encji, wygeneruje też dla nich kontrolery i pliki widoków, jednak nie bedą one działały. Rozważmy poniższy przypadek.

Mamy encjÄ™ Produkt (mapowanÄ…  na tabelÄ™ “PRODUKTY”), encjÄ™ Cennik (mapowanÄ… na tabelÄ™ “CENNIKI”) i encjÄ™ Cena (tabela “CENY”) . NetBeans, na podstawie istniejÄ…cego schematu bazy danych, wygenerowaÅ‚ mi takie klasy (po drobnej korekcie nazw wÅ‚asnoÅ›ci):

package net.schowek.grailsapp;
 
// importy
 
@Entity
@Table(name = "PRODUKTY")
@NamedQueries({@NamedQuery(name = "Produkt.findAll", query = "SELECT p FROM Produkt p")})
public class Produkt implements Serializable {
    private static final long serialVersionUID = 1L;
 
    @Id
    @Basic(optional = false)
    @Column(name = "pr_id")
    private Integer id;
 
    @Basic(optional = false)
    @Column(name = "pr_nazwa")
    private String nazwa;
 
    @Column(name = "pr_opis")
    private String opis;
 
    public Produkt() {
    }
 
    public Produkt(Integer prId) {
        this.id = prId;
    }
 
    // gettery i settery
 
    @Override
    public String toString() {
        return getNazwa();
    }
}
package net.schowek.grailsapp;
 
// importy
 
@Entity
@Table(name = "CENNIKI")
@NamedQueries({@NamedQuery(name = "Cennik.findAll", query = "SELECT c FROM Cennik c")})
public class Cennik implements Serializable {
    private static final long serialVersionUID = 1L;
 
    @Id
    @Basic(optional = false)
    @Column(name = "cn_id")
    private Integer id;
 
    @Column(name = "cn_nazwa")
    private String nazwa;
 
    public Cennik() {
    }
 
    public Cennik(Integer cnId) {
        this.id = cnId;
    }
 
    // gettery i settery
 
    @Override
    public String toString() {
        return getNazwa();
    }
}
package net.schowek.grailsapp;
 
// importy
 
@Entity
@Table(name = "CENY")
@NamedQueries({@NamedQuery(name = "Cena.findAll", query = "SELECT c FROM Cena c")})
public class Cena implements Serializable {
    private static final long serialVersionUID = 1L;
 
    @EmbeddedId
    private CenaPK id;
 
    @JoinColumn( name = "ce_pr_id", referencedColumnName = "pr_id", insertable = false, updatable = false  )
    @ManyToOne
    private Produkt produkt;
 
    @JoinColumn( name = "ce_cn_id", referencedColumnName = "cn_id", insertable = false, updatable = false  )
    @ManyToOne
    private Cennik cennik;
 
    @Column(name = "ce_cena")
    private BigDecimal cena;
 
    public Cena() {
    }
 
    public Cena(CenaPK cenaPK) {
        this.id = cenaPK;
    }
 
    // gettery i settery
 
    @Override
    public String toString() {
        return getId().toString();
    }
}

I wreszcie klasa określająca nasz klucz złożony:

package net.schowek.grailsapp;
 
// importy
 
@Embeddable
public class CenaPK implements Serializable {
 
    @Basic(optional = false)
    @Column(name = "ce_pr_id")
    private int cePrId;
 
    @Basic(optional = false)
    @Column(name = "ce_cn_id")
    private int ceCnId;
 
    public CenaPK() {
    }
 
    public CenaPK(int cePrId, int ceCnId) {
        this.cePrId = cePrId;
        this.ceCnId = ceCnId;
    }
 
    public int getCePrId() {
        return cePrId;
    }
 
    public void setCePrId(int cePrId) {
        this.cePrId = cePrId;
    }
 
    public int getCeCnId() {
        return ceCnId;
    }
 
    public void setCeCnId(int ceCnId) {
        this.ceCnId = ceCnId;
    }
 
    @Override
    public String toString() {
        return "net.schowek.grailsapp.CenaPK[cePrId=" + cePrId + ", ceCnId=" + ceCnId + "]";
    }
 
}

Tak przygotowanesą podstawą wygenerowanej aplikacji.  Po uruchomieniu aplikacji za pomocą komendy grails run-app mogę już eksplorować zasoby cen i produktów. Problem jednak zaczyna się, gdy chcę przejrzeć listę cen. Już na liście (http://localhost:8080/GrailsApp/cena/list) widać, że odnośniki do rekordów cen nie przypominają standardowych odnośników do rekordów tabel o prostych kluczach. Przykładowy link do ceny wyglądał tak:

http://localhost:8080/GrailsAppl/cena/show/net.schowek.grailsapp.CenaPK%5BcePrId%3D63%2C+ceCnId%3D2%5D

a strona, którą można pod nim zobaczyć wygląda tak:

err
Jak widać, Grails w miejsce id, podstawia wartość obiektu klasy CenaPK, a ten reprezentowany jest przez swojÄ… metodÄ™ toString().  Po odwoÅ‚aniu siÄ™ do serwera i zapytaniu o cenÄ™ o podanym kluczu, serwer – co oczywiste – czÄ™stuje nas wyjÄ…tkiem.

Oto prosty sposób, w jaki można temu zaradzić (zakładając, że klucz złożony składa się z pól typu numerycznego).

Przede wszystkim, zmieniłem kod klasy CenaPK na taki (zmieniłem toString() i dodałem jeden konstruktor):

    public static final String KEY_SEPARATOR="_";
 
    @Override
    public String toString() {
        return cePrId+KEY_SEPARATOR+ceCnId;
    }
 
    public CenaPK(String k) {
        String[] keys = k.split( KEY_SEPARATOR );
        this.cePrId = Integer.parseInt(keys[0]);
        this.ceCnId = Integer.parseInt(keys[1]);
    }

Jak widzisz, połączyłem w jeden string dwa klucze oddzielone od siebie separatorem. Oznacza to, że ponowne wygenerowanie widoków komendą:

grails generate-views net.schowek.grailsapp.Cena

poskutkuje nową listą cen, zawierającą odnośniki do rekordów podobne do tych:

http://localhost:8080/GrailsAppl/cena/show/63_2

Aby wszystko grało, musiałem jeszcze przerobić kontroler cen (plik CenaController.groovy w katalogu controllers/) i zmienić wszystkie linie ładujące pojedynczy rekord ceny z

def cenaInstance = Cena.get( params.id )

na

def cenaInstance = Cena.get( new CenaPK(params.id) )

Od tej pory mogłem się już cieszyć działającą aplikacją obsługującą klucze złożone.

Aha, separatorem klucza może być w zasadzie dowolny znak, jednak sÄ… tu wyjÄ…tki – np. nie zadziaÅ‚a na pewno slash, ponieważ jest on interpretowany przez UrlMappera grails. Warto zatem sprawdzić co zadziaÅ‚a, a co nie.

Tagi: , , ,

Skomentuj

Zen Cart w zgodzie z WAMP

Ostatnio miałem okazję wykonać kilka modyfikacji dla platformy sklepowej Zen Cart zainstalowanej na serwerze mojego klienta. Zen Cart to platforma sklepowa oparta o darmowy osCommerce. Istnieje dla niego dość sporo rozszerzeń i wtyczek, ale moje zlecenie było tak specyficzne, że niestety nie obyło się bez potrzeby modyfikacji kodu sklepu (czego, mówiąc szczerze, bardzo starałem się uniknąć).

Wersja produkcyjna sklepu dziaÅ‚a pod kontrolÄ… systemu UNIX‘owego, a specyfika zlecenia polegaÅ‚a na tym, że musiaÅ‚em dokonać kilku poważnych poprawek – co najważniejsze – dziaÅ‚ajÄ…c na różnych platformach (czasem musiaÅ‚em pracować na domowym Linuksie, czasem na Windows).

Przygotowanie Å›rodowiska roboczego pod Linuksem zajęło mi naprawdÄ™ niewiele czasu; szybko napisaÅ‚em skrypty automatycznie pobierajÄ…ce bazÄ™ danych i synchronizujÄ…ce pliki kodu z wersjÄ… produkcyjnÄ…. Gorzej, gdy zaczÄ…Å‚em potrzebować możliwoÅ›ci modyfikacji kodu sklepu spod Windows. MuszÄ™ przyznać, że byÅ‚ to mój debiut w pisaniu czegokolwiek w php na tym systemie, ale w koÅ„cu postanowiÅ‚em dać mu szansÄ™. Jako Å›rodowiska programistycznego użyÅ‚em Aptana Studio (choć mam wrażenie, że wystarczyÅ‚by sam Eclipse z PDT), a caÅ‚ość zamierzaÅ‚em postawić na – znanym mi ze sÅ‚yszenia – WAMPie. Instalacja WAMP odbyÅ‚a siÄ™ bardzo sprawnie, pierwsze Hello World – również bez niespodzianek. Problem jednak zaczÄ…Å‚ siÄ™ po instalacji – a jakże – Zen Carta.

Pobrałem zrzut bazy danych i pliki z kodem sklepu i zainstalowałem je w WAMP wykorzystując do tego możliwość tworzenia aliasów.  Okazało się jednak, że instalacja ta nie działa, WAMP serwuje mi tylko komunikaty błędów, albo jakieś krzaki zamiast polskich znaków. Oto lista czynności, które musiałem wykonać, aby zacząć normalnie pracować nad zmianami dla klienta:

Problem 1Apache częstuje mnie błędem 500 (Internal Server Error) przy jakimkolwiek odwołaniu.

Problem ten spowodowany był wyłączoną domyślnie obsługą modułu rewrite przez WAMPowego Apacza. Pomogła zwykła edycja httpd.conf i odkomentowanie linijki:

LoadModule rewrite_module modules/mod_rewrite.so

(oczywiÅ›cie po tym procesie należy wyedytować odpowiednio plik .htacces – zgodnie z potrzebami i Å›cieżkami)

Problem 2 – Kodowanie polskich znaków bazy danych

Zen Cart niespecjalnie zwraca uwagÄ™ na różne możliwoÅ›ci kodowania znaków diakrytycznych bazy danych – na serwerze produkcyjnym byÅ‚a jakaÅ› domyÅ›lna konfiguracja i takÄ… też wgraÅ‚em u siebie. Niestety, okazaÅ‚o siÄ™, że WAMPowe domyÅ›lne ustawienie kodowania UTF8 pozwala na wczytanie bazy bez problemów (wpisy sÄ… dobrze widoczne z poziomu PhpMyAdmina), jednak sam sklep wyÅ›wietla znaki zapytania zamiast polskich literek.  Tu nie obyÅ‚o siÄ™ bez modyfikacji kodu Zen Carta i tak  w pliku /includes/init_includes/init_database.php dodaÅ‚em na koÅ„cu pliku linijki:

if( HTTP_SERVER == 'http://localhost' )
    $db-&gt;Execute("SET NAMES 'latin2'  COLLATE 'latin2_general_ci'");

i wreszcie zobaczyÅ‚em polskie znaki na moim localhost. Powyższy ifcheck wstawiÅ‚em po to, by system dziaÅ‚aÅ‚ z innymi znakami tylko na konfiguracji roboczej – w wersji produkcyjnej niech zostanie bez zmian. Później i tak usunÄ™ te linijki, toteż opatrzyÅ‚em je Å‚atwymi do odnalezienia komentarzami. Dodam, że Zen Cart jest tak napisany, że połączenie z bazÄ… danych odbywa siÄ™ w jeszcze kilku innych miejscach,  należy wiÄ™c mieć na wzglÄ™dzie potencjalnÄ… potrzebÄ™ dodania powyższych linijek do innych plików, zawierajÄ…cych linijkÄ™:

$db-&gt;connect( DB_SERVER, DB_SERVER_USERNAME, DB_SERVER_PASSWORD, DB_DATABASE, USE_PCONNECT, false );

Problem 3 – Puste listy produktów

Kiedy już zażegnaÅ‚em problemy ‘techniczne’, okazaÅ‚o siÄ™, że sklep dziaÅ‚a, ale listy produktów sÄ… w nim puste. Nie dostawaÅ‚em komunikatów błędów, ale mimo to strona główna zawieraÅ‚a jedynie boczne sideboksy, a w Å›rodku nic. OkazaÅ‚o siÄ™, że problemem tutaj byÅ‚ fakt, że domyÅ›lna konfiguracja php w WAMP ma wyłączonÄ… opcjÄ™ Short Tags, co powoduje, że wszelkie template’y zawierajÄ…ce znacznik otwarcia kodu php <? zamiast <?php nie byÅ‚y interpretowane przez parser php. Włączenie powyższej opcji w pliku php.ini spowodowaÅ‚o prawidÅ‚owe wyÅ›wietlanie produktów.

Po tych poprawkach i restarcie wszystkich usÅ‚ug WAMPa mogÅ‚em wreszcie zacząć prace nad sklepem. Wydaje mi siÄ™ jednak, że – gdybym chciaÅ‚ oprzeć na takiej konfiguracji wersjÄ™ produkcyjÄ… sklepu – musiaÅ‚bym siÄ™ jeszcze sporo natrudzić, by uznać to rozwiÄ…zanie za stabilne.

Tagi: , , , ,

Skomentuj

Hojna Gwiazdka

Tegoroczna Gwiazdka była dla mnie, jako programisty, niesamowicie obfita. Pod choinką znalazłem stos świetnych pozycji dotyczących ogólnie Javy, ale i jej poszczególnych technologii. I tak, dostało mi się:

  • JUnit – Pragmatyczne testy jednostkowe w Javie
  • JAVA – Tworzenie aplikacji sieciowych za pomocÄ… Springa, Hibernate i Eclipse
  • Eclipse Web Tools Platform – Tworzenie aplikacji WWW w jÄ™zyku Java
  • J2EE – Wzorce projektowe
  • Enterprise Java Beans 3.0 (którÄ… dostaÅ‚em nieco wczeÅ›niej, ale jest mi szczególnie bliska ze wzglÄ™du na ostatnie tÄ… platformÄ… zainteresowanie)

Z takim arsenaÅ‚em lektur mam teraz zajęć na zimowe wieczory pod dostatkiem.  W tej jednak chwili prawdziwym dylematem bÄ™dzie dla mnie pytanie “Od czego zacząć?” gdy zasiÄ…dÄ™ na kanapie z filiżankÄ… kawy ;)

MuszÄ™ siÄ™ zwierzyć, że książki papierowe to mój ulubiony sposób zdobywania wiedzy w zakresie programowania, ale też niestety najbardziej zaniedbany. W epoce Google’a trudno jest ‘marnować’ czas na wyszukiwanie odpowiednich pozycji książkowych, podczas gdy Å‚atwo sobie coÅ› znaleźć w Sieci ‘na szybko’. Ja także staÅ‚em siÄ™ ofiarÄ… czegoÅ› w rodzaju fast-foodu w dziedzinie zdobywania wiedzy (szybkie wyszukiwanie, krótka lektura wiki, krótka lektura tutoriala, krótkie ‘Hello World’ i już ‘znam’ techonlogiÄ™) i trochÄ™ tego żaÅ‚ujÄ™. Mam teraz idealnÄ… okazjÄ™, by nadrobić zalegÅ‚oÅ›ci, pozagłębiać siÄ™ trochÄ™ w szczegóły i – wreszcie – poznać polskie nazwy niektórych terminów, które stosujÄ™ w praktyce od dÅ‚uższego czasu (kolejna wada ‘fast-foodów wiedzy’ to taka, że wiÄ™kszość pozyskiwanych informacji przychodzi w jÄ™zyku angielskim i nie zna siÄ™ jej oficjalnych tÅ‚umaczeÅ„).

Dodatkową niespodzianką, jaka zawitała pod Świąteczne drzewko, była koszulka z bardzo fajnie wykonanym napisikiem (za którą dziękuję Adze i Zbigowi) ;)

Prezenty pod choinkÄ…

Wesołych Świąt!

Tagi: , , , , , , , , ,

Komentarze (6)