Magento 2: Proxy
Proxy (znany również jako pośrednik, pełnomocnik) jest strukturalnym wzorcem projektowym. Pozwala stworzyć obiekt zastępczy w miejsce innego obiektu. Wykorzystujemy go, aby nadzorował dostęp do pierwotnego obiektu, aby w odpowiednim czasie zainicjalizować go i wykonać pewne czynności. Przydatne w szczególności, gdy dany obiekt potrzebuje dużo zasobów, jak i również kiedy ich inicjalizacja jest czasochłonna.
Rodzaje i zastosowanie wzorca Proxy
Sam wzorzec Proxy, ma ogólnie szersze zastosowanie niż tylko „leniwą inicjalizacje”, pokrótce wymienię istniejące rodzaje (źródło: https://refactoring.guru/):
- Wirtualny pełnomocnik — leniwa inicjalizacja, gdy mamy do czynienia z zasobożernym obiektem usługi.
- Pełnomocnik ochronny — odpowiada za kontrolę dostępu, kiedy potrzebujemy ograniczyć niektórym klientom dostęp do określonych usług.
- Pełnomocnik zdalny — odpowiada za uruchamianie zdalnej usługi, kiedy obiekt udostępniający usługi znajduje się na zdalnym serwerze.
- Pełnomocnik dziennika żądań — umożliwia prowadzenie dziennik żądań, pozwala rejestrować rejestr żądań dla nadzorowanej usługi.
- Pełnomocnik z pamięcią podręczną — przechowuje informację o wynikach określonych żądań
i zarządza pamięcią podręczną. Przydatne, gdy dane żądania zwracają ten sam wynik. Można wykorzystać parametry żądania w formie kluczy identyfikujących odpowiedni obszar pamięci podręcznej.
Jeśli chodzi o zastosowanie, to możliwe jest wykorzystanie go do sprytnych referencji. Możemy opóźniać tworzenie zasobnożernych obiektów usług, ale także wykorzystać go do usuwania takich obiektów, gdy kod kliencki go nie potrzebuje. Dzięki temu możemy zwolnić zasoby systemowe.
W celu zaimplementowania wzorca proxy potrzebujemy stworzyć dla danej klasy interfejs (jeśli takowy nie istnieje). Jeśli stworzenie interfejsu wymagałoby dużych zmian w kodzie, klasa Proxy może po prostu dziedziczyć po oryginalnej klasie. Sama klasa Proxy powinna posiadać odniesienia do obiektu, który nadzoruje. Następnie potrzebujemy zaimplementować poszczególne metody zgodnie z ich przeznaczeniem i gdy istnieje taka potrzeba, oddelegować wykonanie zadania bezpośrednio do obiektu usługi. Bardzo często potrzebne jest również napisanie metody kreacyjnej — czy to pod postacią prostej metody statycznej, czy też w formie metody wytwórczej, która zainicjalizuje i zwróci nam obiekt serwisu.
Wykorzystanie Proxy w Magento 2
W Magento 2 proxy to jeden z ważniejszych wzorców projektowych i funkcji, który można wykorzystać do poprawy wydajności i zmniejszenia zasobów. Samo Magento pozwala na automatyczne wygenerowanie takiej klasy proxy, więc nie musimy go pisać samodzielnie, a sama klasa Proxy jest dostępna przez odpowiednią referencję.
Rozpatrzy prosty przykład klasy, która jako argument przyjmuje model sesji dla obiektu customer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php namespace My\Module\Model; use Magento\Customer\Model\Session; class SomeClass { /** * @var Session */ private $customerSession; /** * SomeClass constructor. * @param Session $customerSession */ public function __construct( Session $customerSession ) { $this->customerSession = $customerSession; } |
Dla klasy Magento\Customer\Model\Session odniesienie do jej wygenerowanej wersji Proxy będzie następujące: Magento\Customer\Model\Session\Proxy.
Potrzebujemy dodać konfigurację w pliku di.xml, aby móc wykorzystać wersję Proxy klasy sesji.
1 2 3 4 5 6 7 |
<type name="My\Module\Model\SomeClass"> <arguments> <argument name="customerSession" xsi:type="object"> Magento\Customer\Model\Session\Proxy </argument> </arguments> </type> |
Dla argumentu $customerSession konstruktora klasy SomeClass ładujemy klasę Magento\Customer\Model\Session\Proxy w miejsce normalnego modelu sesji. Dlaczego w ten sposób? Podanie jej bezpośrednio do konstruktora zlimitowałoby możliwości do rozszerzenia klasy. Dla klas proxy nie możemy tworzyć pluginów.
Jeśli pracujemy na Magento na trybie production, potrzebujemy wygenerować ręcznie takie pliki poprzez użycie komendy:
1 |
bin/magento setup:di:compile |
Wspomniana klasa Proxy dla modelu sesji customera zostaje wygenerowana w: generated/code/Magento/Customer/Model/Session/Proxy.php.
PHPStorm wygeneruje nam taki oto diagram dla wspomnianej klasy:
Przegląd wygenerowanej klasy Proxy dla modelu sesji customera
Przyjrzyjmy się bliżej wygenerowanej klasie Magento\Customer\Model\Session\Proxy, której kod wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
<?php namespace Magento\Customer\Model\Session; /** * Proxy class for @see \Magento\Customer\Model\Session */ class Proxy extends \Magento\Customer\Model\Session implements \Magento\Framework\ObjectManager\NoninterceptableInterface { /** * Object Manager instance * * @var \Magento\Framework\ObjectManagerInterface */ protected $_objectManager = null; /** * Proxied instance name * * @var string */ protected $_instanceName = null; /** * Proxied instance * * @var \Magento\Customer\Model\Session */ protected $_subject = null; /** * Instance shareability flag * * @var bool */ protected $_isShared = null; /** * Proxy constructor * * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param string $instanceName * @param bool $shared */ public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Magento\\Customer\\Model\\Session', $shared = true) { $this->_objectManager = $objectManager; $this->_instanceName = $instanceName; $this->_isShared = $shared; } /** * @return array */ public function __sleep() { return ['_subject', '_isShared', '_instanceName']; } /** * Retrieve ObjectManager from global scope */ public function __wakeup() { $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance(); } /** * Clone proxied instance */ public function __clone() { $this->_subject = clone $this->_getSubject(); } /** * Get proxied instance * * @return \Magento\Customer\Model\Session */ protected function _getSubject() { if (!$this->_subject) { $this->_subject = true === $this->_isShared ? $this->_objectManager->get($this->_instanceName) : $this->_objectManager->create($this->_instanceName); } return $this->_subject; } /** * {@inheritdoc} */ public function getCustomerConfigShare() { return $this->_getSubject()->getCustomerConfigShare(); } /** * {@inheritdoc} */ public function setCustomerData(\Magento\Customer\Api\Data\CustomerInterface $customer) { return $this->_getSubject()->setCustomerData($customer); } /** * {@inheritdoc} */ public function getCustomerData() { return $this->_getSubject()->getCustomerData(); } /** * {@inheritdoc} */ public function getCustomerDataObject() { return $this->_getSubject()->getCustomerDataObject(); } /** * {@inheritdoc} */ public function setCustomerDataObject(\Magento\Customer\Api\Data\CustomerInterface $customerData) { return $this->_getSubject()->setCustomerDataObject($customerData); } /** * {@inheritdoc} */ public function setCustomer(\Magento\Customer\Model\Customer $customerModel) { return $this->_getSubject()->setCustomer($customerModel); } /** * {@inheritdoc} */ public function getCustomer() { return $this->_getSubject()->getCustomer(); } /** * {@inheritdoc} */ public function setCustomerId($id) { return $this->_getSubject()->setCustomerId($id); } /** * {@inheritdoc} */ public function getCustomerId() { return $this->_getSubject()->getCustomerId(); } /** * {@inheritdoc} */ public function getId() { return $this->_getSubject()->getId(); } /** * {@inheritdoc} */ public function setId($customerId) { return $this->_getSubject()->setId($customerId); } /** * {@inheritdoc} */ public function setCustomerGroupId($id) { return $this->_getSubject()->setCustomerGroupId($id); } /** * {@inheritdoc} */ public function getCustomerGroupId() { return $this->_getSubject()->getCustomerGroupId(); } /** * {@inheritdoc} */ public function isLoggedIn() { return $this->_getSubject()->isLoggedIn(); } /** * {@inheritdoc} */ public function checkCustomerId($customerId) { return $this->_getSubject()->checkCustomerId($customerId); } /** * {@inheritdoc} */ public function setCustomerAsLoggedIn($customer) { return $this->_getSubject()->setCustomerAsLoggedIn($customer); } /** * {@inheritdoc} */ public function setCustomerDataAsLoggedIn($customer) { return $this->_getSubject()->setCustomerDataAsLoggedIn($customer); } /** * {@inheritdoc} */ public function loginById($customerId) { return $this->_getSubject()->loginById($customerId); } /** * {@inheritdoc} */ public function logout() { return $this->_getSubject()->logout(); } /** * {@inheritdoc} */ public function authenticate($loginUrl = null) { return $this->_getSubject()->authenticate($loginUrl); } /** * {@inheritdoc} */ public function setBeforeAuthUrl($url) { return $this->_getSubject()->setBeforeAuthUrl($url); } /** * {@inheritdoc} */ public function setAfterAuthUrl($url) { return $this->_getSubject()->setAfterAuthUrl($url); } /** * {@inheritdoc} */ public function regenerateId() { return $this->_getSubject()->regenerateId(); } /** * {@inheritdoc} */ public function writeClose() { return $this->_getSubject()->writeClose(); } /** * {@inheritdoc} */ public function __call($method, $args) { return $this->_getSubject()->__call($method, $args); } /** * {@inheritdoc} */ public function start() { return $this->_getSubject()->start(); } /** * {@inheritdoc} */ public function isSessionExists() { return $this->_getSubject()->isSessionExists(); } /** * {@inheritdoc} */ public function getData($key = '', $clear = false) { return $this->_getSubject()->getData($key, $clear); } /** * {@inheritdoc} */ public function getSessionId() { return $this->_getSubject()->getSessionId(); } /** * {@inheritdoc} */ public function getName() { return $this->_getSubject()->getName(); } /** * {@inheritdoc} */ public function setName($name) { return $this->_getSubject()->setName($name); } /** * {@inheritdoc} */ public function destroy(?array $options = null) { return $this->_getSubject()->destroy($options); } /** * {@inheritdoc} */ public function clearStorage() { return $this->_getSubject()->clearStorage(); } /** * {@inheritdoc} */ public function getCookieDomain() { return $this->_getSubject()->getCookieDomain(); } /** * {@inheritdoc} */ public function getCookiePath() { return $this->_getSubject()->getCookiePath(); } /** * {@inheritdoc} */ public function getCookieLifetime() { return $this->_getSubject()->getCookieLifetime(); } /** * {@inheritdoc} */ public function setSessionId($sessionId) { return $this->_getSubject()->setSessionId($sessionId); } /** * {@inheritdoc} */ public function getSessionIdForHost($urlHost) { return $this->_getSubject()->getSessionIdForHost($urlHost); } /** * {@inheritdoc} */ public function isValidForHost($host) { return $this->_getSubject()->isValidForHost($host); } /** * {@inheritdoc} */ public function isValidForPath($path) { return $this->_getSubject()->isValidForPath($path); } /** * {@inheritdoc} */ public function expireSessionCookie() { return $this->_getSubject()->expireSessionCookie(); } } |
Klasa Proxy rozszerza daną klasę, aby stać się ich leniwą wersją (ang. lazy loaded). Działa to tak, że prawdziwą instancję klasy, którą klasa Proxy rozszerza, tworzona jest tylko po tym jak jedna z metod klasy jest wywoływana. Zadanie to wykonuje metoda _getSubject(). Wygenerowana klasa Proxy rozszerza oryginalną klasę. Daje nam to możliwość użycia tej klasy jako zależność wszędzie tam, gdzie wykorzystywana jest oryginalna klasa. W przeciwieństwie do rodzica klasa Proxy ma tylko jedną wstrzykiwaną zależność klasę Object Managera.
Zwróć uwagę, że dana klasa Proxy powinna implementować interfejs Magento\Framework\ObjectManager\NoninterceptableInterface, w ten sposób Magento nie będzie generowało klas Interception. W związku z tym nie będzie możliwe podpiąć się do publicznych metod tej klasy, dlatego nie jest możliwe stworzenie dla niej pluginu.
Inne przykłady klas Proxy w Magento:
Magento\Framework\App\Cache\Proxy – kod,
Magento\Framework\App\AreaList\Proxy – kod.
Są to klasy ręcznie stworzone, ale mają podobną implementację do tych automatycznie generowanych.
Magento 2: manipulowanie wstrzykiwanymi zależnościami do klas za pomocą type i virtualType - Web Programming
31 października 2023 @ 01:23
[…] Stosowany również w przypadku, gdy w wybranej klasie chcemy podmienić zależność na klasę typu proxy: […]
Magento 2: Zdarzenia i obserwatory - Web Programming
4 listopada 2023 @ 01:43
[…] Jest to klasa wygenerowana przez Magento, o tym czy jest proxy zostało omówione w tej notce. Właściwa klasa, która zawieraj zaimplementowaną metodę disptach() to […]
Magento 2 tryby działania aplikacji - Web Programming
20 grudnia 2023 @ 22:46
[…] Generowanie kodu aplikacji (klas fabryk i proxy), […]
Pluginy w Magento 2 - Web Programming
13 stycznia 2024 @ 13:46
[…] Klasy implementujące interfejs MagentoFrameworkObjectManagerNoninterceptableInterface. Interfejs ten głównie implementują klasy proxy. […]
Magento 2: Object Manager - Web Programming
3 kwietnia 2024 @ 20:34
[…] Object Manager może być zależnością w klasach służących do tworzenia obiektów takich jak fabryki czy proxy, […]