PHP Classes

File: src/Graph.php

Recommend this page to a friend!
  Classes of Rodolfo Berrios Arce   Workflow   src/Graph.php   Download  
File: src/Graph.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Workflow
Create and run action workflows
Author: By
Last change:
Date: 25 days ago
Size: 5,611 bytes
 

Contents

Class file image Download
<?php

/*
 * This file is part of Chevere.
 *
 * (c) Rodolfo Berrios <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace
Chevere\Workflow;

use
Chevere\DataStructure\Interfaces\VectorInterface;
use
Chevere\DataStructure\Map;
use
Chevere\DataStructure\Traits\MapTrait;
use
Chevere\DataStructure\Vector;
use
Chevere\Workflow\Interfaces\GraphInterface;
use
Chevere\Workflow\Interfaces\JobInterface;
use
InvalidArgumentException;
use function
Chevere\Message\message;

final class
Graph implements GraphInterface
{
   
/**
     * @template-use MapTrait<VectorInterface<string>>
     */
   
use MapTrait;

   
/**
     * @var VectorInterface<string>
     */
   
private VectorInterface $syncJobs;

    public function
__construct()
    {
       
$this->map = new Map();
       
$this->syncJobs = new Vector();
    }

    public function
withPut(
       
string $name,
       
JobInterface $job,
    ):
GraphInterface {
       
$vector = $job->dependencies();
       
$this->assertNotSelfDependency($name, $vector);
       
$new = clone $this;
        foreach (
$vector as $dependency) {
            if (!
$new->has($dependency)) {
               
$new->map = $new->map
                   
->withPut($dependency, new Vector());
            }
        }
        if (
$new->map->has($name)) {
           
/** @var VectorInterface<string> $existing */
           
$existing = $new->map->get($name);
           
$merge = array_merge($existing->toArray(), $vector->toArray());
           
$vector = new Vector(...$merge);
        }
       
$new->handleDependencyUpdate($name, $vector);
       
$new->map = $new->map->withPut($name, $vector);
        if (
$job->isSync()) {
           
$new->syncJobs = $new->syncJobs->withPush($name);
        }

        return
$new;
    }

    public function
has(string $job): bool
   
{
        return
$this->map->has($job);
    }

    public function
get(string $job): VectorInterface
   
{
       
/** @var VectorInterface<string> */
       
return $this->map->get($job);
    }

    public function
hasDependencies(string $job, string ...$dependencies): bool
   
{
       
/** @var VectorInterface<string> $array */
       
$array = $this->map->get($job);

        return
$array->contains(...$dependencies);
    }

    public function
toArray(): array
    {
       
$sort = [];
       
$previous = [];
       
$sync = [];
       
$toIndex = 0;
        foreach (
$this->getSortAsc() as $job => $dependencies) {
            foreach (
$dependencies as $dependency) {
                if (
in_array($dependency, $previous, true)) {
                   
$toIndex++;
                   
$previous = [];

                    break;
                }
            }
           
$sort[$toIndex][] = $job;
           
$previous[] = $job;
            if (
$this->syncJobs->find($job) !== null) {
               
$sync[$job] = $toIndex;
            }
        }

        return
$this->getSortJobs($sort, $sync);
    }

   
/**
     * @return array<string, VectorInterface<string>>
     */
   
private function getSortAsc(): array
    {
       
$array = $this->map->toArray();
       
uasort($array, function (VectorInterface $a, VectorInterface $b) {
            return
match (true) {
               
$b->contains(...$a->toArray()) => -1,
               
// @infection-ignore-all
               
$a->contains(...$b->toArray()) => 1,
                default =>
0
           
};
        });

       
/* @phpstan-ignore-next-line */
       
return $array;
    }

   
/**
     * @param array<int, array<int, string>> $sort
     * @param array<string, int> $sync
     * @return array<int, array<int, string>>
     */
   
private function getSortJobs(array $sort, array $sync): array
    {
        if (
count($this->syncJobs) === 0) {
            return
$sort;
        }
       
$aux = 0;
       
$vector = new Vector(...$sort);
        foreach (
$sync as $job => $index) {
           
$auxIndex = $index + $aux;
           
/** @var array<int, string> $array */
           
$array = $vector->get($auxIndex);
           
$key = array_search($job, $array, true);
            unset(
$array[$key]);
           
$array = array_values($array);
           
$vector = $vector
               
->withSet($auxIndex, $array)
                ->
withInsert($auxIndex, [$job]);
           
$aux++;
        }
       
/** @var array<int, array<int, string>> */
       
$array = $vector->toArray();

        return
array_values(
           
array_filter($array)
        );
    }

   
/**
     * @param VectorInterface<string> $vector
     */
   
private function assertNotSelfDependency(string $job, VectorInterface $vector): void
   
{
        if (!
$vector->contains($job)) {
            return;
        }

        throw new
InvalidArgumentException(
            (string)
message(
               
'Cannot declare job **%job%** as a self-dependency',
               
job: $job
           
)
        );
    }

   
/**
     * @param VectorInterface<string> $vector
     */
   
private function handleDependencyUpdate(string $job, VectorInterface $vector): void
   
{
       
/** @var string $dependency */
       
foreach ($vector as $dependency) {
           
/** @var VectorInterface<string> $update */
           
$update = $this->map->get($dependency);
           
$findJob = $update->find($job);
            if (
$findJob !== null) {
               
$update = $update->withRemove($findJob);
            }
           
$this->map = $this->map->withPut($dependency, $update);
        }
    }
}