PHP code example of ericgansa / ghost-trees-bundle
1. Go to this page and download the library: Download ericgansa/ghost-trees-bundle library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
use EricGansa\GhostTreesBundle\Attribute\GhostField;
use EricGansa\GhostTreesBundle\Attribute\RequiredOnRoot;
use EricGansa\GhostTreesBundle\Contract\GhostableInterface;
use EricGansa\GhostTreesBundle\Trait\GhostNodeTrait;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Trajet implements GhostableInterface
{
use GhostNodeTrait; // fournit getParent(), setParent(), isGhost(),
// resolve(), incarnate(), reset(), createGhostOf()
#[ORM\Column(nullable: true)]
#[GhostField] // marque la propriété pour la résolution dynamique
#[RequiredOnRoot] // obligatoire sur les racines, silencieux sur les fantômes
private ?string $lieuDepart = null;
// Redéclaration UNIQUEMENT pour le mapping Doctrine (targetEntity requis).
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
#[ORM\JoinColumn(nullable: true, onDelete: 'CASCADE')]
protected ?GhostableInterface $parent = null;
public function getLieuDepart(): ?string
{
return $this->resolve($this->lieuDepart, 'getLieuDepart');
// ↑ retourne la valeur locale si non-null, sinon remonte au parent
}
}
// Incarnation sans service (autonome)
$ghost->incarnate();
// → $ghost est maintenant une racine indépendante avec toutes ses valeurs matérialisées
// Retour à la transparence (annule les surcharges)
$ghost->reset();
// → $ghost lit de nouveau toutes ses valeurs depuis le parent
// Fabrique un fantôme
$copy = Trajet::createGhostOf($original);
// → $copy->getParent() === $original, tous les champs à null (lecture transparente)
use EricGansa\GhostTreesBundle\Contract\GhostResolverInterface;
public function __construct(private GhostResolverInterface $resolver) {}
public function attachToParent(Trajet $child, Trajet $parent): void
{
// Lève GhostDepthExceededException ou GhostCycleException si invalide.
$this->resolver->assertValidParent($child, $parent);
$child->setParent($parent);
}
use EricGansa\GhostTreesBundle\Contract\GhostIncarnatorInterface;
public function incarnate(Trajet $ghost, GhostIncarnatorInterface $incarnator, EntityManagerInterface $em): void
{
$em->wrapInTransaction(function () use ($incarnator, $ghost) {
$incarnator->incarnate($ghost); // émet GhostIncarnatedEvent
});
}
use EricGansa\GhostTreesBundle\Event\GhostIncarnatedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: GhostIncarnatedEvent::class)]
final class IncarnationAuditor
{
public function __construct(private LoggerInterface $logger) {}
public function __invoke(GhostIncarnatedEvent $event): void
{
$this->logger->info('Ghost incarnated', [
'entity' => $event->entity::class . '#' . $event->entity->getId(),
'previousParent' => $event->previousParent?->getId(),
]);
}
}
#[AsEventListener(event: GhostIncarnatedEvent::class)]
final class NotifyAuthorOnIncarnation
{
public function __invoke(GhostIncarnatedEvent $event): void
{
$previous = $event->previousParent;
if ($previous instanceof Recipe && $previous->getAuthor()) {
$this->mailer->send(new RecipeIncarnatedMail($previous->getAuthor(), $event->entity));
}
}
}