Stratadox/TableLoader

View on GitHub
src/Loader/HasMany.php

Summary

Maintainability
A
25 mins
Test Coverage
<?php
declare(strict_types=1);

namespace Stratadox\TableLoader\Loader;

use function in_array;
use Stratadox\Hydrator\ArrayHydrator;
use Stratadox\Hydrator\CannotHydrate;
use Stratadox\Hydrator\Hydrates;

/**
 * Defines a has-many relationship.
 *
 * @author Stratadox
 */
final class HasMany implements MakesConnections
{
    private $property;
    private $collection;

    private function __construct(string $property, Hydrates $collection)
    {
        $this->property = $property;
        $this->collection = $collection;
    }

    /**
     * Makes a connector for a has-many type relationship.
     *
     * @param string        $property   The property to map.
     * @param Hydrates|null $collection The collection hydrator to use.
     * @return MakesConnections         The relationship connector.
     */
    public static function in(
        string $property,
        Hydrates $collection = null
    ): MakesConnections {
        return new self($property, $collection ?: ArrayHydrator::create());
    }

    /** @inheritdoc */
    public function load(
        KnowsWhereToLook $from,
        array $data,
        KnowsWhereToLookTo $to,
        ContainsResultingObjects $objects
    ): array {
        $related = [];
        foreach ($data as $row) {
            if ($to->ignoreThe($row)) {
                continue;
            }
            $object = $objects[$to->label()][$to->this($row)];
            if ($this->shouldAdd($object, $from->this($row), $related)) {
                $related[$from->this($row)][] = $object;
            }
        }
        return [
            $this->property => $this->makeCollections($from->label(), $related)
        ];
    }

    /**
     * Checks if we should add the object to the relations collection.
     *
     * @param object $relation The related object.
     * @param string $from     The id of the object that holds the relationship.
     * @param array  $existing The map of existing relationships.
     * @return bool            Whether the object is new.
     */
    private function shouldAdd(
        object $relation,
        string $from,
        array $existing
    ): bool {
        return !isset($existing[$from])
            || !in_array($relation, $existing[$from], true);
    }

    /**
     * Makes collections for the relationship.
     *
     * @param string  $from The label of the entity we connect from.
     * @param array[] $many The objects to connect with.
     *
     * @return array        The collections as [string id => iterable]
     * @throws CannotLoadTable
     */
    private function makeCollections(string $from, array $many): array
    {
        $collections = [];
        foreach ($many as $id => $relatedObjects) {
            try {
                $collections[$id] = $this->collection->fromArray($relatedObjects);
            } catch (CannotHydrate $exception) {
                throw UnmappableRelationship::encountered(
                    $exception,
                    $this->property,
                    $from,
                    $id
                );
            }
        }
        return $collections;
    }
}