Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Przechwycenie momentu utworzenia zmiennej
Forum PHP.pl > Forum > PHP > Object-oriented programming
CuteOne
Witam,

na wstępie trochę kodu

1.
  1.  
  2. class A {
  3.  
  4. public function xyz() {
  5.  
  6. $this->a = 'aaa';
  7. }
  8. }
  9.  
  10. $a = new A();
  11. $a -> xyz();


2.
  1.  
  2. class A {
  3.  
  4. public $a='bbb';
  5.  
  6. public function xyz() {
  7.  
  8. $this->a = 'aaa';
  9. }
  10. }
  11.  
  12. $a = new A();
  13. $a -> xyz();

i pytanie - czy istnieje, możliwość przechwycenia z zewnątrz, momentu zainicjowania(1) $this->a lub zmiany jego wartości(2)?

EDIT: __set() nie wchodzi w grę
Crozin
Utwórz setter, który tuż po nadaniu wartości utworzy zdarzenie, które poinformuje o zmianie wartości wszystkich subskrybentów. Google: php observator/event dispatcher.
CuteOne
Właśnie chciałem uniknąć obserwatora ;P ale wydaje mi się, że ten sposób będzie najlepszy. Jeszcze jedno pytanie jeśli, można - co w przypadku zmiennych lokalnych danej metody:
  1. class A {
  2.  
  3. public function xyz() {
  4.  
  5. $a = 'aaa'; //notify
  6. $a = 'bbb'; //notify
  7. }
  8. }


Kombinowałem z closures http://www.php.net/manual/en/functions.anonymous.php#95124 ale wtedy dana zmienna staje się zależna od wartości zewnętrznych
Crozin
Dlaczego chciałeś uniknąć obserwatora czy event dispatchera?
Cytat
Jeszcze jedno pytanie jeśli, można - co w przypadku zmiennych lokalnych danej metody
Najzwyczajniej w świecie odczep się od nich. wink.gif Zmienne lokalne danej metody są na wyłączny jej użytek i nie powinny mieć wpływu na cokolwiek poza wnętrzem metody.

Opisz może co jest faktycznym problemem, bo wygląda na to, że mamy tutaj do czynienia z problemem XY.
CuteOne
Tworzę system pluginów na bazie Zenda. Z tą różnicą, że pluginem, może być nie tylko klasa rozszerzająca "Zend_Plugin_Abstract" ale cały moduł z kontrolerami, modelami i wszystkim tym co zawiera standardowy moduł + własne pluginy(rozszerzające Zend_Plugin_Abstract). Innymi słowy moduły mogą być rozszerzane przez inne moduły(i ich pluginy), które mogą być dalej rozszerzane i tak w nieskończoność...


Uproszczony przykład dwóch niezależnych od siebie modułów - "pseudopluginów"
  1. class Comments_IndexController extends Zend_Controller_Action {
  2.  
  3. public function viewAction() {
  4.  
  5. $id = $this->_getParam('id');
  6.  
  7. $model = new Comments_Model_XYZ();
  8. $row = $model -> find($id);
  9.  
  10. $this->view->comment = $row;
  11. }
  12. }
  13.  
  14.  
  15. class BBCode_ParseController extends Zend_Controller_Action {
  16.  
  17. public function view($comment) {
  18.  
  19. $this->view->comment = new BBCode_Model_Prepare($comment);
  20. }
  21.  
  22. public function strip($row) {
  23.  
  24. return explode('#', strip_tags(implode('#', $row)));
  25. }
  26. }


oraz ich konfiguracja.
list - lista zainstalowanych pluginów,
action - definicja zależności pomiędzy pluginami
  1. {
  2. "list": [ // lista pluginów/modułów
  3. {
  4. "name": "Comments",
  5. "version": "1.00",
  6. "weight": 8,
  7. "active": true
  8. },
  9. {
  10. "name": "BBCode",
  11. "version": "1.00",
  12. "weight": 7,
  13. "active": true,
  14. "depend": [
  15. {"name": "Comments", "version": ">=1.00"}
  16. ]
  17. }
  18. ],
  19. "action": [ // preDispatch i postDispatch - przed odpaleniem metody i po odpaleniu metody
  20. {
  21. "use": "BBCode",
  22. "hooks": [
  23. // odbierz zwrócone dane z Comments->view i 'wepchnij' do BBCode->view jako parametr
  24. {"hook_method": "BBCode->view after Comments->view", "hook_var": "[@return]"},
  25. // odbierz $row po jego zainicjowaniu i wepchnij do BBCode->strip jako parametr
  26. {"hook_method": "BBCode->strip around Comments->view", "hook_var": "[@var $row]"}
  27. ]
  28. }
  29. ]
  30. }


Dla zwiększenia, możliwość ingerowania w dane i funkcjonalność, danej metody zastosowałem pseudo AOP (before, after, around). Before i after mam już rozwiązane, problem to around czyli możliwość ingerencji w metodę podczas jej wykonywania np. w zmienną $row przed lub po jej zainicjowaniu.
Crozin
Nawet AOP nie zakłada takiego pogwałcenia kolejności wykonywania kodu jak ingerencja we wnętrze jakiejś metody.

Według mnie powinieneś tutaj skorzystać z event dispatchera i tworzyć bardzo dużo zdarzeń, gdzie część z nich będzie miała możliwość modyfikowania obiektu, którego dane zdarzenie dotyczy. Dzięki temu będziesz mógł bez problemu podpiąć np. BBCode* przez proste podpięcie go pod odpowiednie zdarzenia w systemie.

* zlituj się nad swoimi użytkownikami i nie katuj ich tym paskudnym i niewygodnym formatem.
CuteOne
Zrobiłem małą inwentaryzację swoich pomysłów "jak pluginować pluginy" i doszedłem do kilku wniosków:
- pisanie nowych pluginów musi być równie proste jak pisanie zwykłych modułów (ameryki nie odkryłem ale to chyba będzie największe wyzwanie przy pisaniu systemu)
- każdy kontroler, model czy widok musi posiadać odgórnie wyznaczone miejsca dostępu (hooki) dla danych wrażliwych(widać bez obserwatora się nie obejdzie wink.gif )
- każdy moduł(pseudopligin) musi posiadać własne API, dzięki czemu możliwe będzie sterowanie modułem przy użyciu innego modułu. Nad tą kwestią muszę się jeszcze zastanowić - co, jak i gdzie smile.gif
- idąc Twoją radą zrezygnuje z możliwości manipulowania zmiennymi danych metod.
- rozszerzanie pluginu przez inny plugin (tak jak w akcji Comments->viewAction() => BBCode->view()), będzie możliwe poprzez:
1. Nadpisanie metody
2. Hooki
3. Before i After
4. Za pomocą serializacji obiektu i zmianach struktury przed jego deserializacją (do przemyślenia)
5. Za pomocą API

Zostało jeszcze parę(naście) kwestii do przemyślenia ale to już pierdółki. Teraz zastanawiam się w jaki sposób uaktualniać już zainstalowane pluginy - wsteczna kompatybilność i te sprawy. Gdyby istniała, możliwość dziedziczenia metod rodzica przez dziecko bez ingerencji w kod..

Co do BBCode to tylko przykład smile.gif wybrałem go bo najlepiej obrazuje w jaki sposób jeden plugin, może ingerować w dane innego pluginu.

Pozdrawiam i dziękuję za Twoje rady

Edit: co do Event Dispatchera to nie mam na jego temat wystarczającej wiedzy abym mógł napisać "profesjonalny" system - pracuje na Zend 1.x.x, może za jakiś czas zagłębie się w niego dokładniej gdy będę przechodził na Zenda 2.
Crozin
Nie do końca rozumiem co dokładnie chcesz stworzyć (to chyba przez to, że słowa plugin/moduł mają po 1000 znaczeń :]) ale...
Cytat
każdy kontroler, model czy widok musi posiadać odgórnie wyznaczone miejsca dostępu (hooki) dla danych wrażliwych(widać bez obserwatora się nie obejdzie )
Odgórne narzucanie czegokolwiek lubi się mścić.
Cytat
- rozszerzanie pluginu przez inny plugin (tak jak w akcji Comments->viewAction() => BBCode->view()), będzie możliwe poprzez:
1. Nadpisanie metody
2. Hooki
3. Before i After
4. Za pomocą serializacji obiektu i zmianach struktury przed jego deserializacją (do przemyślenia)
5. Za pomocą API
1. Nadpisywanie niemal zawsze jest najgorszym sposobem rozszerzania o dodatkowe, potencjalnie niepowiązane funkcje.
4. Nawet się nie waż takich głupot robić.
5. Nie rozumiem co masz na myśli tutaj przez API (znowu, 1000 znaczeń tego słowa).

Zadbaj o to by w systemie było dużo zdarzeń, przykładowo:
  1. $comment = /* pobierz obiekt komentarza, np. z formularza */;
  2.  
  3. $event= new GenericPrePersistEvent($comment);
  4. $eventDispatcher->dispatch('jakas.unikalna.nazwa.zdarzenia.np.comment.pre_persist', $event);
  5.  
  6. $comment = $event->getObject(); // zwraca obiekt, który mógł zostać zmodyfikowany (np. treść mogła zostać potraktowana parserem BBCode)
  7.  
  8. /* zapisz obiekt $comment w bazie danych */
  9.  
  10. $event = new GenericPostPersistEvent($comment);
  11. $eventDispatcher->dispatch('comment.post_persist', $event);
Takie proste rozwiązanie, powinno sprawdzić się w zdecydowanej większości przypadków. Nie tworzy ono też żadnych powiązań pomiędzy "głównym kodem" a pluginami.

Cytat
Teraz zastanawiam się w jaki sposób uaktualniać już zainstalowane pluginy - wsteczna kompatybilność i te sprawy. Gdyby istniała, możliwość dziedziczenia metod rodzica przez dziecko bez ingerencji w kod..
W zależności od tego, jak solidne/rozbudowane narzędzie chcesz uzyskać, a: oprzyj to o zwykły menadżer zależności (np. PHP-owski Composer), b: nie mam pojęcia czy istnieje jakiś odpowiednik w PHP (pewnie nie), ale pogoogleaj za Java OSGi - bardziej za architekturą i sposobie działania.

Cytat
Edit: co do Event Dispatchera to nie mam na jego temat wystarczającej wiedzy abym mógł napisać "profesjonalny" system - pracuje na Zend 1.x.x, może za jakiś czas zagłębie się w niego dokładniej gdy będę przechodził na Zenda 2.
A po co niby pisać samemu coś co już jest? Ot, chociażby ten dostępny w Symfony: https://github.com/symfony/EventDispatcher
CuteOne
Cytat
Nie do końca rozumiem co dokładnie chcesz stworzyć (to chyba przez to, że słowa plugin/moduł mają po 1000 znaczeń :]) ale...

Musze zaprojektować system ERP a właściwie podstawkę, którą można dowolnie rozszerzać. Taki system z reguły składa się z kilkunastu niezależnych od siebie elementów np. HR, work flow, crm. Elementy te są na tyle "duże", że zastosowanie zwykłego systemu pluginów odpada, dlatego pomyślałem aby pluginem mógł być pojedynczy moduł np.
Kod
modules
   default
      |-controllers
      |-models
      |-views
   hr
      |-controllers
      |-models
      |-views
      |-plugins
   crm
      |-controllers
      |-models
      |-views


Na etapie projektowania tak rozbudowanego systemu nie jestem w stanie przewidzieć zależności pomiędzy poszczególnymi modułami np. Work Flow, może być użyty w CRM'ie, statystyki sprzedaży z CRM'a mogą być użyte w HR do generowania raportów, raporty z HR mogą być wykorzystane przez Work Flow itd. Sprawę dodatkowo komplikuje fakt, że moduły będą pisane przez zupełnie nie znanych mi programistów a więc system musi być przejrzysty, intuicyjny i przede wszystkim na tyle "łatwy" aby pisanie dodatkowych modułów nie była przeprawą rzez piekło.

Cytat
Odgórne narzucanie czegokolwiek lubi się mścić.

Pewnie tak smile.gif ale jakoś muszę udostępnić miejsca, w których klasa A będzie mogła zmienić model w klasie B bez ingerencji w kod (jeden z wymogów projektu..).

Cytat
1. Nadpisywanie niemal zawsze jest najgorszym sposobem rozszerzania o dodatkowe, potencjalnie niepowiązane funkcje.
4. Nawet się nie waż takich głupot robić.
5. Nie rozumiem co masz na myśli tutaj przez API (znowu, 1000 znaczeń tego słowa).

1. Teraz gdy zobaczyłem Twój przykład z dispatcherem to faktycznie nadpisywanie nie miało by sensu
4. Gdzieś widziałem przykład wykorzystania serializacji do zmian w strukturze klasy podczas aktualizacji i powiem ci, że byłem wniebowzięty takim podejściem do problemu ;P
5. API:
  1. class CRM {
  2.  
  3. public function view() {
  4.  
  5. $api_wf = new API_WorkFlow();
  6. $api_wf -> setId(1);
  7.  
  8. $raport = $api_wf -> getRaport() //-> generateCSV(); - dodatkowa metoda zmieniająca kontekst danych
  9. }
  10. }
  11.  
  12. class Model_WorkFlow {
  13.  
  14. public function getRaport() {
  15.  
  16. // zwraca obiekt raportu
  17. }
  18.  
  19. public function setName($name) {
  20.  
  21. $this->entity->name = $name;
  22. }
  23. }
  24.  
  25. class Plugin_WorkFlow {
  26.  
  27. public function generateCSV() {
  28.  
  29. // zwraca string w formacie csv
  30. }
  31. }
  32.  
  33. class API_WorkFlow {
  34.  
  35. public function setId($id) { $this->id = $id; }
  36.  
  37. public function getRaport($params=array()) {
  38.  
  39. //prosty przykład wykorzystania API
  40. if(isset($params['view']) && $param['view'] == 'json') {
  41.  
  42. return new Model_WorkFlow_JSON($this->id);
  43. }
  44. else {
  45.  
  46. $model = new Model_WorkFlow();
  47. return $model -> getRaport();
  48. }
  49. }
  50.  
  51. public function editName(Model_WorkFlow_RaportInterface $raport, $name) {
  52.  
  53. $model = new Model_WorkFlow($raport);
  54. $model -> setName($name);
  55. $model -> save();
  56. }
  57. }
  58.  
  59. $crm = new CRM();
  60. $crm -> view();

W ten sposób mamy dostęp do zdefiniowanych w API metod bez bezpośredniego wywoływania modeli, kontrolerów itp. z innych modułów.

Cytat
W zależności od tego, jak solidne/rozbudowane narzędzie chcesz uzyskać, a: oprzyj to o zwykły menadżer zależności (np. PHP-owski Composer), b: nie mam pojęcia czy istnieje jakiś odpowiednik w PHP (pewnie nie), ale pogoogleaj za Java OSGi - bardziej za architekturą i sposobie działania.

Pogooglam dzięki za wskazówkę
Crozin
Cytat
Pewnie tak ale jakoś muszę udostępnić miejsca, w których klasa A będzie mogła zmienić model w klasie B bez ingerencji w kod (jeden z wymogów projektu..).
Standardowe rozwiązanie tego problemu, które od lat się całkiem dobrze sprawdza, to IoC (zrealizowane np. przez DIC-a) + IDD. Jeżeli tylko swój własny kod będziesz pisał tak jakby był on przeznaczony jako tzw. 3rd-party-code, nie powinieneś mieć problemów - chociaż jest to bardziej czasochłonne, a kod czasami komplikuje się na dłuższą metę może przynieść to sporo oszczędności (czasu i pieniędzy).
Cytat
Gdzieś widziałem przykład wykorzystania serializacji do zmian w strukturze klasy podczas aktualizacji i powiem ci, że byłem wniebowzięty takim podejściem do problemu ;P
"Oryginalne" podejście do tematu niemal zawsze oznaczać będzie złe podejście.
CuteOne
Twoje rady jak zawsze trafiają w dziesiątkę! Dziękuje bardzo za pomoc, teraz muszę ponownie zrobić remanent swoich pomysłów - zajmie to pewnie z tydzień, dwa. W razie wątpliwości mam nadzieję, że użyczysz mi swojej fachowej wiedzy.

Jeszcze raz dziękuję i pozdrawiam


Edit:

Wstępny projekt architektury aplikacji już mam

Legenda:
- EventDispatcher pochodzi z Symfony2
- ED skrót od EventDispatchera
- Plugin Manager - rejestruje wszystkie akcje pluginów do ED
- Resource Manager - standardowy Zendowski loader (lekko zmodyfikowany)
- ViewAction - renderowanie widoku

Kolejność ładowania klas od lewej do prawej, z góry na dół.

Nadal jednak nie rozwiązałem problemu zmiany modeli ;P W teorii bez problemu można wstrzyknąć modelX, Y, Z ale przy rozbudowanych akcjach będzie to wyglądało dość komicznie
  1. $class = new IndexController();
  2. $class -> viewAction(
  3. new ModelA,
  4. new ModelB,
  5. new ModelC,
  6. new ModelD,
  7. new ModelE
  8. ),
  9. new ServiceA,
  10. new ServiceB,
  11. new ServiceC
  12. )
  13. );

Drugim problem jest brak kontroli nad wstrzykiwanymi modelami
  1. // Tak jest elegancko
  2. interface IndexInterface {
  3.  
  4. public function viewAction(ModelX $modelX, ModelY $modelY);
  5. }
  6.  
  7. // a tu nie bardzo :)
  8. interface IndexInterface {
  9.  
  10. public function viewAction(array $models, array $services);
  11. }



Dzisiaj poświęcę parę godzin nad studiowaniem wzorców, może znajdę rozwiązanie tego problemu
Crozin
Ale dlaczego chcesz dla danego obiektu wstrzykiwać inne obiekty przy pomocy tablicy, a nie jak sam pokazałeś, jako osobne parametry?
CuteOne
tongue.gif

W sumie to sam nie wiem. Ubzdurałem sobie aby wstrzykiwać modele pakietami
Kod
{
   "use": "pluginXYZ",
   "inject": [
        {"models":["X", "Y"]},
        {"services": ["xx", "yyy"]}
   ],

   //inny plugin, który chce skorzystać z pluginXYZ
   "use": "pluginXYZ",
   "inject": [
        {"models":["V", "C", "E", "H", "H2"]},
        {"services": ["xx", "yyy"]}
   ]
}

viewAction($pakiet_models, $pakiet_services);

Dopiero po rozpisaniu tego na kartce wyszły ewentualne niedopatrzenia, więc zrezygnowałem z tablic na rzecz kontenerów
Kod
{
   "use": "pluginXYZ",
   "inject": [
        {"models": "ModelX_Container"},
        {"services": "ServiceX_Container"}
   ],

   //inny plugin, który chce skorzystać z pluginXYZ
   "use": "pluginXYZ",
   "inject": [
        {"models": "ModelZZ_Container"},
        {"services": "ServiceZZ_Container"}
   ]
}

  1. public function viewAction($model_container, $service_container) {
  2.  
  3. //po interfejsie modelu
  4. if($model_container->has('ModelX_Interface')) {
  5. $modelX = $model_container -> get('ModelX_Interface');
  6. }
  7. //lub wszystkie danego typu (kolekcji)
  8. if($model_container->has('Models_Domain_Interface')) {
  9. $models = $model_container -> getDomain('Models_Domain_Interface');
  10. }
  11. //lub po nazwie modelu
  12. if($model_container->has('ModelX')) {
  13. $modelX = $model_container -> get('ModelX');
  14. }
  15. }
Crozin
1. Po co jakieś dziwne rozgraniczenie na modele i usługi? A jak będziesz chciał wstrzyknąć jeszcze jakiś zupełnie oderwany od nich obiekt? Zresztą usługi pochodzą najczęściej z warstwy modelu.
2. Ponownie - zamiast wymyślać własne rozwiązanie, skorzystaj z gotowego i na 99% lepszego rozwiązania, np. kontenera dostępnego w Symfony2
CuteOne
1. Tak jest skonstruowany Zend a nie mam za wiele czasu na tego typu mieszanie w kodzie
2. Coraz bardziej skłaniam się do nauki S2 i stosowania go w późniejszych aplikacjach..
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.