Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [SF2][SF][Symfony2]Managing Configuration with Extensions. Ktoś to rozumie? Ktoś tego używał?
Forum PHP.pl > Forum > PHP > Frameworki
koszykarze
Panie boże, męczę ten temat od tygodnia.

http://symfony.com/doc/2.8/components/depe...ompilation.html

Zacząłem od nauki service container w oficjalnym podręczniku. Tam wszystko jasne, jak dla opóźnionych w rozwoju. To lubię. Na początku tego tematu jest notka "chcesz wiedzieć więcej czytaj o komponencie Dependency Injection". No to dobrze, przeczytam. Zaczyna się bez problemów: rejestracja serwisów w php w obiekcie ContainerBundle, dodawanie parametrów, argumentów. Ładowanie istniejącyh plików konfiguracyjnych. Modyfikacja definicji serwisów.

Az przychodzi rozdział "Compiling the Container" (w pdf chapter 38). a konkretnie sekcja "Managing Configuration with Extension". (na 5 stronach w pdf)

I tu już nie rozumiem nic. Do tej pory wszystko było jasne:
- Definiuję serwisy w yml (albo xml, albo php) w swoim bundlu w resources/config/services.yml. Importuję ten plik konfiguracyjny w app/config
- Natomiast, gdy chcę by bundel był wyodrębniony i używany w innych projektach to trzeba zadbać o to by serwisy bundla zarejestrować w klasie Extension (implementującej ExtensionInterface) by serwisy były dostępne po prezniesieniu bundla do nowego projektu. By to zrobić trzeba załadować pliki konfiguracyjne w metodzie load()
  1. class AcmeDemoExtension implements ExtensionInterface
  2. {
  3. public function load(array $configs, ContainerBuilder $container)
  4. {
  5. $loader = new XmlFileLoader(
  6. $container,
  7. new FileLocator(__DIR__.'/../Resources/config')
  8. );
  9. $loader->load('services.xml');
  10. }
  11. // ...
  12. }


Ale to co dalej jest opisywane na kolejnych stronach (pdf) to jest sajgon.
- "The Extension must specify a getAlias method to implement the interface."
  1. class AcmeDemoExtension implements ExtensionInterface
  2. {
  3. // ...
  4. public function getAlias()
  5. {
  6. return 'acme_demo';
  7. }
  8. }

- jaki interfejs? po co? ( musi? a w klasie Extension FOSRestBundle tej metody nie ma.

- For YAML configuration files specifying the alias for the extension as a key will mean that those values
are passed to the Extension's load method

  1. # ...
  2. acme_demo:
  3. foo: fooValue
  4. bar: barValue

- co?

Dobra nie będę dalej cytował kolejnych fragmentów, mam pokreślony cały rozdział pytaniami, alei tak nikt mi nie odpowie na te pytania.

Ale jakby komuś się chciało to tak z grubsza jaka jest idea tego rozdziału? Kiedy mam tego używać? Do czego jest klasa Extension? Czym jest parametr $configs metody load() ?

(a na końcu tego rozdziału jest notka "In the full-stack framework the compilation and caching of the container is taken care of for you. W google i na stackoverfolw nie ma nic co dotyczy COMPILING CONTAINER. Ktoś tego w ogóle używa? )
===========================================
=====edit===================================
macie rację, może zacznę od nauki yamla.
Crozin
Wygląda na to, że jednak nie rozumiesz poprawnie sposobu działania tego komponentu FW. Najpierw kilka sprostowań:
Cytat
rejestracja serwisów w php w obiekcie ContainerBundle
Nie ma czegoś takiego jak ContainerBundle. Chodziło Ci zapewne o ContainerBuildera? Jeśli nie napisz co miałeś na myśli.
Cytat
Natomiast, gdy chcę by bundel był wyodrębniony i używany w innych projektach to trzeba zadbać o to by serwisy bundla zarejestrować w klasie Extension (implementującej ExtensionInterface) by serwisy były dostępne po prezniesieniu bundla do nowego projektu.
Nie ma żadnego podziału na bundle "wewnętrzne" i "wyodrębnione" - wszystkie działają w dokładnie ten sam sposób, włącznie z bundle'ami samego Symfony.
Cytat
Ale to co dalej jest opisywane na kolejnych stronach (pdf) to jest sajgon.
- "The Extension must specify a getAlias method to implement the interface."
[...]
- jaki interfejs? po co? ( musi? a w klasie Extension FOSRestBundle tej metody nie ma.
Po co używać interfejsów pisać nie będę, bo jest tego masa w sieci. FOSRestBundle nie jest tu żadnym wyjątkiem, tylko jego metoda getAlias() jest... odziedziczona z Symfony\Component\DependencyInjection\Extension\Extension.
Cytat
- For YAML configuration files specifying the alias for the extension as a key will mean that those values
are passed to the Extension's load method
[...]
- co?
Oznacza to, że jeżeli Twój bundle zwraca z metody ...Extension::getAlias() wartość "abc_def_ghi" to w konfiguracji w pliku app/config/config.yml będziesz korzystać właśnie z tego klucza by określić jego konfigurację, która później zostanie przekazana jako zwykła tablica do ...Extension::load() w celu dalszego przetworzenia.

---

Jak działa ten komponent w uproszczeniu w standardowej konfiguracji? Zakładamy, że mamy wyczyszczony cache.

1. Każdy bundle rejestrowany jest przy pomocy klasy MySuperBundle, która implementuje BundleInterface, który to definiuje metodę getContainerExtension() zwracającą właśnie obiekt klasy rozszerzenia (może tego nie robić, ale to rzadkie w tym przypadku).
2. Pliki konfiguracyjne YAML z app/config są scalane w jeden (config_prod.yml ma na początku wczytanie config.yml, a ten z kolei wczytuje sobie parameters.yml i security.yml). Odpowiedzialne są za to "reguły" IMPORTS.
3. Gdzieś wewnątrz bebechów Symfony rozpoczyna się proces kompilacji kontenera. Tworzony jest obiekt ContainerBuilder.
4. Dla każdego zarejestrowanego bundle'a odpalana jest metoda BundleInterface::build(), a w przypadku, gdy dany bundle posiada rozszerzenie uruchamiana jest metoda ExtensionInterface::load(). Dla tej ostatniej przekazany jest dodatkowy parametr - konfiguracja spod klucza określonego przez ExtensionInterface::getAlias().
5. To co się dzieje wewnątrz ExtensionInterface::load() to już sprawa wyłącznie danego rozszerzenia, ale standardowy model to: (na przykładzie FrameworkExtension czyli podstawowego bundle'a samego Symfony)
5.1. Wczytanie definicji serwisów/parameterów z plików YAML/XML z Resources/config przy pomocy Yaml/XmlFileLoader. Definicji serwisów, a nie samych serwisów!
5.2. Jakieś bardziej dynamiczne tworzenie/modyfikowanie definicji serwisów/parametrów na podstawie konfiguracji z app/config/config.yml
6. Uruchamiana jest metoda BundleInterface::build
7. Punkty 4 - 6 uruchamiane są dla każdego bundle'a.
8. W czasie budowania można właściwie w każdym momencie (w praktyce jest to punkt 6.) dodać ContainerBuilderowi obiekty, które jeszcze na samym końcu wykonają na nim jakieś prace (CompilerPass). Po co? Ponieważ są one wtedy wstanie pracować na "całości" (pamiętaj, że bundle uruchamiane są w kolejności ich rejestracji). Sztandarowym przykładem będzie tutaj przykładowo wybranie wszystkich definicji zawierających tag XYZ i dodanie im czegoś, albo użycie ich w jakieś formie.
9. Gdy już wszystko zostało przetworzone całość zapisywana jest w var/cache/prod/...container.php jako kod PHP, a właściwie potężna klasa z metodami zwracającymi serwisy, stąd nazwa "kompilacji kontenera". We wszystkich powyższych punktach pracowaliśmy na ContainerBuilderze, który zawierał jedynie definicje seriwsów, a na końcu tego procesu skończyliśmy z jednym plikiem zawierającym konkretny kod, który jest dopiero wykorzystywany przez framework (Dependency Injection Container / Service Contaienr).

W przypadku, gdy już mamy w cache kontener punkty 2 - 9 nie wykonują się - wszystko jest już gotowe/dostępne.
koszykarze
znakomicie, bardzo dziękuję.


(nie rozumiem punktu 8, ale wrócę do niego po przeczytaniu podrozdziału DI o CompilerPass)
(szkoda też, że w dokumentacji o DI ani o Service Container nie było linka do http://symfony.com/doc/2.8/cookbook/bundle...figuration.html )
lukaskolista
Cytat
8. W czasie budowania można właściwie w każdym momencie (w praktyce jest to punkt 6.) dodać ContainerBuilderowi obiekty, które jeszcze na samym końcu wykonają na nim jakieś prace (CompilerPass). Po co? Ponieważ są one wtedy wstanie pracować na "całości" (pamiętaj, że bundle uruchamiane są w kolejności ich rejestracji). Sztandarowym przykładem będzie tutaj przykładowo wybranie wszystkich definicji zawierających tag XYZ i dodanie im czegoś, albo użycie ich w jakieś formie.

Cytat
(nie rozumiem punktu 8, ale wrócę do niego po przeczytaniu podrozdziału DI o CompilerPass)

W rozdziale o CompilerPass nic konkretnego nie znajdziesz, bo służy on do budowania relacji między usługami różnych bundli.

Pozwolę sobie wyjaśnić punkt 8:
Moc Symfony drzemie między innymi w bardzo dobrym Service Containerze (kontenerze usług). Za punkt wyjściowy weźmy sobie dowolny punkt działania aplikacji, w którym uruchamiana jest jej logika, np. akcję indexAction kontrolera UserControler (która powinna wyświetlić listę użytkowników). Jak można się domyślić zanim skrypt dojdzie do wykonania tej akcji, musi wcześniej wykonać dużo innych operacji związanych np. z routingiem i samym uruchomieniem środowiska frameworka. Skoro usługi w kontenerze definiujesz na podstawie konfiguracji, to gdzieś muszą być tworzone i do tego służy właśnie ContainerBuilder. Wygląda to mniej więcej tak:
1. Rozszerzenia bundli (extensiony) importują konfigurację.
2. Na podstawie konfiguracji budowane są definicje obiektów, np. $definition = new Definition($class);
3. Utworzone definicje obiektów (które technicznie same są obiektami) dodaje się do ContainerBuilder tak: $container->setDefinition('nazwa_definicji', $definition);
4. Możliwe jest tworzenie zależności między usługami poprzez referencje, np. new Reference('id_uslugi')
5. Jak już w extensionach wszystko zostanie zdefiniowane, to compiler przechodzi do compilerPass, który jak wspomniałem pozwala budować zależności między usługami różnych bundli, czego w extensionach się nie robi (chyba się nawet nie da, ale nawet nie próbowałem nigdy).
6. Jak już wszystkie definicje usług i relacje między nimi zostaną zdefiniowane, to wtedy framework przystępuje do faktycznego tworzenia obiektów usług i dopiero wtedy na podstawie wcześniej utworzonych definicji tworzy fizyczne obiekty tych usług.

Co daje powyższe? Praktycznie nieograniczone możliwości tworzenia konfigurowalnych usług. Często dochodzi do przesady pod tym kątem, bo ludzie próbują stworzyć zupełnie w ich aplikacji niepotrzebne możliwości konfiguracyjne, które nigdy nie są wykorzystywane. Mam na myśli pakiety wewnętrzne (jak wspomniał Crozin technicznie są identyczne co zewnętrzne, podział jest czysto merytoryczny), które są wykorzystywane tylko w jednaj, konkretnej aplikacji i nigdy nie zostaną przeniesione do innej np. z uwagi na to, że implementują konkretną logikę biznesową, która w innych aplikacjach jest zupełnie nieprzydatna.

Edit:
Dla bardziej szczegółowego wyjaśnienia:
  1. $loader->load('services.xml');
Powyższy kod buduje definicje usług na podstawie tagów <service> z pliku XML, natomiast to, co opisałem powyżej może znajdować się po wykonaniu ->load() i tam możesz sobie zbudować dowolne usługi na podstawie tablicy konfiguracji czy czegokolwiek innego, do czego jest dostęp w rozszerzeniu bundla.
koszykarze
fantastycznie. Bardzo pomocne.


===========
EDIT:
===========
mam kilka wątpliwości, ale możecie je zlekceważyć, bo chyba łatwo znajdę odpowiedzi. Mimo wszystko je tu zapiszę i usunę post od razu, gdy znajdę odpowiedzi. Głośno myślę. (będę edytował ten post)

(nie cytuję dosłownie)

1. -Crozin pisał "nie ma czegoś takiego jak bundle wewnętrzy i wyodrębniony, wszystkie działają tak samo".

No, ale jednak dla bundle stworzonego przeze mnie, pliki konfiguracyjne tj. serwisy z resources/config/services.yml są importowane w app/config dzięki czemu serwisy są widoczne w całym projekcie.

Natomiast 3rd party bundle nie mają dostępu do mojego app/config stąd developer bundle'a musi niejako od swojej strony zadbać o to, by serwisy z tego bundle były po przeniesieniu widoczny w projekcie użytkownika, dlatego w Extension trzeba rejestrować serwisy. W zasadzie, gdyby ktoś nie chciał dawać możliwości konfiguracji bundle'a to nie musiałby tworzyć Extension, wystarczyłoby importować serwisy w app/config. (hmmm acha i chyba to miał na myśli crozin mówiąc, że bundle się nie różnią)


2. Czy te frazy znaczą to samo w kontekście miejsca gdzie definiuję usługę np plik service.yml?
[configuration files][service configuration][container configuration]

(ja ciągle pisząc, myśląc o plikach konfiguracyjnych, tak wynikało dla mnie z podręcznika, myślałem o definicjach serwisów w services.yml z dyrektywami service, parameters i to wszystko. Nie wiedziałem, że tu też chodzi o dynamiczne opcje dla bundle jako parametr w metodzie load w Extension.)

3. "W klasach Extension można w zależności od ustawien w plikach konfiguracyjnych bundle'a tworzyć, modyfikować definicje serwisów"

A co mi da dynamiczna zmiana definicji serwisu skoro i tak muszę ręcznie zmienić klasę na której opiera się serwis? Co mi da dynamiczne dodanie argumentu konstruktora serwisu, skoro i tak ręcznie muszę dopisać ten argument w klasie ?

- "Jak już wszystkie definicje usług i relacje między nimi zostaną zdefiniowane, to wtedy framework przystępuje do faktycznego tworzenia obiektów usług "

framework tworzy obiekty na podstawie definicji?

- no tak co się dziwisz. Obiekty, nie klasy.

acha. Nie no czekaj, a zawartość metod tych serwisów? Je też można zdefiniować w Extension?

-czytaj dalej dokumentację

4. ........
lukaskolista
3rd psrt bundle mają dostęp do całego Twojego projektu.
Konfigurację wewnętrznych pakietów też możesz trzymać właśnie w tych pakietach dokładnie tak samo jak w przypadku 3rd part bundle - ja tak robię i przy dużej ilości zdefiniowanych usług jest to bardzo dobre rozwiązanie.
koszykarze
jakby się komuś chciało skomentować punkt 3 z poprzedniego postu.

edit:
nie mogę go edytować więc napiszę tutaj po ludzku o co mi chodzi.


3. "W klasach Extension można w zależności od ustawien w plikach konfiguracyjnych bundle'a tworzyć, modyfikować definicje serwisów"

"Jak już wszystkie definicje usług i relacje między nimi zostaną zdefiniowane, to wtedy framework przystępuje do faktycznego tworzenia obiektów usług "

- Nie rozumiem co mi da dynamiczna zmiana definicji serwisu skoro i tak muszę ręcznie zmienić klasę, na której opiera się serwis? Co mi da np dynamiczne dodanie argumentu konstruktora serwisu, skoro i tak ręcznie muszę dopisać ten argument w klasie ? (W klasie Container w cache są tylko tworzone obiekty. Czyli klasy muszę ręcznie modyfikować.)
lukaskolista
Cytat
- Nie rozumiem co mi da dynamiczna zmiana definicji serwisu skoro i tak muszę ręcznie zmienić klasę, na której opiera się serwis? Co mi da np dynamiczne dodanie argumentu konstruktora serwisu, skoro i tak ręcznie muszę dopisać ten argument w klasie ? (W klasie Container w cache są tylko tworzone obiekty. Czyli klasy muszę ręcznie modyfikować.)
Odniosę się do tej wypowiedzi.

Cytat
- Nie rozumiem co mi da dynamiczna zmiana definicji serwisu skoro i tak muszę ręcznie zmienić klasę, na której opiera się serwis?

Zmień sposób myślenia. Nie myśl implementacją, tylko interfacem/abstrakcją. Jak masz usługę np. security, która korzysta z klasy komponentu symfony, to sobie możesz w swoim pakiecie o nazwie np. SecurityExtra, a konkretnie w compilePass() pobrać definicję ten usługi i zmienić jej klasę na swoją, byle by się interface zgadzał. Możesz też udekorować taką usługę, bardziej szczegółoto jest to opisane tutaj: http://symfony.com/doc/current/components/...rating-services
Po wykonaniu powyższych manewrów klasy, które korzystają z tej usługi nie są świadome zmian, jakie zaszły, bo interface się zgadza a Ty jesteś zadowolony z dodania nowej funkcjonalności do tej usługi. Ma to zastosowanie zwłaszcza w gotowych już projektach, gdzie trzeba bez ruszania kodu aplikacji coś pozmieniać, pododawać.

Cytat
Co mi da np dynamiczne dodanie argumentu konstruktora serwisu, skoro i tak ręcznie muszę dopisać ten argument w klasie
Tak, jak powyżej opisałem. Nadpisując definicję usługi możesz podać swoją klasę, która może mieć zupełnie inny konstruktor (o ile interface go nie definiuje). Poza tym budowa kontenera usług i definicji nie ogranicza się do konstruktora, jest to dużo bardziej zaawansowany twór, możesz np. napisać pakiet, który taguje zdefiniowane w konfiguracji usługi odpowiednimi tagami (do tagowania usług pewnie dojdziesz, taguje się np. event listenery/subscribery aby były rozpoznawane przez framework jako właśnie listenery/subscribery).

Cytat
(W klasie Container w cache są tylko tworzone obiekty. Czyli klasy muszę ręcznie modyfikować.)
Ale możesz zrobić opcjonalne argumenty, które w zależności od konfiguracji są podawane albo i nie. Możesz dodać wywołania metod (przykład z dokumentacji kontenera usług, chodzi o opcję "calls"):
  1. app.newsletter_manager:
  2. class: AppBundle\Newsletter\NewsletterManager
  3. calls:
  4. - [setMailer, ['@app.mailer']]
koszykarze
Dzięki, świetnie wytłumaczone!

(kiepsko wkleiłeś link, wrzucam poprawny bo będę tu zaglądał w przyszłościDecorating Services)


Wszystko jest super, szkoda tylko, że to nie wynika wprost z dokumentacji symfony. Podstawowa dokumentacja pisana jak dla opóźnionych w rozwoju a dokumentacja komponentów to już tak nie koniecznie. W ogóle jakby mi się wyszukiwarki internetowe popsuły, jakby nikt nie używał, albo nie miał nigdy problemów np z hasłem "symfony decorating services".

lukaskolista
Ja tam patrzę w źródła smile.gif Symfony jest dobrze napisane i da się zrozumieć kod bez większych problemów. Przykładowo całe DI w rozszerzeniach podpatrzyłem z FrameworkBundle, budowniczy konfiguracji też jest kiepsko udokumentowany, prawdziwa zabawa zaczyna się, jak sobie spojrzysz w gotowe pakiety - nagle okazuje się, że nie umiesz napisać dobrego budowniczego konfiguracji + można zajrzeć w sam builder.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.