Unverified Commit fb42722d authored by Alex Pott's avatar Alex Pott
Browse files

fix: #3565937 Workaround PHP bug with fibers and __get()

By: catch
By: godotislate
By: thtas
By: berdir
By: alexpott
(cherry picked from commit 52737f95)
parent a19cb005
Loading
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
namespace Drupal\Core\Field\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Utility\FiberResumeType;

/**
 * Base class for field items referencing other entities.
@@ -13,4 +14,27 @@
 */
abstract class EntityReferenceItemBase extends FieldItemBase implements EntityReferenceItemInterface {

  /**
   * {@inheritdoc}
   */
  public function __get($property_name) {
    // This is a workaround for a PHP bug where a fiber suspend from within a
    // __get() call can incorrectly trigger PHP's recursion guarding.
    // See https://github.com/php/php-src/issues/14983
    if ($property_name === 'entity' && \Fiber::getCurrent()) {
      $fiber = new \Fiber(fn() => parent::__get($property_name));
      $fiber->start();
      while (!$fiber->isTerminated()) {
        if ($fiber->isSuspended()) {
          $resume_type = $fiber->resume();
          if (!$fiber->isTerminated() && $resume_type !== FiberResumeType::Immediate) {
            usleep(500);
          }
        }
      }
      return $fiber->getReturn();
    }
    return parent::__get($property_name);
  }

}
+50 −0
Original line number Diff line number Diff line
@@ -87,6 +87,56 @@ protected function setUp(): void {

  }

  /**
   * Tests fiber suspension within EntityReferenceFieldItemList::__get().
   *
   * @see https://github.com/php/php-src/issues/14983
   */
  public function testEntityReferenceListFiberSuspension(): void {
    $referenced_entity = $this->container->get('entity_type.manager')
      ->getStorage($this->referencedEntityType)
      ->create(['type' => $this->bundle]);
    $referenced_entity->save();

    $storage = $this->container->get('entity_type.manager')->getStorage($this->entityType);

    $entity = $storage->create(['type' => $this->bundle]);
    $entity->{$this->fieldName}->target_id = $referenced_entity->id();
    $entity->save();

    $entity = $storage->load($entity->id());

    $fiber = new \Fiber(fn() => $entity->{$this->fieldName}->entity);
    $fiber->start();
    $referenced_entity = $entity->{$this->fieldName}->entity;
    $this->assertIsObject($referenced_entity);
  }

  /**
   * Tests fiber suspension within EntityReferenceItemBase::__get().
   */
  public function testEntityReferenceItemFiberSuspension(): void {
    $referenced_entity = $this->container->get('entity_type.manager')
      ->getStorage($this->referencedEntityType)
      ->create(['type' => $this->bundle]);
    $referenced_entity->save();

    $storage = $this->container->get('entity_type.manager')->getStorage($this->entityType);

    $entity = $storage->create(['type' => $this->bundle]);
    $entity->{$this->fieldName}->target_id = $referenced_entity->id();
    $entity->save();

    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
    $entity = $storage->load($entity->id());
    $field_item = $entity->get($this->fieldName)->first();

    $fiber = new \Fiber(fn() => $field_item->entity);
    $fiber->start();
    $referenced_entity = $field_item->entity;
    $this->assertIsObject($referenced_entity);
  }

  /**
   * Tests reference field validation.
   */