Development

Documentation/pl_PL/book/1.0/17-Extending-Symfony

You must first sign up to be able to contribute.

Version 20 (modified by PiotrLegnica, 10 years ago)
--

Rozdział 17 - Rozbudowywanie Symfony

Prędzej czy później, będziesz musiał zmienić działanie symfony. Czy będziesz chciał zmienić zachowanie klasy, czy dodać nowe funkcje, ten moment na pewno nastąpi--każdy klient stawia indywidualne wymagania, których żaden framework nie da rady przewidzieć. Na szczęście ta sytuacja występuje na tyle często, że symfony udostępnia mechanizm rozbudowy klas zwany wstawkami. Możesz nawet wymienić klasy rdzenia symfony, zmieniając ustawienia fabryk. Po napisaniu rozszerzenia, możesz go spakować jako wtyczkę, aby można było użyć go w innej aplikacji, lub udostępnić innym użytkownikom symfony.

Wstawki

Pośród obecnych ograniczeń PHP, jednym z najbardziej uciążliwych jest brak możliwości dziedziczenia po więcej niż jednej klasie bazowej. Innym ograniczeniem jest brak możliwości dodawania lub nadpisywania metod już istniejącej klasy. By złagodzić te ograniczenia i uczynić framework naprawdę rozszerzalnym, symfony udostępnia klasę sfMixer. Nie jest ona nawiązaniem do gotowania, lecz do pojęcia wstawek w programowaniu zorientowanym obiektowo. Wstawka jest zbiorem metod lub funkcji, które mogą być dodane do klasy w celu jej rozszerzenia.

Wielokrotne dziedziczenie

Wielokrotne dziedziczenie jest zdolnością klasy do dziedziczenia po więcej niż jednej klasie bazowej. Spójrzmy na ten przykład. Wyobraź sobie klasy Story i Book, każda ma swoje właściwości i metody--tak jak pokazano na listingu 17-1.

Listing 17-1 - Dwie przykładowe klasy

[php]
class Story
{
  protected $title = '';
  protected $topic = '';
  protected $characters = array();

  public function __construct($title = '', $topic = '', $characters = array())
  {
    $this->title = $title;
    $this->topic = $topic;
    $this->characters = $characters;
  }

  public function getSummary()
  {
    return $this->title.', a story about '.$this->topic;
  }
}

class Book
{
  protected $isbn = 0;

  function setISBN($isbn = 0)
  {
    $this->isbn = $isbn;
  }

  public function getISBN()
  {
    return $this->isbn;
  }
}

Klasa ShortStory dziedziczy po Story, klasa ComputerBook dziedziczy po Book, więc klasa Novel powinna dziedziczyć po obu klasach Story i Book, by skorzystać z ich metod. Niestety, w PHP jest to niemożliwe. Nie możesz zadeklarować klasy Novel tak jak pokazano na listingu 17-2.

Listing 17-2 - Wielokrotne dziedziczenie jest niemożliwe w PHP

[php]
class Novel extends Story, Book
{
}

$myNovel = new Novel;
$myNovel->getISBN();

Jednym z rozwiązań mogłoby być napisanie dwóch interfejsów, które klasa Novel by implementowała, lecz wtedy nie byłoby faktycznej implementacji tych metod w klasach bazowych.

Mieszanie klas

Klasa sfMixer pochodzi do problemu inaczej, biorąc istniejącą klasę i rozszerzając ją a posteritori, jeśli klasa ta zawiera odpowiednie "punkty zaczepienia". Proces ten składa się z dwóch etapów:

  • Zadeklarowanie klasy jako rozszerzalnej
  • Zarejestrowanie rozszerzeń (lub wstawek), po deklaracji klasy Listing 17-3 pokazuje jak można zaimplementować klasę Novel za pomocą klasy sfMixer.

*Listing 17-3 - Wielokrotnie dziedziczenie jest możliwe dzięki sfMixer*

[php]
class Novel extends Story
{
  public function __call($method, $arguments)
  {
    return sfMixer::callMixins();
  }
}

sfMixer::register('Novel', array('Book', 'getISBN'));
$myNovel = new Novel();
$myNovel->getISBN();

Jedna z klas (Story) została wybrana na główną klasę nadrzędną, korzystając z wbudowanego mechanizmu dziedziczenia PHP. Klasa Novel została zadeklarowana jako rozszerzalna dzięki fragmentowi kodu umieszczonemu w metodzie __call(). Metoda z drugiej klasy (Book) jest potem dodawana do Novel przez wywołanie metody sfMixer::register(). Proces ten jest wyjaśniony w następnych sekcjach.

Gdy zostanie wywołana metoda getISBN() klasy Novel, zadziała ona jakby klasa była zadeklarowana w sposób pokazany na listingu 17-2--tylko, że to magia metody __call() i statycznych metod sfMixer, która to zachowanie symuluje. Metoda getISBN() została wmieszana w klasę Novel.

SIDEBAR Kiedy używać wstawek

Mechanizm wstawek symfony jest użyteczny w wielu wypadkach. Symulowanie wielokrotnego dziedziczenia opisane powyżej, jest tylko jednym z nich.

Możesz użyć wstawek by zmodyfikować metodę po jej zadeklarowaniu. Na przykład, przy budowaniu biblioteki graficznej, prawdopodobnie zaimplementujesz klasę Line--reprezentującą linię. Będzie ona zawierać cztery atrybuty (koordynaty obu końców) i metodę draw(), rysującą linię. ColoredLine powinna zawierać te same metody i właściwości, oraz jeden dodatkowy atrybut - kolor. Ponadto, metoda draw() klasy ColoredLine jest nieco inna od tej z Line, ponieważ używa różnych kolorów. Mógłbyś spakować zdolności graficznego elementu który używa kolorów w klasę ColoredElement. Pozwoliłoby to użyć ponownie tych metod dla innych elementów graficznych (Dot, Polygon, itd.). W tym wypadku, idealną implementacją ColoredLine byłoby rozszerzenie klasy Line i wmieszanie metod z klasy ColoredElement. Finalna metoda draw() byłaby połączeniem tej z klasy Line i tej z klasy ColoredElement.

Wstawki są też postrzegane jako sposób na dodawanie nowych metod do istniejących klas. Na przykład, klasa akcji symfony, nazwana sfActions, jest zadeklarowana wewnątrz frameworka. Jedną z wad PHP jest brak możliwości zmiany definicji klasy sfActions już po jej zadeklarowaniu. Mógłbyś chcieć dodać własną metodę do sfActions, przydatną tylko dla jednej aplikacji--na przykład, przekierowywanie żądania do specjalnej usługi sieciowej. Samo PHP nie dałoby rady, ale mechanizm wstawek daje idealne rozwiązanie.

Deklarowanie klasy jako rozszerzalnej

By zadeklarować klasę jako rozszerzalną, musisz wstawić do kodu jeden lub więcej "punktów zaczepienia", które klasa sfMixer będzie mogła później zidentyfikować. Punkty te są wywołaniami metody sfMixer::callMixins(). Wiele klas symfony je posiada, w tym sfRequest, sfResponse, sfController, sfUser, czy sfAction.

Punkt zaczepienia może być umieszczony w różnych punktach klasy, w zależności od pożądanego stopnia rozszerzalności:

  • By móc wstawiać nowe metody do klasy, musisz umieścić kod w metodzie __call() i zwracać jego wynik, jak pokazano na listingu 17-4.

Listing 17-4 - Danie klasie możliwości rozszerzenia nowymi metodami

[php]
class SomeClass
{
  public function __call($method, $arguments)
  {
    return sfMixer::callMixins();
  }
}
  • By móc modyfikować zachowanie istniejących metod, musisz wstawić kod wewnątrz tejże metody, jak pokazano na listingu 17-5. Kod dodany przez wstawki zostanie wywołany w miejscu umieszczenia punktu zaczepienia.

Listing 17-5 - Danie metodzie możliwości modyfikacji

[php]
class SomeOtherClass
{
  public function doThings()
  {
    echo "I'm working...";
    sfMixer::callMixins();
  }
}

Kod zaczepienia możesz umieścić więcej niż raz. W takim wypadku musisz nazwać poszczególne punkty, by móc potem uściślić, co konkretnie rozszerzasz, jak pokazano na listingu 17-6. Nazwany punkt zaczepienia jest wywołaniem metody sfMixer::callMixins() z nazwą jako parametr. Nazwa ta jest później używana przy rejestracji wstawek.

Listing 1 7-6 - Metoda może zawierać wiele punktów zaczepienia, jeśli są nazwane

[php]
class AgainAnotherClass
{
  public function doMoreThings()
  {
    echo "I'm ready.";
    sfMixer::callMixins('beginning');
    echo "I'm working...";
    sfMixer::callMixins('end');
    echo "I'm done.";
  }
}

Oczywiście, możesz także użyć obu technik naraz, by stworzyć klasę o możliwości dodawania nowych i modyfikowania istniejących metod, jak pokazuje listing 17-7.

Listing 17-7 - Klasa może być rozszerzalna na wiele sposobów

[php]
class BicycleRider
{
  protected $name = 'John';

  public function getName()
  {
    return $this->name;
  }

  public function sprint($distance)
  {
    echo $this->name." sprints ".$distance." meters\n";
    sfMixer::callMixins(); // The sprint() method is extendable
  }

  public function climb()
  {
    echo $this->name.' climbs';
    sfMixer::callMixins('slope'); // The climb() method is extendable here
    echo $this->name.' gets to the top';
    sfMixer::callMixins('top'); // And also here
  }

  public function __call($method, $arguments)
  {
    return sfMixer::callMixins(); // The BicyleRider class is extendable
  }
}

CAUTION Tylko klasy zadeklarowane jako rozszerzalne mogą być rozszerzone przez sfMixer. To znaczy, że nie możesz użyć tego mechanizmu w stosunku do klas które nie są "zapisane" do tej usługi.

Rejestrowanie rozszerzeń

By zarejestrować rozszerzenie dla istniejącego punktu zaczepienia, wystarczy użyć metody sfMixer::register(). Pierwszym argumentem jest element który ma być rozszerzony, drugim funkcja PHP reprezentująca wstawkę.

Format pierwszego argumentu zależy od tego, co chcesz rozszerzać:

  • Jeśli rozszerzasz klasę, użyj jej nazwy.
  • Jeśli rozszerzasz metodę z nienazwanym punktem zaczepenia, użyj wzoru klasa:metoda.
  • Jeśli rozszerzasz metodę z nazwanym punktem zaczepienia, użyj wzoru klasa:metoda:punkt.

Listing 17-8 pokazuje tą zasadę poprzez rozszerzenie klasy z listingu 17-7. Rozszerzony obiekt jest automatycznie przekazywany jako pierwszy parametr do wstawianej metody (chyba, że rozszerzona metoda jest statyczna). Metoda taka ma także dostęp do parametrów oryginalnej metody.

Listing 17-8 - Rejestrowanie rozszerzeń

[php]
class Steroids
{
  protected $brand = 'foobar';

  public function partyAllNight($bicycleRider)
  {
    echo $bicycleRider->getName()." spends the night dancing.\n";
    echo "Thanks ".$brand."!\n";
  }

  public function breakRecord($bicycleRider, $distance)
  {
    echo "Nobody ever made ".$distance." meters that fast before!\n";
  }

  static function pass()
  {
    echo " and passes half the peloton.\n";
  }
}

sfMixer::register('BicycleRider', array('Steroids', 'partyAllNight'));
sfMixer::register('BicycleRider:sprint', array('Steroids', 'breakRecord'));
sfMixer::register('BicycleRider:climb:slope', array('Steroids', 'pass'));
sfMixer::register('BicycleRider:climb:top', array('Steroids', 'pass'));

$superRider = new BicycleRider();
$superRider->climb();
=> John climbs and passes half the peloton
=> John gets to the top and passes half the peloton
$superRider->sprint(2000);
=> John sprints 2000 meters
=> Nobody ever made 2000 meters that fast before!
$superRider->partyAllNight();
=> John spends the night dancing.
=> Thanks foobar!

Mechanizm rozszerzania nie tylko dodaje nowe metody. Metoda partyAllNight() używa atrybutu klasy Steroids. Znaczy to, że gdy rozszerzasz klasę BicycleRider metodą klasy Steroids, tak naprawdę tworzysz nową instancję klasy Steroids wewnątrz BicycleRider.

CAUTION Nie możesz dodać dwóch metod o tej samej nazwie do jednej klasy. Powodem tego jest fakt, że callMixins() wywołana wewnątrz __call() używa nazwy wstawionej metody jako klucza. Oprócz tego, nie możesz dodać metody do klasy, która zawiera już własną metodę o tej samej nazwie, ponieważ mechanizm wstawek polega na wykorzystaniu magiczności metody __call() i w tym wypadku nie zostałby wywołany.

Drugim argumentem register() jest funkcja PHP, więc może to być tablica klasa::metoda, obiekt->metoda, lub sama nazwa funkcji. Zobacz przykłady na listingu 17-9.

Listing 17-9 - Każda funkcja PHP może być wstawką

[php]
// Użyj statycznej metody jako wstawki
sfMixer::register('BicycleRider', array('Steroids', 'partyAllNight'));

// Użyj metody instancji jako wstawki
$mySteroids = new Steroids();
sfMixer::register('BicycleRider', array($mySteroids, 'partyAllNight'));

// Użyj funkcji jako wstawki
sfMixer::register('BicycleRider', 'die');

Mechanizm rozszerzeń jest dynamiczny, co znaczy, że nawet gdy utworzysz instancję klasy, może ona skorzystać z późniejszych rozszerzeń. Zobacz listing 17-10.

Listing 17-10 - Mechanizm rozszerzeń jest dynamiczny i działa nawet po utworzeniu instancji

[php]
$simpleRider = new BicycleRider();
$simpleRider->sprint(500);
=> John sprints 500 meters
sfMixer::register('BicycleRider:sprint', array('Steroids', 'breakRecord'));
$simpleRider->sprint(500);
=> John sprints 500 meters
=> Nobody ever made 500 meters that fast before!

Rozszerzanie z większą precyzją

Instrukcja sfMixer::callMixins() jest tak naprawdę tylko skrótem do czegoś nieco bardziej skomplikowanego. Metoda ta automatycznie przechodzi przez listę zarejestrowanych wstawek i wywołuje je pojedynczo, przekazując im rozszerzany obiekt i parametry rozszerzanej metody. W skrócie, sfMixer::callMixins() działa mniej więcej w sposób przedstawiony na listingu 17-11.

*Listing 17-11 - callMixins() przechodzi przez listę wstawek i wywołuje je*

[php]
foreach (sfMixer::getCallables($class.':'.$method.':'.$hookName) as $callable)
{
  call_user_func_array($callable, $parameters);
}

Jeżeli chcesz przekazać inne parametry, lub zrobić coś ze zwracanymi wartościami, możesz sam napisać pętlę foreach w miejscu callMixins(). Listing 17-12 pokazuje wstawkę bardziej zintegrowaną z klasą.

*Listing 17-12 - Zastępowanie callMixins() własną pętlą*

[php]
class Income
{
  protected $amout = 0;

  public function calculateTaxes($rate = 0)
  {
    $taxes = $this->amount * $rate;
    foreach (sfMixer::getCallables('Income:calculateTaxes') as $callable)
    {
      $taxes += call_user_func($callable, $this->amount, $rate);
    }

    return $taxes;
  }
}

class FixedTax
{
  protected $minIncome = 10000;
  protected $taxAmount = 500;

  public function calculateTaxes($amount)
  {
    return ($amount > $this->minIncome) ? $this->taxAmount : 0;
  }
}

sfMixer::register('Income:calculateTaxes', array('FixedTax', 'calculateTaxes'));

SIDEBAR Zachowania Propela

Zachowania Propela, omawiane w rozdziale 8 są specjalnymi rodzajami wstawek: rozszerzają one obiekty wygenerowane przez Propela. Spójrzmy na przykład.

Wszystkie obiekty Propela odpowiadające tabelom w bazie posiadają metodę delete(), fizycznie usuwającą rekord z bazy. Ale dla klasy Invoice, której rekordów usunąć nie możesz, mógłbyś zmodyfikować metodę delete(), by zamiast usuwać rekord ustawiała wartość atrybutu is_deleted na true. Metody pobierające obiekty (doSelect(), retrieveByPk()) mogłyby brać pod uwagę jedynie rekordy z is_deleted ustawionym na false. Potrzebowałbyś także metody fizycznie usuwającej rekord, forceDelete(). Wszystkie te modyfikacje mogą być spakowane w jedną klasę, ParanoidBehavior. Ostateczna klasa Invoice rozszerza wygenerowaną przez Propela BaseInvoice, a metody ParanoidBehavior są do niej dostawione.

Tak więc zachowanie jest wstawką dla obiektów Propela. Właściwie, pojęcie "zachowanie" oznacza w symfony także fakt, że wstawka jest spakowana jako wtyczka. Dopiero co wspomniana ParanoidBehavior odpowiada prawdziwej wtyczce do symfony, nazwanej sfPropelParanoidBehaviorPlugin. Zajrzyj do wiki symfony (http://www.symfony-project.com/trac/wiki/sfPropelParanoidBehaviorPlugin) po instrukcje instalacji i wykorzystania tej wtyczki.

Jedno ostatnie słowo o zachowaniach - by móc je wspierać, obiekty Propela muszą zawierać duże ilości punktów zaczepienia. Mogą one spowolnić działanie całości i odbić się na wydajności, jeżeli nie korzystasz z zachowań. Dlatego też wstawki te są domyślnie wyłączone. By dodać do obiektów wsparcie dla zachowań, ustaw propel.builder.addBehaviors na true w swoim pliku propel.ini i wygeneruj modele ponownie.

Fabryki

Fabryka jest definicją klasy przeznaczonej dla pewnego zadania. Rdzeń symfony używa fabryk, np. dla udostępnienia kontrolerów czy obsługi sesji. Gdy framework potrzebuje nowego obiektu żądania, przeszukuje ustawienia fabryk, by znaleźć nazwę klasy odpowiedzialnej za to zadanie. Domyślnym ustawieniem dla żądań jest sfWebRequest, więc symfony tworzy obiekty tej klasy, gdy musi zająć się żądaniem. Wielkim plusem stosowania ustawień fabryk, jest łatwość modyfikacji zachowania rdzenia symfony: wystarczy, że zmienisz definicję fabryki i symfony użyje twojej klasy w miejsce domyślnej.

Ustawienia fabryk są przechowywane w pliku factories.yml. Listing 17-13 pokazuje domyślne definicje. Każda definicja składa się z nazwy ładowanej klasy i (opcjonalnie) dodatkowych parametrów. Na przykład, fabryka magazynu sesji (klucz storage:) używa parametru session_name do nazwania ciasteczka tworzonego na komputerze użytkownika w celu zachowania sesji.

*Listing 17-13 - Domyślne definicje fabryk, z pliku myapp/config/factories.yml*

cli:
  controller:
    class: sfConsoleController
  request:
    class: sfConsoleRequest

test:
  storage:
    class: sfSessionTestStorage

#all:
#  controller:
#    class: sfFrontWebController
#
#  request:
#    class: sfWebRequest
#
#  response:
#    class: sfWebResponse
#
#  user:
#    class: myUser
#
#  storage:
#    class: sfSessionStorage
#    param:
#      session_name: symfony
#
#  view_cache:
#    class: sfFileCache
#    param:
#      automaticCleaningFactor: 0
#      cacheDir:                %SF_TEMPLATE_CACHE_DIR%

Najlepszym sposobem na zmianę fabryki jest stworzenie klasy dziedziczącej po domyślnej i dodanie do niej nowych metod. Na przykład, fabryka sesji użytkownika jest ustawiona na myUser (znajdującej się w myapp/lib), która dziedziczy po sfUser. Użyj tego mechanizmu by wykorzystać istniejące fabryki. Listing 17-14 pokazuje przykład wymiany fabryki obiektu żądania.

Listing 17-14 - Nadpisywanie fabryk

[php]
// Utwórz plik myRequest.class.php w zasięgu autoloadera,
// na przykład w katalogu myapp/lib/
<?php

class myRequest extends sfRequest
{
  // Tutaj twój kod
}

// Ustaw tą klasę jako fabrykę żądań w factories.yml
all:
  request:
    class: myRequest

Pomosty do komponentów innych frameworków

Jeżeli będziesz potrzebować funkcjonalności jakiejś klasy, ale nie będziesz chcieć kopiować jej do katalogu lib/ symfony, prawdopodobnie umieścisz ją poza zasięgiem symfony. W takim wypadku, będziesz musiał użyć require w swoim kodzie, by użyć tej klasy, chyba, że użyjesz pomostu by skorzystać z autoloadera symfony.

Symfony nie oferuje (na razie) narzędzi do wszystkiego. Jeżeli będziesz potrzebować generatora PDF, API do Google Maps, lub implementację wyszukiwarki Lucene w PHP, prawdopodobnie użyjesz kilku bibliotek z Zend Framework. Jeżeli będziesz chciał manipulować obrazami z poziomu PHP, połączyć się z kontem POP3 by przeczytać e-maile, lub stworzyć interfejs konsolowy, mógłbyś użyć bibliotek z eZcomponents. Na szczęście, jeżeli ustawisz odpowiednie opcje, komponenty z obu tych bibliotek będą działać w twoich aplikacjach symfony.

Pierwszą rzeczą, którą musisz ustawić (chyba, że zainstalowałeś biblioteki poprzez PEAR) są ścieżki do bibliotek. Ustawiasz to w pliku settings.yml twojej aplikacji:

.settings:
  zend_lib_dir:   /usr/local/zend/library/
  ez_lib_dir:     /usr/local/ezcomponents/

Potem, rozszerz autoloader, podając które biblioteki mają być pod uwagę, gdy symfony nie znajdzie klasy:

.settings:
  autoloading_functions:
    - [sfZendFrameworkBridge, autoload]
    - [sfEzComponentsBridge,  autoload]

Zauważ, że ustawienia te różnią się od tych z pliku autoload.yml (więcej informacji o tym pliku znajdziesz w rozdziale 19). Opcja autoloading_functions określa klasy pośredniczące, a autoload.yml określa ścieżki i zasady poszukiwania klas. Poniższa lista przedstawia, co dzieje się, gdy tworzysz obiekt niezaładowanej klasy:

  1. Autoloader symfony (sfCore::splAutoload()) przeszukuje ścieżki zdefiniowane w pliku autoload.yml.
  2. Jeżeli klasa nie została znaleziona, wywoływane są autoloadery zdefiniowane w sf_autoloading_functions, jeden po drugm, póki któryś nie zwróci true:
  3. sfZendFrameworkBridge::autoload()
  4. sfEzComponentsBridge::autoload()
  5. Jeżeli te także zwrócą false i używasz PHP 5.0.X, symfony wygeneruje wyjątek z informacją o braku klasy. PHP w wersji 5.1 lub wyższej sam wygeneruje błąd.

Znaczy to, że komponenty danego frameworka korzystają z mechanizmu automatycznego ładowania, a ty możesz ich używać nawet łatwiej niż w ich oryginalnym środowisku. Na przykład, by użyć komponentu Zend_Search w Zend Frameworku, w celu implementacji wyszukiwarki, musisz napisać:

[php]
require_once 'Zend/Search/Lucene.php';
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
...

Z symfony i pomostem do Zend Framework, jest to prostsze. Po prostu napisz:

[php]
$doc = new Zend_Search_Lucene_Document(); // The class is autoloaded
$doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
...

Dostępne pomosty są przechowywane w katalogu $sf_symfony_lib_dir/addon/bridge/.

Wtyczki

Prawdopodobnie będziesz musiał ponownie użyć fragmentu kodu który napisałeś dla jednej z twoich aplikacji symfony. Jeżeli możesz tą funkcjonalność zmieścić w jednej klasie, wtedy nie ma problemu: Wrzuć klasę do folderu lib/ innej aplikacji i autoloader zajmie się resztą. Jeśli jednak kod jest rozrzucony po wielu plikach, na przykład kompletnie nowy styl do generatora panelu administracyjnego lub kombinacja plików JavaScript i funkcji pomocniczych do automatyzacji twojego ulubionego efektu wizualnego, kopiowanie plików nie jest najlepszym rozwiązaniem.

Wtyczki oferują sposób na spakowanie kodu rozproszonego po wielu plikach i ponowne użycie tego kodu w wielu projektach. Częścią wtyczki mogą być klasy, filtry, wstawki, funkcje pomocnicze, konfiguracje, zadania, moduły, schematy i rozszerzenia modeli, fixture'y, web assety, itp. Wtyczki są łatwe w instalacji, aktualizacji i deinstalacji. Mogą być rozprowadzane jako archiwa .tgz, pakiety PEAR, lub prosty checkout kodu z repozytorium. Zaletą pakowania wtyczek jako pakietów PEAR jest możliwość zarządzania zależnościami, prostsza aktualizacja i automatyczne odnajdywanie. Mechanizmy ładowania symfony biorą wtyczki pod uwagę, a możliwości oferowane przez wtyczkę są dostępne tak, jakby kod wtyczki był częścią frameworka.

Więc, krótko mówiąc, wtyczka jest spakowanym rozszerzeniem dla projektu symfony. Dzięki wtyczkom, będziesz mógł dzielić pomiędzy aplikacje nie tylko swój kod, ale także używać kodu wniesionego przez innych i dodawać nowe rozszerzenia do rdzenia symfony.

Znajdowanie wtyczek do Symfony

Strona projektu symfony zawiera podstronę dedykowaną wtyczkom. Jest ona częścią wiki symfony i jest dostępna pod adresem:

http://www.symfony-project.com/trac/wiki/SymfonyPlugins

Każda wtyczka wymieniona tam ma własną stronę, ze szczegółowymi instrukcjami instalacji i dokumentacją.

Niektóre z tych wtyczek są wniesione przez społeczność, a niektóre są napisane przez programistów rdzenia symfony. Pośród tych drugich znajdziesz następujące:

  • sfFeedPlugin: Automatyzuje manipulację kanałami RSS i Atom
  • sfThumbnailPlugin: Tworzy miniaturki--na przykład, uploadowanych plików
  • sfMediaLibraryPlugin: Pozwala na upload i zarządzanie multimediami, zawiera rozszerzenie dla edytorów WYSIWYG pozwalające na wstawianie obrazków do tekstu
  • sfShoppingCartPlugin: Pozwala na zarządzanie wózkiem sklepowym
  • sfPagerNavigationPlugin: Udostępnia klasyczne, oraz Ajax-owe kontrolki stronicowania, bazujące na obiekcie sfPager
  • sfGuardPlugin: Udostępnia autentykację, autoryzację oraz inne funkcje do zarządzania użytkownikami nad standardowymi funkcjami symfony
  • sfPrototypePlugin: Udostępnia pliki JavaScript prototype oraz script.aculo.us jako samodzielną bibliotekę
  • sfSuperCachePlugin: Zapisuje cache stron w katalogu poniżej katalogu głównego serwera, w celu jak najszybszego odczytu
  • sfOptimizerPlugin: Optymalizuje kod twojej aplikacji by przyspieszyć ją w środowisku produkcyjnym (zobacz następny rozdział po szczegóły)
  • sfErrorLoggerPlugin: Zapisuje każdy błąd 404 i 500 w bazie danych i udostępnia moduł administracyjny do przeglądania tych błędów
  • sfSslRequirementPlugin: Udostępnia obsługę szyfrowania SSL dla akcji

Wiki proponuje także wtyczki zaprojektowane do rozszerzania twoich obiektów Propela, zwanych także zachowaniami. Pośród nich, znajdziesz:

  • sfPropelParanoidBehaviorPlugin: Zastępuje fizyczne usuwanie obiektu aktualizacją kolumny deleted_at
  • sfPropelOptimisticLockBehaviorPlugin: Implementuje optymistyczne blokowanie dla obiektów Propela

Powinieneś regularnie sprawdzać wiki symfony, ponieważ nowe wtyczki są wciąż dodawane, a przynoszą one bardzo przydatne skróty do wielu aspektów programowania aplikacji web.

Oprócz wiki symfony, innymi drogami do rozprowadzania wtyczek jest udostępnienie archiwów wtyczki do ściągnięcia, umieszczenia ich na kanale PEAR, lub w publicznym repozytorium kontroli wersji.

Instalowanie wtyczki

Proces instalacji wtyczki jest różny dla każdego sposobu pakowania. Zawsze sprawdź dołączony plik README i/lub instrukcje instalacji na stronie wtyczki. Oprócz tego, zawsze czyść cache symfony po instalacji wtyczki.

Wtyczki są instalowane dla całych projektów. Każda z metod opisanych w następnych sekcjach kończy się umieszczeniem plików wtyczki w katalogu myproject/plugins/pluginName/.

Wtyczki PEAR

Wtyczki wymienione na wiki symfony są udostępnione jako pakiety PEAR dołączone do strony wiki. By zainstalować taki plugin, użyj zadania plugin-install podając pełny adres URL, tak jak pokazano na listingu 17-15.

Listing 17-15 - Instalowanie wtyczki z wiki symfony

> cd myproject
> php symfony plugin-install http://plugins.symfony-project.com/pluginName
> php symfony cc

Możesz także ściągnąć wtyczkę i zainstalować ją z dysku, w tym wypadku, zastąp nazwę kanału absolutną ścieżką do archiwum, tak jak pokazano na listingu 17-16.

Listing 17-16 - Instalowanie wtyczki z lokalnego archiwum PEAR

> cd myproject
> php symfony plugin-install /home/sciezka/do/wtyczki/pluginName.tgz
> php symfony cc

Niektóre wtyczki są dostępne na kanałach PEAR. Możesz je instalować używając zadania plugin-install, podając nazwę kanału, tak jak pokazano na listingu 17-17.

Listing 17-17 - Instalowanie wtyczki z kanału PEAR

> cd myproject
> php symfony plugin-install channelName/pluginName
> php symfony cc

Wszystkie te sposoby instalacji używają pakietów PEAR, więc termin "wtyczka PEAR" będzie używany ogólnie do określenia wtyczek zainstalowanych z wiki symfony, kanału PEAR, albo ściągniętego archiwum PEAR.

Wtyczki spakowane

Niektóre wtyczki są zwykłymi archiwami. By je zainstalować, po prostu rozpakuj zawartość archiwum do folderu plugins/ twojego projektu. Jeżeli wtyczka zawiera podfolder web/, skopiuj go albo dowiąż do katalogu web/ projektu, tak jak pokazano na listingu 17-18. I nie zapomnij wyczyścić cache.

Listing 17-18 - Instalowanie wtyczki z archiwum

> cd plugins
> tar -zxpf myPlugin.tgz
> cd ..
> ln -sf plugins/myPlugin/web web/myPlugin
> php symfony cc

Instalacja wtyczek z systemów kontroli wersji

Źródła wtyczek są czasem przechowywane w repozytoriach kodu dla kontroli wersji. Możesz je zainstalować poprzez wykonanie prostego checkoutu kodu do katalogu plugins/, ale może to być kłopotliwe, jeśli twój projekt także jest pod kontrolą wersji.

W takim wypadku możesz zadeklarować wtyczkę jako zewnętrzną zależność, wtedy każda aktualizacja źródeł twojego projektu zaktualizuje także źródła wtyczki. Na przykład, Subversion przechowuje zewnętrzne zależności we własności svn:externals. Możesz więc dodać wtyczkę poprzez edycję tej własności i aktualizację kodu twojego projektu, tak jak pokazano na listingu 17-19.

Listing 17-19 - Instalowanie wtyczki z repozytorium kontroli wersji

> cd myproject
> svn propedit svn:externals plugins
  pluginName   http://svn.example.com/pluginName/trunk
> svn up
> php symfony cc

NOTE Jeżeli wtyczka zawiera katalog web/, musisz go dowiązać tak samo, jak w przypadku instalacji wtyczki z archiwum.

Aktywacja modułu z wtyczki

Niektóre wtyczki zawierają całe moduły. Jedyną różnicą między modułami z wtyczek a klasycznymi modułami jest fakt, że moduły z wtyczek nie są przechowywane w katalogu myproject/apps/myapp/modules/ (w celu łatwej aktualizacji). Muszą one być także aktywowane w pliku settings.yml, jak pokazano na listingu 17-20.

*Listing 17-20 - Aktywacja modułu wtyczki, w myapp/config/settings.yml*

all:
  .settings:
    enabled_modules:  [default, sfMyPluginModule]

Jest to wymagane by uniknąć sytuacji w której moduł wtyczki zostałby przez pomyłkę udostępniony aplikacji która go nie wymaga, co mogłoby utworzyć dziurę w bezpieczeństwie. Wyobraź sobie wtyczkę która udostępnia moduły frontend oraz backend. Włączyłbyś moduł frontend tylko dla aplikacji publicznej, a backend tylko dla administracyjnej. Dlatego też moduły wtyczek nie są domyślnie włączone.

TIP Moduł default jest jedynym domyślnie włączonym modułem. Nie jest to właściwie moduł wtyczki, ponieważ jest częścią frameworka, a przechowywany jest w $sf_symfony_data_dir/modules/default/. Jest to moduł udostępniający strony z gratulacjami i domyślne strony dla błędów 404 i credentials required. Jeżeli nie chcesz używać tych stron, po prostu usuń ten moduł z enabled_modules.

Wyświetlanie listy zainstalowanych wtyczek

Jeżeli spojrzenie na katalog plugins/ projektu mówi ci jakie wtyczki są zainstalowane, zadanie plugin-list powie ci nawet więcej: numer wersji oraz nazwę kanału z którego została zainstalowana wtyczka (zobacz listing 17-21).

Listing 17-21 - Wyświetlanie lisy zainstalowanych wtyczek

> cd myproject
> php symfony plugin-list

Installed plugins:
sfPrototypePlugin               1.0.0-stable # pear.symfony-project.com (symfony)
sfSuperCachePlugin              1.0.0-stable # pear.symfony-project.com (symfony)
sfThumbnail                     1.1.0-stable # pear.symfony-project.com (symfony)

Aktualizacja i usuwanie wtyczek

By odinstalować wtyczkę PEAR, wywołaj zadanie plugin-uninstall z katalogu głównego projektu, tak jak pokazano na listingu 17-22. Musisz poprzedzić nazwę wtyczki nazwą kanału z którego została zainstalowana (użyj zadania plugin-list by się tego dowiedzieć).

Listing 17-22 - Usuwanie wtyczki

> cd myproject
> php symfony plugin-uninstall pear.symfony-project.com/sfPrototypePlugin
> php symfony cc

TIP Niektóre kanały posiadają alias. Na przykład, inną nazwą kanału pear.symfony-project.com jest symfony, co oznacza, że w celu usunięcia wtyczki sfPrototypePlugin z listingu 17-22, możesz po prostu użyć komendy php symfony plugin-uninstall symfony/sfPrototypePlugin.

By usunąć wtyczkę instalowaną z archiwum lub SVN, ręcznie usuń pliki wtyczki z katalogów plugins/ i web/, po czym wyczyść cache.

By zaktualizować wtyczkę, użyj zadania plugin-upgrade (dla wtyczek PEAR) lub wywołaj svn update (dla wtyczek z SVN). Aktualizacja wtyczek rozprowadzanych jako archiwa jest nieco trudniejsza.

Anatomia wtyczki

Wtyczki są pisane w PHP. Jeżeli rozumiesz jak zorganizowane są aplikacje, zrozumiesz także strukturę wtyczki.

Struktura plików wtyczki

Katalog wtyczki jest zorganizowany mniej więcej tak, jak katalog projektu. Pliki wtyczki muszą znajdować się we właściwych folderach, by mogły być znalezione przez symfony. Przyjrzyj się strukturze plików wtyczki opisanej na listingu 17-23.

Listing 17-23 - Struktura plików wtyczki

pluginName/
  config/
    *schema.yml        // Schematy danych
    *schema.xml
    config.php         // Specyficzna konfiguracja wtyczki
  data/
    generator/
      sfPropelAdmin
        */             // Szablony generatora panelu administracyjnego
          templates/
          skeleton/
    fixtures/
      *.yml            // Pliki z fixture'ami
    tasks/
      *.php            // Zadania Pake
  lib/
    *.php              // Klasy
    helper/
      *.php            // Funkcje pomocnicze
    model/
      *.php            // Modele
  modules/
    */                 // Moduły
      actions/
        actions.class.php
      config/
        module.yml
        view.yml
        security.yml
      templates/
        *.php
      validate/
        *.yml
  web/
    *                  // Assety

Możliwości wtyczek

Wtyczki mogą zawierać wiele rzeczy. Ich zawartość jest automatycznie brana pod uwagę przez aplikację podczas działania i podczas wywoływania zadań z poziomu linii komend. Jednak by wtyczki działały jak należy, musisz uszanować kilka konwencji:

  • Schematy baz danych są wykrywane przez zadania propel-*. Gdy wywołujesz propel-build-model dla swojego projektu, przebudowujesz modele projektu oraz wtyczek. Zauważ, że schematy wtyczek atrybut package muszą zawsze mieć ustawiony według schematu plugins.pluginName.lib.model, tak jak pokazano na listingu 17-24.

*Listing 17-24 - Przykładowy schemat bazy danych wtyczki, w myPlugin/config/schema.yml*

propel:
  _attributes:    { package: plugins.myPlugin.lib.model }
  my_plugin_foobar:
    _attributes:    { phpName: myPluginFoobar }
      id:
      name:           { type: varchar, size: 255, index: unique }
      ...
  • Konfiguracja wtyczki musi być dołączona w skrypcie startującym (config.php). Plik ten jest wykonywany po konfiguracji aplikacji i projektu, więc symfony jest już ustawione w tym czasie. Możesz użyć tego pliku, na przykład, by dodać katalogi do include_path, lub by rozszerzyć instniejące klasy wstawkami.
  • Fixture'y są zlokalizowane w katalogu data/fixtures/ wtyczki i będą przetwarzane przez zadanie propel-load-data.
  • Zadania są dostępne dla komendy symfony zaraz po instalacji wtyczki. Wywołaj symfony bez parametrów by zobaczyć listę dostępnych zadań, włączając w to zadania dodane przez wtyczki.
  • Klasy są ładowane są tak samo jak te umieszczone w katalogach lib/ projektu.
  • Funkcje pomocnicze są znajdowane automatycznie gdy wywołasz use_helper() w szablonie. Muszą być w podkatalogu helper/ któregoś z katalogów lib/ wtyczki.
  • Modele umieszczone w myplugin/lib/model/ rozszerzają modele generowane przez Propela (w myplugin/lib/model/om/ i /myplugin/lib/model/map/). Są oczywiście automatycznie ładowane. Pamiętaj, że nie możesz nadpisywać wygenerowanych klas wtyczek z własnego projektu.
  • Moduły udostępniają nowe akcje dostępne z zewnątrz, jeśli tylko włączysz je w enabled_modules twojej aplikacji.
  • Assety web (obrazki, skrypty, arkusze stylów, itp.) są udostępniane serwerowi. Gdy instalujesz wtyczkę używając linii komend, symfony tworzy symboliczne dowiązanie do katalogu web/ projektu (jeśli twój system na to pozwala), lub kopiuje zawartość katalogu web/ wtyczki do katalogu web/ projektu. W przypadku wtyczek instalowanych z archiwów lub repozytorium kontroli wersji, musisz ręcznie skopiować katalog web/ wtyczki (plik README dołączony do wtyczki powinien o tym wspominać).

Ręczne ustawianie wtyczki

Są pewne elementy których zadanie plugin-install nie może obsłużyć, i które wymagają ręcznego ustawiania podczas instalacji: * Dodatkowa konfiguracja aplikacji może być używana w kodzie wtyczki (np. używając sfConfig::get('app_myconfig_foo')), ale nie możesz umieścić domyślnych wartości w pliku app.yml zlokalizowanym w katalogu config/ wtyczki. By obsłużyć domyślne wartości, użyj drugiego argumentu sfConfig::get(). Ustawienia wciąż mogą być nadpisane przez aplikację (zobacz listing 17-25). * Dodatkowe zasady routingu muszą być dodane ręcznie do pliku routing.yml aplikacji. * Dodatkowe filtry muszą być dodane ręcznie do pliku filters.yml aplikacji. * Dodatkowe fabryki muszą być dodane ręcznie do pliku factories.yml aplikacji.

Ogólnie rzecz biorąc, cała konfiguracja mająca wylądować w plikach konfiguracyjnych aplikacji musi być dodana ręcznie. Wtyczki wymagające ręcznej instalacji powinny zawierać plik README szczegółowo opisujący proces instalacji.

Personalizowanie wtyczki dla aplikacji

Kiedy chcesz spersonalizować wtyczkę, nigdy nie zmieniaj kodu znajdującego się w katalogu plugins/. Jeśli to zrobisz, stracisz swoje modyfikacje po aktualizacji wtyczki. Do potrzeb personalizacji wtyczki udostępniają konfigurację i wspierają nadpisywanie.

Dobrze zaprojektowana wtyczka używa ustawień, które mogą być zmienione w pliku app.yml aplikacji, tak jak pokazano na listingu 17-25.

Listing 17-25 - Personalizacja wtyczki używającej konfiguracji

[php]
// przykładowy kod wtyczki
$foo = sfConfig::get('app_my_plugin_foo', 'bar');

// Zmień domyślną wartość 'foo' ('bar') w pliku app.yml
all:
  my_plugin:
    foo:       barbar

Ustawienia modułów i ich domyślne wartości są często opisane w pliku README wtyczki.

Możesz zamienić domyślną zawartość modułu wtyczki tworząc moduł o tej samej nazwie we własnej aplikacji. Nie jest to tak naprawdę nadpisywanie, ponieważ elementy twojej aplikacji są używane zamiast tych z wtyczki. Działa to dobrze jeśli stworzysz szablony i pliki konfiguracyjne o tej samej nazwie co te we wtyczce.

Z innej strony, jeżeli wtyczka chce zaoferować moduł z możliwością nadpisania jego akcji, actions.class.php w module wtyczki musi być pusty i dziedziczyć z automatycznie ładowanej klasy, by metoda tej klasy mogła być dziedziczona także przez actions.class.php modułu aplikacji. Zobacz listing 17-26.

Listing 17-26 - Personalizacja akcji wtyczki

[php]
// In myPlugin/modules/mymodule/lib/myPluginmymoduleActions.class.php
class myPluginmymoduleActions extends sfActions
{
  public function executeIndex()
  {
    // Some code there
  }
}

// In myPlugin/modules/mymodule/actions/actions.class.php
class mymoduleActions extends myPluginmymoduleActions
{
  // Nothing
}

// In myapp/modules/mymodule/actions/actions.class.php
class mymoduleActions extends myPluginmymoduleActions
{
  public function executeIndex()
  {
    // Override the plug-in code there
  }
}

Jak napisać wtyczkę

Tylko wtyczki pakowane jako pakiety PEAR mogą być instalowane poprzez zadanie plugin-install. Pamiętaj, że taka wtyczka może być rozprowadzana poprzez wiki symfony, kanał PEAR, lub zwykły download. Więc jeżeli chcesz być autorem wtyczki, lepiej jest opublikować ją jako pakiet PEAR niż jako zwykłe archiwum. Dodatkowo, wtyczki spakowane jako pakiety PEAR są łatwiejsze w aktualizacji, mogą deklarować zależności i automatycznie umieszczać assety w katalogu web/.

Organizacja plików

Przypuśćmy, że zaimplementowałeś nową funkcję i chcesz spakować ją jako wtyczkę. Pierwszym krokiem jest logiczna organizacja plików, by mechanizmy ładowania symfony mogły je znaleźć. W tym celu musisz trzymać się struktury pokazanej na listingu 17-23. Listing 17-27 pokazuje przykładową strukturę plików wtyczki sfSamplePlugin.

Listing 17-27 - Przykładowa lista plików wtyczki

sfSamplePlugin/
  README
  LICENSE
  config/
    schema.yml
  data/
    fixtures/
      fixtures.yml
    tasks/
      sfSampleTask.php
  lib/
    model/
      sfSampleFooBar.php
      sfSampleFooBarPeer.php
    validator/
      sfSampleValidator.class.php
  modules/
    sfSampleModule/
      actions/
        actions.class.php
      config/
        security.yml
      lib/
        BasesfSampleModuleActions.class.php
      templates/
        indexSuccess.php
  web/
    css/
      sfSampleStyle.css
    images/
      sfSampleImage.png

Podczas tworzenia, lokalizacja katalogu wtyczki (sfSamplePlugin/ w przpadku listingu 17-27) nie jest ważne. Może znajdować się w każdym miejscu na dysku.

TIP Weź przykład z istniejących wtyczek i, podczas pierwszych prób tworzenia wtyczki, staraj się naśladować ich nazewnictwo i strukturę plików.

Tworzenie pliku package.xml

Następnym krokiem tworzenia wtyczki jest stworzenie pliku package.xml w katalogu głównym wtyczki. Plik ten podąża za składnią PEAR. Przyjrzyj się typowemu plikowi package.xml wtyczki symfony na listingu 17-28.

*Listing 17-28 - Przykładowy package.xml wtyczki symfony*

<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.4.6" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
 <name>sfSamplePlugin</name>
 <channel>pear.symfony-project.com</channel>
 <summary>symfony sample plugin</summary>
 <description>Just a sample plugin to illustrate PEAR packaging</description>
 <lead>
  <name>Fabien POTENCIER</name>
  <user>fabpot</user>
  <email>fabien.potencier@symfony-project.com</email>
  <active>yes</active>
 </lead>
 <date>2006-01-18</date>
 <time>15:54:35</time>
 <version>
  <release>1.0.0</release>
  <api>1.0.0</api>
 </version>
 <stability>
  <release>stable</release>
  <api>stable</api>
 </stability>
 <license uri="http://www.symfony-project.com/license">MIT license</license>
 <notes>-</notes>
 <contents>
  <dir name="/">
   <file role="data" name="README" />
   <file role="data" name="LICENSE" />
   <dir name="config">
    <!-- model -->
    <file role="data" name="schema.yml" />
   </dir>
   <dir name="data">
    <dir name="fixtures">
     <!-- fixtures -->
     <file role="data" name="fixtures.yml" />
    </dir>
    <dir name="tasks">
     <!-- tasks -->
     <file role="data" name="sfSampleTask.php" />
    </dir>
   </dir>
   <dir name="lib">
    <dir name="model">
     <!-- model classes -->
     <file role="data" name="sfSampleFooBar.php" />
     <file role="data" name="sfSampleFooBarPeer.php" />
    </dir>
    <dir name="validator">
     <!-- validators ->>
     <file role="data" name="sfSampleValidator.class.php" />
    </dir>
   </dir>
   <dir name="modules">
    <dir name="sfSampleModule">
     <file role="data" name="actions/actions.class.php" />
     <file role="data" name="config/security.yml" />
     <file role="data" name="lib/BasesfSampleModuleActions.class.php" />
     <file role="data" name="templates/indexSuccess.php" />
    </dir>
   </dir>
   <dir name="web">
    <dir name="css">
     <!-- stylesheets -->
     <file role="data" name="sfSampleStyle.css" />
    </dir>
    <dir name="images">
     <!-- images -->
     <file role="data" name="sfSampleImage.png" />
    </dir>
   </dir>
  </dir>
 </contents>
 <dependencies>
  <required>
   <php>
    <min>5.0.0</min>
   </php>
   <pearinstaller>
    <min>1.4.1</min>
   </pearinstaller>
   <package>
    <name>symfony</name>
    <channel>pear.symfony-project.com</channel>
    <min>1.0.0</min>
    <max>1.1.0</max>
    <exclude>1.1.0</exclude>
   </package>
  </required>
 </dependencies>
 <phprelease />
 <changelog />
</package>

Interesującymi częściami są tagi <contents> i <dependencies>, opisane niżej. Co do reszty tagów, nie ma w nich nic specyficznego dla symfony, więc możesz odnieść się do dokumentacji PEAR (http://pear.php.net/manual/en) po więcej informacji nt. formatu package.xml.

Zawartość

Tag <contents> jest miejscem w którym opisujesz strukturę plików wtyczki. Dzięki temu PEAR będzie wiedzieć co skopiować gdzie. Opisz strukturę plików używając tagów <dir> i <file>. Wszystkie tagi <file> muszą mieć atrybut role="data". Część <contents> listingu 17-28 opisuje strukturę plików z listingu 17-27.

TIP Użycie tagów <dir> nie jest wymagane, ponieważ możesz użyć względnych ścieżek jako wartości atrybutu name tagów <file>. Jest to jednak zalecane, by plik package.xml pozostał czytelny.

Zależności wtyczek

Wtyczki są zaprojektowane by działać z odpowiednimi wersjami PHP, PEAR, symfony, pakietów PEAR lub innych wtyczek. Zadeklarowanie tych zależności w tagu <dependencies> pozwoli PEAR-owi określić, czy wymagane pakiety są już zainstalowane i w razie potrzeby wygenerować wyjątek.

Zawsze powinieneś deklarować zależność od PHP, PEAR i symfony, w wersjach przynajmniej odpowiadających twojej instalacji jako wymagane minimum. Jeżeli nie wiesz jakie wersje podać, zadeklaruj zależność od PHP 5.0, PEAR 1.4 i symfony 1.0.

Zalecane jest także zadeklarowanie maksymalnej wersji symfony dla każdej wtyczki. Spowoduje to wygenerowanie błędu gdy użytkownik będzie chciał użyć wtyczki z bardziej zaawansowaną wersją frameworka, i zobliguje autora wtyczki do sprawdzenia, czy działa ona z daną wersją przed wydaniem kolejnej wersji. Lepiej dostać ostrzeżenie i ściągnąć aktualizację niż mieć wtyczkę, która po cichu przestanie działać.

Budowanie wtyczki

Komponent PEAR posiada komendę (pear package) która tworzy archiwum .tgz pakietu, jeśli zostanie ona wywołana z katalogu zawierającego package.xml, jak pokazano na listingu 17-29.

Listing 17-29 - Pakowanie wtyczki jako paczki PEAR

> cd sfSamplePlugin
> pear package

Package sfSamplePlugin-1.0.0.tgz done

Po zbudowaniu wtyczki, sprawdź czy działa instalując ją samemu, jak pokazano na listingu 17-30.

Listing 17-30 - Instalowanie wtyczki

> cp sfSamplePlugin-1.0.0.tgz /home/production/myproject/
> cd /home/production/myproject/
> php symfony plugin-install sfSamplePlugin-1.0.0.tgz

Spakowane pliki trafią do różnych katalogów twojego projektu, zgodnie z opisującym je tagiem <contents>. Listing 17-31 pokazuje gdzie powinny zostać umieszczone pliki sfSamplePlugin po instalacji.

*Listing 17-31 - Pliki wtyczki są instalowane do katalogów plugins/ i web/*

plugins/
  sfSamplePlugin/
    README
    LICENSE
    config/
      schema.yml
    data/
      fixtures/
        fixtures.yml
      tasks/
        sfSampleTask.php
    lib/
      model/
        sfSampleFooBar.php
        sfSampleFooBarPeer.php
      validator/
        sfSampleValidator.class.php
    modules/
      sfSampleModule/
        actions/
          actions.class.php
        config/
          security.yml
        lib/
          BasesfSampleModuleActions.class.php
        templates/
          indexSuccess.php
web/
  sfSamplePlugin/               ## Kopia lub symboliczne dowiązanie, zależnie od systemu
    css/
      sfSampleStyle.css
    images/
      sfSampleImage.png

Przetestuj zachowanie wtyczki w swojej aplikacji. Jeżeli działa dobrze, jesteś gotowy do umieszczenia jej w innych projektach--lub udostępnienia jej społeczności symfony.

Zamieszczanie własnej wtyczki na stronie Symfony

Wtyczka symfony zyskuje większą popularność, gdy jest rozpowszechniana poprzez stronę symfony-project.com. Twoje wtyczki także mogą być rozpowszechniane tą drogą, jeżeli wykonasz następujące instrukcje: 1. Upewnij się, że plik README opisuje instalację i użycie twojej wtyczki, i że plik LICENSE zawiera informacje o użytej licencji. Sformatuj swój plik README używając składni formatowania Wiki (http://www.symfony-project.com/trac/wiki/WikiFormatting). 2. Stwórz pakiet PEAR dla twojej wtyczki używając komendy pear package i przetestuj go. Pakiet PEAR musi być nazwany sfSamplePlugin-1.0.0.tgz (1.0.0 jest wersją wtyczki). 3. Stwórz nową stronę na wiki nazwaną sfSamplePlugin (Plugin jest wymaganym przyrostkiem). Opisz na niej sposób użycia wtyczki, licencję, zależności i procedurę instalacji. Możesz użyć zawartości pliku README wtyczki. Wzoruj się na istniejących stronach wtyczek. 4. Załącz swój pakiet PEAR do strony (sfSamplePlugin-1.0.0.tgz). 5. Dodaj nową stronę wtyczki ([wiki:sfSamplePlugin]) do listy dostępnych wtyczek, która także jest stroną wiki (http://www.symfony-project.com/trac/wiki/SymfonyPlugins).

Jeżeli wykonasz te instrukcje, użytkownicy będą mogli zainstalować wtyczkę używając prostej komendy w katalogu projektu:

> php symfony plugin-install http://plugins.symfony-project.com/sfSamplePlugin

Nazewnictwo

By utrzymać porządek w folderze plugins/, upewnij się, że nazwy wtyczek są napisane wielbłądzimStylem i kończą się na Plugin (na przykład, shoppingCartPlugin, feedPlugin, itp.). Przed nazwaniem swojej wtyczki sprawdź czy nie istnieje już wtyczka o takiej samej nazwie.

NOTE Wtyczki polegające na Propelu powinny zawierać Propel w nazwie. Na przykład, wtyczka autentykacji używająca obiektów Propela powinna być nazwana sfPropelAuth.

Wtyczki powinny zawsze zawierać plik LICENSE zawierający warunki użycia i wybraną licencję. Powinieneś także dodać plik README do wyjaśnienia zmian wersji, przeznaczenia wtyczki, jej działania, instalacji i konfiguracji, itp.

Podsumowanie

Klasy symfony zawierają punkty zaczepienia sfMixer które dają możliwość modyfikacji ich z poziomu aplikacji. Mechanizm wstawek pozwala na wielokrotne dziedziczenie i nadpisywanie klas podczas działania, nawet jeśli ograniczenia PHP tego zabraniają. Możesz więc łatwo rozszerzyć możliwości symfony, nawet jeśli musisz zmodyfikować klasy rdzenia--po to powstała konfiguracja fabryk.

Wiele takich rozszerzeń istnieje; są spakowane jako wtyczki, łatwe do instalacji, aktualizacji i usunięcia poprzez linię komend symfony. Tworzenie wtyczki jest tak proste jak tworzenie pakietu PEAR i zapewnia łatwe dzielenie kodu między aplikacjami.

Wiki symfony zawiera wiele wtyczek i możesz nawet dodać własne. Teraz, gdy już wiesz jak to robić, mamy nadzieję, że wzbogacisz rdzeń symfony wieloma użytecznymi rozszerzeniami!