Tag Archives: oop

webmastering

Weź mi zrób procesor tekstu cz. 2 / 2

Zgod­nie z zapo­wie­dzią, dzisiaj będziemy imple­men­to­wać moduły, które coś z poda­nym tekstem robią. Do tego celu głów­nie używać będziemy wyra­żeń regularnych.

1. Na pierw­szy ogień pójdzie popra­wiacz spacji. Jak często trafiają się wam doku­menty forma­to­wane za pomocą 20 spacji? Za pomocą tego cuda wywa­limy wszystko co ma powy­żej dwóch za koleją.

class SpaceFixer implements TextJob {
   public function process($string) {
      $string = trim($string);
      $string = preg_replace('/\s{2,}/' , ' ' , $string);
      return $string;
   }
}

Zasada dzia­ła­nia prosta: najpierw wyrzuć wszel­kie spacje przed i za strin­giem, a następ­nie poszu­kaj dwie lub więcej spacji za koleją i zamień je na pojedyncze.

2. Teraz coś sporo trud­niej­szego: doda­wacz twar­dych spacji do spój­ni­ków. Zasada dzia­ła­nia taka: znajdź mi spację, poje­dyn­czy znak, znowu spację i jeden lub więcej znaków (np. jedze­nie_i_picie).

class AddNbsps implements TextJob {
   public function process($string) {
      $output = ''; // inicjalizujemy pustą zmienną, zostało mi to po Javie
      foreach ($this->splitStringsByTagChar($string) as $split) {
         $output .= substr($split , 0 , 1) == '<' ? $split : $this->fixLine($split);
      }
      return $output;
   }
   private function splitStringsByTagChar($string) {
      return preg_split('/(<.+?>)/', $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
   }
   private function fixLine($line) {
      return preg_replace('/(\s{1}[-\("\w]{1})\s{1}([-\)"\w]+)/' , '$1&nbsp;$2' , $line);
   }
}

Jak widać trochę się pokom­pli­ko­wało. Po co nam te dodat­kowe funk­cje? Ano po to żeby nie popsuły się nasze HTMLowe tagi. Funk­cja splitStringsByTagChar nieza­chłan­nie szuka czegoś co przy­po­mina sekwen­cję <coś> i szat­kuje nam ciąg do tablicy.

Opera­tor trój­ar­gu­men­towy po fore­ach spraw­dza czy element tablicy zaczyna się od znaku < . Jeżeli tak, to zosta­wia go w spokoju, a jak nie, to kieruje do prze­róbki do fixLine. Można zauwa­żyć, że oprócz klasy znaków \w (alfa­nu­me­ryczne i _) doda­łem również ” ( ). Po prze­ana­li­zo­wa­niu kilku tekstów stwier­dzi­łem, że tak lepiej działa (a mogłoby pewnie lepiej gdybym poświę­cił na to jesz­cze trochę czasu).

3. Wyszu­ki­wacz linków.

class AddLinks implements TextJob {
   public function process($string) {
      return preg_replace('`(?<!href="|src=")((https?://|www\.)[\w{1,}./?=#&]+)`i' , '<a href="$1">$1</a>' , $string);
   }
}

Tutaj można zauwa­żyć dziwną konstruk­cję (?<! Jest to aser­cja wsteczna. Działa tak: znajdź mi ciąg znaków zaczy­na­jący się od http:// lub www., a jak znaj­dziesz, to sprawdź czy czasami przed znale­zio­nym ciągiem nie ma konstruk­cji z aser­cji. Po co to? Do wykry­cia, czy link znaj­duje się już w tagu <a href=” lub <img src=”, a więc czy nie popsu­jemy kodu html. Do tego mógł­bym równie dobrze użyć funk­cji szat­ku­ją­cej z poprzed­niego modułu, ale chcia­łem żeby było trudniej :-)

Jak tego wszyst­kiego używać? Dosyć prosto: w atry­bu­tach poda­jemy moduły, które nas inte­re­sują i poda­jemy tekst:

$jakisTekst = 'Ala ma kota, a kot ma HIV';
$txtProcesser = TextProcesser::getInstance(array('SpaceFixer','AddNbsps','AddLinks'));
$processedTxt = $txtProcesser->processJobs($jakisTekst);

Tekst będzie prze­twa­rzany w poda­nej kolej­no­ści. Fajne, co?
[czeka na oklaski]

webmastering

Weź mi zrób procesor tekstu cz. 1 / 2

Dzisiaj będzie wpis o ułatwia­niu życia użyt­kow­ni­kom paneli admi­ni­stra­cyj­nych. Chodzi tutaj o sytu­ację, gdy użyt­kow­nik naszej strony wkleja byle jak jakiś tekst z Worda do TinyMCE, a następ­nie dziwi się, że na stro­nie wycho­dzi kaszana.

Parę rzeczy da się popra­wić z auto­matu poprzez prze­two­rze­nie przez proce­sor tekstu. Skąd taki wziąć? A napi­szemy sobie sami.

W tym wpisie stwo­rzymy „rusz­to­wa­nie”, a w następ­nej części parę modu­łów robią­cych jakieś konkretne rzeczy.

Pierw­sze czego potrze­bu­jemy to inter­fejs poje­dyn­czego modułu prze­twa­rza­ją­cego tekst. Będzie banal­nie prosty: metoda process pobiera stringa i zwraca prze­two­rzo­nego stringa.

interface TextJob {
   public function process($string);
}

Dalej piszemy klasę proce­sora tekstu.

class TextProcesser {
   private $jobsToDo = array();
   private function __construct() {}
   public static function getInstance($params = array()) {
      $textProcesser = new TextProcesser();
      foreach ($params as $param) {
         try {
            if (!class_exists($param)) throw new Exception('Klasa ' . $param . ' nie istnieje!');
            $param = new $param;
            if (!($param instanceof TextJob)) throw new Exception('Klasa ' . $param . ' nie obsluguje interfejsu TextJob!');
         } catch (Exception $e) {
            echo $e->getMessage();
            die();
         }
         $textProcesser->addJob($param);
      }
      return $textProcesser;
   }
 
   private function addJob(TextJob $tj) {
      $this->jobsToDo[] = $tj;
   }
 
   public function processJobs($string) {
      foreach ($this->jobsToDo as $job) {
         $string = $job->process($string);
      }
      return $string;
   }
}

Po kolei:

  1. Zmienna $jobsToDo trzyma tablicę prac do wykonania.
  2. Klasy nie będziemy tworzyć poprzez konstruk­tor, dlatego został zmie­niony na prywatny.
  3. Statyczna metoda getInstance tworzy obiekt proce­sora i przy okazji spraw­dza czy podana tablica $params zawiera istnie­jące klasy i czy imple­men­tują one inter­fejs TextJob.
  4. Metoda addJob dodaje kolejną pracę do wyko­na­nia do kolejki.
  5. processJobs wyko­nuje właściwą robotę — pobiera stringa do prze­two­rze­nia i w kolej­no­ści doda­nia prac prze­twa­rza go.

Co mamy do tej pory? Klasę, do której możemy doda­wać tyle konkret­nych zadań ile chcemy, o ile tylko imple­men­tują inter­fejs TextJob. W następ­nej części będziemy wyrzu­cać zbędne spacje, doda­wać twarde spacje i szukać linków.

webmastering

3 wzorce projektowe w ok. 100 liniach kodu PHP

Ostat­nio sporo czytam na temat progra­mo­wa­nia obiek­to­wego. W niemal każdej książce, która wpadła mi w łapki, przy­kłady wzor­ców projek­to­wych podane są w języku Java. Wyjąt­kiem jest Progra­mo­wa­nie obiek­towe w PHP 5 Hasina Haydera.

Do napi­sa­nia postu skło­niły mnie poja­wia­jące się gdzie­nie­gdzie narze­ka­nia progra­mi­stów na PHP, że jakoby ten język to zabawka, że nie da się w nim tworzyć w pełni obiek­to­wego kodu itd. Dzisiaj zade­mon­struję dzia­ła­nie 3 wzor­ców projek­to­wych na raz (Stra­te­gia, Obser­wa­tor, Łańcuch zobowiązań).

Chcemy zrobić coś takiego:

Mamy więc jeden obiekt, który zawiera w sobie dane na temat obiek­tów, które chce powia­da­miać, że coś się u niego zmie­niło. Tak działa typowy wzorzec obser­wa­tor. Ja to jednak zmody­fi­ko­wa­łem tak, że pierw­szy obser­wa­tor w łańcu­chu otrzy­muje infor­ma­cję o zmia­nie stanu obiektu obser­wo­wa­nego. Jeżeli umie sobie pora­dzić z prze­ka­za­nym komu­ni­ka­tem — reaguje. Jeżeli ta infor­ma­cja go zbyt­nio nie inte­re­suje — prze­ka­zuje następ­nemu obiek­towi w łańcu­chu. Tak działa wzorzec łańcuch zobo­wią­zań. Po co mi wzorzec stra­te­gia? Z czystego leni­stwa :-) Po co pisać kilka rodza­jów obser­wa­to­rów do testu, które reagują w inny sposób na komu­ni­katy, skoro można imple­men­to­wać inter­fejs reagu­jący na różne komu­ni­katy. Jeżeli będę chciał testo­wać inny rodzaj komu­ni­ka­tów to napi­szę sobię nową imple­men­ta­cję inter­fejsu reagu­ją­cego i tyle :-)

Bierzemy się więc do roboty. Najpierw tworzymy sobie 2 inter­fejsy. 1 dla wzorca obser­wa­tor, drugi dla łańcu­cha zobowiązań.

interface Obserwujacy {
    public function powiadomienie($komunikat);
}
 
interface Lancuch {
    public function zareaguj($komunikat);
}

Następ­nie tworzymy sobie jakiś obiekt obserwowany:

class Obserwowany {
 
    private $obserwatorzy = array();
    private $komunikat;
 
    public function addObserwujacy(Obserwujacy $o) {
        $this->obserwatorzy[] = $o;
    }
 
    public function setKomunikat($k) {
        $this->komunikat = $k;
        $this->powiadamiaj();
    }
 
    private function powiadamiaj() {
        foreach ($this->obserwatorzy as $obserwator) {
            $obserwator->powiadomienie($this->komunikat);
        }
    }
}

Obiekt ma zale­d­wie 3 metody. Pierw­sza dodaje kolej­nego obser­wa­tora do listy, druga zmie­nia stan obiektu (wysyła komu­ni­kat wszyst­kim), trze­cia dla każdego obser­wa­tora wywo­łuje metodę powia­da­mia­jącą o zmia­nie stanu.

Następ­nie tworzymy klasę Obser­wa­tor, która reaguje na wszyst­kie komunikaty.

class Obserwator implements Obserwujacy , Lancuch {
 
    protected $reagujNa; // implements ReagujNa
    protected $nazwa;
    protected $lancuch;
 
    public function __construct($nazwa, Lancuch $l) {
        $this->reagujNa = new ReagujNaWszystko();
        $this->nazwa = $nazwa;
        $this->lancuch = $l;
    }
 
    public function powiadomienie($komunikat) {
        $this->zareaguj($komunikat);
    }
 
    public function zareaguj($komunikat) {
        if ($this->reagujNa->reaguj($komunikat)) {
            echo '<strong>' . $this->nazwa . '</strong> otrzymal komunikat ' . $komunikat . ' i zareagowal.<br />';
        }
        else {
            $this->lancuch->zareaguj($komunikat);
        }
    }
}

Zmienna $reagujNa imple­men­tuje inter­fejs ReagujNa, który stwo­rzymy poni­żej. Niestety w PHP nie da się wymu­sić okre­ślo­nego typu zmien­nej — w tym lepsza jest Java. Jeżeli ktoś koniecz­nie chce, to może sobie spraw­dzać czy dana zmienna rzeczy­wi­ście imple­men­tuje inter­fejs przed wywołaniem.

interface ReagujNa {
    public function reaguj($komunikat);
}
 
class ReagujNaWszystko implements ReagujNa {
    public function reaguj($komunikat) {
            return true;
    }
}
 
class ReagujNaLiterki implements ReagujNa {
    public function reaguj($komunikat) {
        if (is_string($komunikat)) {
            return true;
        }
        else {
            return false;
        }
    }
}
 
class ReagujNaNumerki implements ReagujNa {
    public function reaguj($komunikat) {
        if (is_numeric($komunikat)) {
            return true;
        }
        else {
            return false;
        }
    }
}

Mamy tutaj trzy klasy posia­da­jące po jednej meto­dzie, które zwra­cają prawdę lub fałsz w zależ­no­ści od poda­nego para­me­tru. Dalej wypa­da­łoby wyko­rzy­stać te klasy zmie­nia­jąc zacho­wa­nie obiektu Obserwator.

class ObserwatorLiterek extends Obserwator {
    public function __construct($nazwa, Lancuch $l) {
        parent::__construct($nazwa,$l);
        $this->reagujNa = new ReagujNaLiterki();
    }
}
 
class ObserwatorNumerkow extends Obserwator {
    public function __construct($nazwa, Lancuch $l) {
        parent::__construct($nazwa,$l);
        $this->reagujNa = new ReagujNaNumerki();
    }
}

Na koniec potrzebne jest nam jesz­cze ostat­nie ogniwo łańcu­cha, nazwane przeze mnie Limi­ter. Cokol­wiek limi­ter dostaje — wyrzuca na wyjściu i tyle.

class Limiter implements Lancuch {
 
    private $nazwa;
 
    public function __construct ($nazwa) {
        $this->nazwa = $nazwa;
    }
    public function zareaguj($komunikat) {
        echo '<strong>Limiter ' . $this->nazwa . '</strong> otrzymal komunikat ' . $komunikat . '<br />';
    }
}

Pora to wszystko prze­te­sto­wać. Tworzymy obiekt obserwowany,

$obserwowany = new Obserwowany();

a następ­nie 4 ciągi obser­wa­to­rów. Tworzymy go od końca. Coś jak Matrioszki.

// ciag 1
$limiter1 = new Limiter('ciagu 1');
$obserwatorWszystkiego = new Obserwator('Obserwator Wszystkiego ciagu 1',$limiter1);
$obserwowany->addObserwujacy($obserwatorWszystkiego);
// ciag 2
$limiter2 = new Limiter('ciagu 2');
$obserwatorLiterek = new ObserwatorLiterek('Obserwator Literek ciagu 2',$limiter2);
$obserwowany->addObserwujacy($obserwatorLiterek);
// ciag 3
$limiter3 = new Limiter('ciagu 3');
$obserwatorNumerkow = new ObserwatorNumerkow('Obserwator Numerkow ciagu 3',$limiter3);
$obserwowany->addObserwujacy($obserwatorNumerkow);
// ciag 4
$limiter4 = new Limiter('ciagu 4');
$obsAll = new Obserwator('Obserwator Wszystkiego ciagu 4',$limiter4);
$obsNum = new ObserwatorNumerkow('Obserwator Numerkow ciagu 4',$obsAll);
$obsLit = new ObserwatorLiterek('Obserwator Literek ciagu 4',$obsNum);
$obserwowany->addObserwujacy($obsLit);

Ostat­nim krokiem jest spraw­dze­nie jak to teraz działa.

$obserwowany->setKomunikat('abc');
echo '----------------------------------';
$obserwowany->setKomunikat(123);

Wynik:
Obserwator Wszystkiego ciagu 1 otrzymal komunikat abc i zareagowal.
Obserwator Literek ciagu 2 otrzymal komunikat abc i zareagowal.
Limiter ciagu 3 otrzymal komunikat abc
Obserwator Literek ciagu 4 otrzymal komunikat abc i zareagowal.
----------------------------------
Obserwator Wszystkiego ciagu 1 otrzymal komunikat 123 i zareagowal.
Limiter ciagu 2 otrzymal komunikat 123
Obserwator Numerkow ciagu 3 otrzymal komunikat 123 i zareagowal.
Obserwator Numerkow ciagu 4 otrzymal komunikat 123 i zareagowal.

A teraz wyja­śnie­nie jak to wszystko działa:

Ciąg 1 zawiera w sobie tylko obser­wa­tora wszyst­kiego i limi­ter. Obser­wa­tor każdy komu­ni­kat trak­tuje jako infor­ma­cję dla siebie i nie prze­ka­zuje dalej do limi­tera (przez co komu­ni­kat nigdy do niego nie dojdzie).

Ciąg 2 ma tylko obser­wa­tora lite­rek i limi­ter. Na pierw­szy, słowny komu­ni­kat zare­ago­wał. Na komu­ni­kat zawie­ra­jący liczbę nie zare­ago­wał i prze­ka­zał dalej do limitera.

Ciąg 3 ma w zamian obser­wa­tora numer­ków i limi­ter. Zacho­wał się dokład­nie odwrot­nie od poprzedniego.

Ciąg 4 ma kolejno obser­wa­to­rów lite­rek, numer­ków, wszyst­kiego i limi­ter. Teraz dokład­nie widać jak to działa. Jeżeli dany obser­wa­tor nie znalazł infor­ma­cji przy­dat­nej dla siebie — po prostu prze­ka­zał następ­nemu obser­wa­to­rowi w ciągu. Warto zauwa­żyć, że obser­wa­tor wszyst­kiego i limi­ter w tym przy­padku nigdy nie zare­aguje, gdyż komu­ni­kat zosta­nie rozwią­zany zanim do niego dojdzie.

Dla chęt­nych zamiesz­czam plik wzorce.php (skaso­wali, a kopii nie mam :-( ). Zachę­cam do ekspe­ry­men­tów i ewen­tu­al­nej prze­bu­dowy obser­wa­to­rów tak, aby reago­wały na konkretne słowa lub liczby.