Syndesi/neo4j-sync-bundle

View on GitHub
src/ValueObject/Relation.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

declare(strict_types=1);

namespace Syndesi\Neo4jSyncBundle\ValueObject;

use Stringable;
use Syndesi\Neo4jSyncBundle\Contract\IsEqualToInterface;
use Syndesi\Neo4jSyncBundle\Exception\DuplicatePropertiesException;
use Syndesi\Neo4jSyncBundle\Exception\InvalidArgumentException;
use Syndesi\Neo4jSyncBundle\Exception\MissingIdPropertyException;
use Syndesi\Neo4jSyncBundle\Exception\MissingPropertyException;
use Syndesi\Neo4jSyncBundle\Exception\MissingPropertyValueException;
use Syndesi\Neo4jSyncBundle\Exception\UnsupportedPropertyNameException;
use Syndesi\Neo4jSyncBundle\Helper\CompareEqualHelper;

class Relation implements Stringable, IsEqualToInterface
{
    /**
     * @param RelationLabel $label                 Label of the relationship, e.g. CHILD_NODE_PARENT_NODE.
     * @param NodeLabel     $relatesToLabel        label of the parent node
     * @param Property      $relatesToIdentifier
     * @param NodeLabel     $relatesFromLabel
     * @param Property      $relatesFromIdentifier
     * @param array         $properties
     * @param Property|null $identifier
     *
     * @throws DuplicatePropertiesException
     * @throws InvalidArgumentException
     * @throws MissingIdPropertyException
     * @throws MissingPropertyValueException
     */
    public function __construct(
        private readonly RelationLabel $label,
        private readonly NodeLabel $relatesToLabel,
        private readonly Property $relatesToIdentifier,
        private readonly NodeLabel $relatesFromLabel,
        private readonly Property $relatesFromIdentifier,
        /**
         * @var Property[]
         */
        private readonly array $properties = [],
        private readonly ?Property $identifier = null
    ) {
        if (null === $relatesToIdentifier->getValue()) {
            throw new MissingPropertyValueException('The value of the relates to property of a relation can not be null.');
        }
        if (null === $relatesFromIdentifier->getValue()) {
            throw new MissingPropertyValueException('The value of the relates from property of a relation can not be null.');
        }
        $propertyNames = [];
        foreach ($properties as $property) {
            if (!($property instanceof Property)) {
                throw new InvalidArgumentException(sprintf("Property of type %s should be of type %s", get_class($property), Property::class));
            }
            $propertyNames[] = $property->getName();
        }
        if (count(array_unique($propertyNames)) !== count($properties)) {
            throw new DuplicatePropertiesException('Relation require each property to have an unique name.');
        }
        if ($identifier) {
            if (!in_array($identifier->getName(), $propertyNames)) {
                throw new MissingIdPropertyException(sprintf("Relation has identifier with name '%s', but it is not part of its properties.\n".'If it is a Doctrine entity, was it yet persisted and flushed?', $identifier->getName()));
            }
        }
    }

    public function getLabel(): RelationLabel
    {
        return $this->label;
    }

    public function getRelatesToLabel(): NodeLabel
    {
        return $this->relatesToLabel;
    }

    public function getRelatesToIdentifier(): Property
    {
        return $this->relatesToIdentifier;
    }

    public function getRelatesFromLabel(): NodeLabel
    {
        return $this->relatesFromLabel;
    }

    public function getRelatesFromIdentifier(): Property
    {
        return $this->relatesFromIdentifier;
    }

    /**
     * @return Property[]
     */
    public function getProperties(): array
    {
        return $this->properties;
    }

    /**
     * @return array<string, mixed>
     */
    public function getPropertiesAsAssociativeArray(): array
    {
        $associativeArray = [];
        foreach ($this->properties as $property) {
            $associativeArray[$property->getName()] = $property->getValue();
        }

        return $associativeArray;
    }

    /**
     * @throws MissingPropertyException
     */
    public function getProperty(string $name): mixed
    {
        foreach ($this->properties as $property) {
            /**
             * @var Property $property
             */
            if ($property->getName() === $name) {
                return $property->getValue();
            }
        }
        throw new MissingPropertyException(sprintf("Unable to find property with name '%s'.", $name));
    }

    /**
     * If relation was created with an identifier, this method returns a new instance of a property with both the
     * identifiers name and the identifiers value from the properties.
     *
     * @throws MissingPropertyException
     * @throws UnsupportedPropertyNameException
     */
    public function getIdentifier(): ?Property
    {
        if ($this->identifier) {
            return new Property($this->identifier->getName(), $this->getProperty($this->identifier->getName()));
        }

        return null;
    }

    public function __toString()
    {
        $properties = [];
        foreach ($this->properties as $property) {
            /* @var $property Property */
            $properties[] = sprintf("%s: %s", $property->getName(), $property->getValue());
        }
        $properties = implode(', ', $properties);

        return sprintf(
            "(:%s {%s: %s})-[:%s {%s}]->(:%s {%s: %s})",
            (string) $this->relatesFromLabel,
            $this->relatesFromIdentifier->getName(),
            $this->relatesFromIdentifier->getValue(),
            (string) $this->label,
            $properties,
            (string) $this->relatesToLabel,
            $this->relatesToIdentifier->getName(),
            $this->relatesToIdentifier->getValue()
        );
    }

    public function isEqualTo(object $element): bool
    {
        if (!($element instanceof Relation)) {
            return false;
        }

        $arePropertiesEqual = true;
        if (count($this->properties) !== count($element->properties)) {
            $arePropertiesEqual = false;
        } else {
            foreach ($this->properties as $i => $property) {
                if (!$property->isEqualTo($element->properties[$i])) {
                    $arePropertiesEqual = false;
                    break;
                }
            }
        }

        return
            $this->label->isEqualTo($element->label) &&
            $this->relatesToLabel->isEqualTo($element->relatesToLabel) &&
            $this->relatesToIdentifier->isEqualTo($element->relatesToIdentifier) &&
            $this->relatesFromLabel->isEqualTo($element->relatesFromLabel) &&
            $this->relatesFromIdentifier->isEqualTo($element->relatesFromIdentifier) &&
            $arePropertiesEqual &&
            CompareEqualHelper::compare($this->identifier, $element->identifier);
    }
}