Rules: pipes as classes
A closure is fine for a small step. A PipeRule class gives a step a name, a home and a test. The hub runs the public method named after the class. Inside it, $this->info('…') writes a line to the log against the rule, and returning self::SUCCESS records that the step passed (the other results, and $this->error() / $this->fatal(), come in Controlling the flow below):
<?php
use Keaton\PipeDream\PipeRule;
class TidyTitle extends PipeRule
{
public static $name = 'Tidy the title';
public static $description = 'Trim it, collapse the spaces, title-case it';
public function tidyTitle($post)
{
$post->title = ucwords(strtolower(preg_replace('/\s+/', ' ', trim($post->title))));
$this->info("Title is now \"{$post->title}\"");
return self::SUCCESS;
}
}The method is called through Laravel's container, so dependency injection just works: ask for a class and you get it. Here the rule asks for Slugger, a plain class of yours:
<?php
class Slugger
{
public function slug(string $text): string
{
return trim(preg_replace('/[^a-z0-9]+/', '-', strtolower($text)), '-');
}
}And the rule that uses it:
<?php
use Keaton\PipeDream\PipeRule;
class SlugifyTitle extends PipeRule
{
public static $name = 'Slugify the title';
public static $description = 'Make the URL slug';
public function slugifyTitle($post, Slugger $slugger)
{
$post->slug = $slugger->slug($post->title);
return self::SUCCESS;
}
}The first parameter receives the envelope, whatever you call it (leave it untyped, or type it PipeEnvelope; a different class type there is resolved from the container instead). Inside a rule, $this->words = 3 and $this->words set and read a shared value on it ($this->setWordsResult(3) and $this->getWordsResult() do the same):
<?php
use Keaton\PipeDream\PipeRule;
class CountWords extends PipeRule
{
public static $name = 'Count the words';
public static $description = 'Remember how long the title is';
public function countWords($post)
{
$this->words = str_word_count($post->title);
return self::SUCCESS;
}
}A rule can also decide that it has nothing to do and skip itself. For a one-word title, this one records PipeRule::SKIP (4) and logs its reason. It also uses $this->error(), which records a failure (PipeRule::FAIL) with its message and, by default, lets the hub carry on to the next rule:
<?php
use Keaton\PipeDream\PipeRule;
class CheckTitleLength extends PipeRule
{
public static $name = 'Check the title length';
public static $description = 'Long titles get cut off in feeds';
public function checkTitleLength($post)
{
if ($this->words < 2) {
return $this->skip('A one-word title is fine');
}
if ($this->words > 12) {
$this->error("The title has {$this->words} words");
}
return self::SUCCESS;
}
}Use them like closures, by class name:
<?php
use Keaton\PipeDream\PipeHub;
$post = (object) ['title' => ' hello, PIPE dream '];
$envelope = PipeHub::create()
->addPipes([
TidyTitle::class,
CountWords::class,
CheckTitleLength::class,
SlugifyTitle::class,
])
->sendIt($post);
echo $post->slug, ' (', $envelope->get('words'), ' words)', PHP_EOL;
foreach ($envelope->results() as $result) {
echo $result->rule(), ': ', $result->result(), PHP_EOL;
}
hello-pipe-dream (3 words)
TidyTitle: 1
CountWords: 1
CheckTitleLength: 1
SlugifyTitle: 11 is PipeRule::SUCCESS.