DependencySerializationTrait.php 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. <?php
  2. namespace Drupal\Core\DependencyInjection;
  3. use Drupal\Core\Entity\EntityStorageInterface;
  4. use Symfony\Component\DependencyInjection\ContainerInterface;
  5. /**
  6. * Provides dependency injection friendly methods for serialization.
  7. */
  8. trait DependencySerializationTrait {
  9. /**
  10. * An array of service IDs keyed by property name used for serialization.
  11. *
  12. * @var array
  13. */
  14. protected $_serviceIds = [];
  15. /**
  16. * An array of entity type IDs keyed by the property name of their storages.
  17. *
  18. * @var array
  19. */
  20. protected $_entityStorages = [];
  21. /**
  22. * {@inheritdoc}
  23. */
  24. public function __sleep() {
  25. $this->_serviceIds = [];
  26. $vars = get_object_vars($this);
  27. foreach ($vars as $key => $value) {
  28. if (is_object($value) && isset($value->_serviceId)) {
  29. // If a class member was instantiated by the dependency injection
  30. // container, only store its ID so it can be used to get a fresh object
  31. // on unserialization.
  32. $this->_serviceIds[$key] = $value->_serviceId;
  33. unset($vars[$key]);
  34. }
  35. // Special case the container, which might not have a service ID.
  36. elseif ($value instanceof ContainerInterface) {
  37. $this->_serviceIds[$key] = 'service_container';
  38. unset($vars[$key]);
  39. }
  40. elseif ($value instanceof EntityStorageInterface) {
  41. // If a class member is an entity storage, only store the entity type ID
  42. // the storage is for so it can be used to get a fresh object on
  43. // unserialization. By doing this we prevent possible memory leaks when
  44. // the storage is serialized when it contains a static cache of entity
  45. // objects and additionally we ensure that we'll not have multiple
  46. // storage objects for the same entity type and therefore prevent
  47. // returning different references for the same entity.
  48. $this->_entityStorages[$key] = $value->getEntityTypeId();
  49. unset($vars[$key]);
  50. }
  51. }
  52. return array_keys($vars);
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. public function __wakeup() {
  58. // Tests in isolation potentially unserialize in the parent process.
  59. $phpunit_bootstrap = isset($GLOBALS['__PHPUNIT_BOOTSTRAP']);
  60. if ($phpunit_bootstrap && !\Drupal::hasContainer()) {
  61. return;
  62. }
  63. $container = \Drupal::getContainer();
  64. foreach ($this->_serviceIds as $key => $service_id) {
  65. // In rare cases, when test data is serialized in the parent process,
  66. // there is a service container but it doesn't contain all expected
  67. // services. To avoid fatal errors during the wrap-up of failing tests, we
  68. // check for this case, too.
  69. if ($phpunit_bootstrap && !$container->has($service_id)) {
  70. continue;
  71. }
  72. $this->$key = $container->get($service_id);
  73. }
  74. $this->_serviceIds = [];
  75. // In rare cases, when test data is serialized in the parent process, there
  76. // is a service container but it doesn't contain all expected services. To
  77. // avoid fatal errors during the wrap-up of failing tests, we check for this
  78. // case, too.
  79. if ($this->_entityStorages && (!$phpunit_bootstrap || $container->has('entity_type.manager'))) {
  80. /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
  81. $entity_type_manager = $container->get('entity_type.manager');
  82. foreach ($this->_entityStorages as $key => $entity_type_id) {
  83. $this->$key = $entity_type_manager->getStorage($entity_type_id);
  84. }
  85. }
  86. $this->_entityStorages = [];
  87. }
  88. }