Skip to content

Commit

Permalink
Merge pull request #25 from telkins/develop
Browse files Browse the repository at this point in the history
Raise minimum support to Laravel 10 and PHP8.1
  • Loading branch information
telkins authored Mar 28, 2023
2 parents 1954b29 + d1b73c0 commit a877776
Show file tree
Hide file tree
Showing 15 changed files with 432 additions and 362 deletions.
16 changes: 4 additions & 12 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,12 @@ jobs:
fail-fast: true
matrix:
os: [ubuntu-latest]
php: [8.1, 8.0, 7.4]
laravel: [9.*, 8.*]
php: [8.1]
laravel: [10.*]
dependency-version: [prefer-lowest, prefer-stable]
include:
- laravel: 9.*
testbench: 7.*
- laravel: 8.*
testbench: 6.*
exclude:
- laravel: 9.*
php: 7.4
- laravel: 8.*
php: 8.1
dependency-version: prefer-lowest
- laravel: 10.*
testbench: 8.*
steps:
- name: Checkout code
uses: actions/checkout@v2
Expand Down
13 changes: 7 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@
}
],
"require": {
"php": "^7.4|^8.0",
"illuminate/contracts": "^8.0|^9.0",
"illuminate/database": "^8.0|^9.0",
"illuminate/support": "^8.0|^9.0"
"php": "^8.1",
"illuminate/contracts": "^10.0",
"illuminate/database": "^10.0",
"illuminate/support": "^10.0",
"spatie/laravel-data": "^3.2"
},
"require-dev": {
"orchestra/testbench": "^6.0|^7.0",
"phpunit/phpunit": "^9.3"
"orchestra/testbench": "^8.0",
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<coverage>
<include>
<directory suffix=".php">src/</directory>
Expand Down
90 changes: 90 additions & 0 deletions src/Actions/AddDagEdge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Telkins\Dag\Actions;

use Illuminate\Support\Collection;
use Telkins\Dag\Concerns\EdgeData;
use Telkins\Dag\Concerns\UsesDagConfig;
use Telkins\Dag\Exceptions\CircularReferenceException;
use Telkins\Dag\Exceptions\TooManyHopsException;
use Telkins\Dag\Models\DagEdge;

class AddDagEdge
{
use UsesDagConfig;

public function __construct(
private CreateCompleteEdge $createCompleteEdge,
) {}

/**
* @throws CircularReferenceException
* @throws TooManyHopsException
*/
public function __invoke(EdgeData $edgeData, int $maxHops): ?Collection
{
if ($this->edgeExists($edgeData)) {
return null;
}

$this->guardAgainstCircularRelation($edgeData);

return tap(
$this->getNewlyInsertedEdges(($this->createCompleteEdge)($edgeData)),
fn ($newEdges) => $this->guardAgainstExceedingMaximumHops($newEdges, $maxHops),
);
}

protected function edgeExists(EdgeData $edgeData): bool
{
$edgeClass = $this->dagEdgeModel();

return $edgeClass::where([
['start_vertex', $edgeData->startVertex],
['end_vertex', $edgeData->endVertex],
['hops', 0],
['source', $edgeData->source],
])->count() > 0;
}

/**
* @throws CircularReferenceException
*/
protected function guardAgainstCircularRelation(EdgeData $edgeData): void
{
if ($edgeData->startVertex === $edgeData->endVertex) {
throw new CircularReferenceException();
}

$edgeClass = $this->dagEdgeModel();

if ($edgeClass::where([
['start_vertex', $edgeData->endVertex],
['end_vertex', $edgeData->startVertex],
['source', $edgeData->source],
])->count() > 0) {
throw new CircularReferenceException();
}
}

protected function getNewlyInsertedEdges(DagEdge $edge): Collection
{
$edgeClass = $this->dagEdgeModel();

return $edgeClass::where('direct_edge_id', $edge->id)
->orderBy('hops')
->get();
}

/**
* @throws TooManyHopsException
*/
protected function guardAgainstExceedingMaximumHops(Collection $newEdges, int $maxHops): void
{
if ($newEdges->isNotEmpty() && ($newEdges->last()->hops > $maxHops)) {
throw TooManyHopsException::make($maxHops);
}
}
}
27 changes: 27 additions & 0 deletions src/Actions/CreateCompleteEdge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Telkins\Dag\Actions;

use Telkins\Dag\Concerns\EdgeData;
use Telkins\Dag\Concerns\UsesDagConfig;
use Telkins\Dag\Models\DagEdge;

class CreateCompleteEdge
{
use UsesDagConfig;

public function __construct(
private CreateDirectEdge $createDirectEdge,
private CreateImpliedEdges $createImpliedEdges,
) {}

public function __invoke(EdgeData $edgeData): DagEdge
{
return tap(
($this->createDirectEdge)($edgeData),
fn ($edge) => ($this->createImpliedEdges)($edge, $edgeData),
);
}
}
43 changes: 43 additions & 0 deletions src/Actions/CreateDirectEdge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Telkins\Dag\Actions;

use Telkins\Dag\Concerns\EdgeData;
use Telkins\Dag\Concerns\UsesDagConfig;
use Telkins\Dag\Models\DagEdge;

class CreateDirectEdge
{
use UsesDagConfig;

public function __invoke(EdgeData $edgeData): DagEdge
{
return tap(
$this->createEdge($edgeData),
fn ($edge) => $this->updateNewEdge($edge),
);
}

private function createEdge(EdgeData $edgeData): DagEdge
{
$edgeClass = $this->dagEdgeModel();

return $edgeClass::create([
'start_vertex' => $edgeData->startVertex,
'end_vertex' => $edgeData->endVertex,
'hops' => 0,
'source' => $edgeData->source,
]);
}

private function updateNewEdge(DagEdge $edge): void
{
$edge->update([
'entry_edge_id' => $edge->id,
'exit_edge_id' => $edge->id,
'direct_edge_id' => $edge->id,
]);
}
}
102 changes: 102 additions & 0 deletions src/Actions/CreateImpliedEdges.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Telkins\Dag\Actions;

use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Telkins\Dag\Concerns\EdgeData;
use Telkins\Dag\Concerns\UsesDagConfig;
use Telkins\Dag\Models\DagEdge;

class CreateImpliedEdges
{
use UsesDagConfig;

public function __invoke(DagEdge $edge, EdgeData $edgeData): void
{
$this->createAsIncomingEdgesToB($edge, $edgeData);

$this->createAToBsOutgoingEdges($edge, $edgeData);

$this->createAsIncomingEdgesToEndVertexOfBsOutgoingEdges($edge, $edgeData);
}

protected function createAsIncomingEdgesToB(DagEdge $edge, EdgeData $edgeData): void
{
$select = DB::connection($edgeData->connection)->table($edgeData->tableName)
->select([
'id as entry_edge_id',
DB::connection($edgeData->connection)->raw("{$edge->id} as direct_edge_id"),
DB::connection($edgeData->connection)->raw("{$edge->id} as exit_edge_id"),
'start_vertex',
DB::connection($edgeData->connection)->raw("{$edgeData->endVertex} as end_vertex"),
DB::connection($edgeData->connection)->raw('(hops + 1) as hops'),
DB::connection($edgeData->connection)->raw("'{$edgeData->source}' as source"),
])->where([
['end_vertex', $edgeData->startVertex],
['source', $edgeData->source],
]);

$this->executeInsert($select, $edgeData);
}

protected function createAToBsOutgoingEdges(DagEdge $edge, EdgeData $edgeData): void
{
$select = DB::connection($edgeData->connection)->table($edgeData->tableName)
->select([
DB::connection($edgeData->connection)->raw("{$edge->id} as entry_edge_id"),
DB::connection($edgeData->connection)->raw("{$edge->id} as direct_edge_id"),
'id as exit_edge_id',
DB::connection($edgeData->connection)->raw("{$edgeData->startVertex} as start_vertex"),
'end_vertex',
DB::connection($edgeData->connection)->raw('(hops + 1) as hops'),
DB::connection($edgeData->connection)->raw("'{$edgeData->source}' as source"),
])->where([
['start_vertex', $edgeData->endVertex],
['source', $edgeData->source],
]);

$this->executeInsert($select, $edgeData);
}

protected function createAsIncomingEdgesToEndVertexOfBsOutgoingEdges(DagEdge $edge, EdgeData $edgeData): void
{
$select = DB::connection($edgeData->connection)->table($edgeData->tableName.' as a')
->select([
DB::connection($edgeData->connection)->raw('a.id as entry_edge_id'),
DB::connection($edgeData->connection)->raw("{$edge->id} as direct_edge_id"),
'b.id as exit_edge_id',
'a.start_vertex',
'b.end_vertex',
DB::connection($edgeData->connection)->raw('(a.hops + b.hops + 2) as hops'),
DB::connection($edgeData->connection)->raw("'{$edgeData->source}' as source"),
])->crossJoin($edgeData->tableName.' as b')
->where([
['a.end_vertex', $edgeData->startVertex],
['b.start_vertex', $edgeData->endVertex],
['a.source', $edgeData->source],
['b.source', $edgeData->source],
]);

$this->executeInsert($select, $edgeData);
}

protected function executeInsert(Builder $select, EdgeData $edgeData): void
{
$bindings = $select->getBindings();

$insertQuery = 'INSERT into `'.$edgeData->tableName.'` (
`entry_edge_id`,
`direct_edge_id`,
`exit_edge_id`,
`start_vertex`,
`end_vertex`,
`hops`,
`source`) '
. $select->toSql();

DB::connection($edgeData->connection)->insert($insertQuery, $bindings);
}
}
52 changes: 52 additions & 0 deletions src/Actions/GetAllEdgeEntryIds.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Telkins\Dag\Actions;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Telkins\Dag\Concerns\UsesDagConfig;
use Telkins\Dag\Models\DagEdge;

/**
* Find and return all edge table entry IDs for a given edge.
*/
class GetAllEdgeEntryIds
{
use UsesDagConfig;

public function __invoke(DagEdge $edge, string $tableName, ?string $connection): Collection
{
/**
* First, collect the "rows that were originally inserted...for this
* direct edge".
*/
$originalIds = DB::connection($connection)->table($tableName)
->select('id')
->where('direct_edge_id', $edge->id)
->get()
->pluck('id');

/**
* Next, "scan and find all dependent rows that were inserted
* afterwards".
*/
do {
$dependentIds = DB::connection($connection)->table($tableName)
->select('id')
->where('hops', '>', 0)
->whereNotIn('id', $originalIds)
->where(function ($query) use ($originalIds) {
$query->whereIn('entry_edge_id', $originalIds)
->orWhereIn('exit_edge_id', $originalIds);
})
->get()
->pluck('id');

$originalIds = $originalIds->merge($dependentIds);
} while ($dependentIds->count());

return $originalIds;
}
}
Loading

0 comments on commit a877776

Please sign in to comment.