Skip to content

Instantly share code, notes, and snippets.

@Kcko
Last active February 12, 2022 22:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kcko/61d8f6baecc8ccfe68b99dc6a1b1b224 to your computer and use it in GitHub Desktop.
Save Kcko/61d8f6baecc8ccfe68b99dc6a1b1b224 to your computer and use it in GitHub Desktop.
<?php
interface Command
{
public function handle() : void;
}
final class CreateListing implements Command
{
private const MIN_TITLE_LENGTH = 10;
private const MIN_CONTENT_LENGTH = 15;
private const MIN_AUTHOR_LENGTH = 5;
private $repository;
private $title;
private $content;
private $author;
public function __construct(ListingRepository $repository, string $title, string $content, string $author)
{
$this->repository = $repository;
$this->title = $title;
$this->content = $content;
$this->author = $author;
}
private function validate(): void
{
if (strlen($this->title) < self::MIN_TITLE_LENGTH) {
throw new LengthException(sprintf("Title is too short. Must be at least %d characters",
self::MIN_TITLE_LENGTH));
}
if (strlen($this->content) < self::MIN_CONTENT_LENGTH) {
throw new LengthException(sprintf("Content is too short. Must be at least %d characters",
self::MIN_CONTENT_LENGTH));
}
if (strlen($this->author) < self::MIN_AUTHOR_LENGTH) {
throw new LengthException(sprintf("Author name is too short. Must be at least %d characters",
self::MIN_AUTHOR_LENGTH));
}
}
public function handle(): void
{
$this->validate();
$this->repository->create($this->title, $this->content, $this->author);
}
}
final class DeleteListing implements Command
{
private $repository;
private $listingUid;
public function __construct(ListingRepository $repository, string $listingUid)
{
$this->repository = $repository;
$this->listingUid = $listingUid;
}
public function handle(): void
{
$this->repository->delete($this->listingUid);
}
}
final class ListingRepository
{
public function create(string $title, string $content, string $author): void
{
echo sprintf("Creating new listing by \"%s\" and title \"%s\"", $author, $title).PHP_EOL;
echo sprintf("Content: \"%s\"", $content).PHP_EOL;
echo sprintf("Generated uid: \"%s\"", uniqid()).PHP_EOL;
}
public function delete(string $uid): void
{
echo sprintf("Removing job listing with uid: \"%s\"", $uid).PHP_EOL;
}
}
final class Client
{
private $listingRepository;
public function __construct(ListingRepository $repository)
{
$this->listingRepository = $repository;
}
public function createListing(string $title, string $content, string $author): void
{
$command = new CreateListing($this->listingRepository, $title, $content, $author);
$command->handle();
}
public function deleteListing(string $listingUid) : void
{
$command = new DeleteListing($this->listingRepository, $listingUid);
$command->handle();
}
}
$client = new Client(new ListingRepository());
$client->createListing(
"New job listing",
"This is a content of a listing",
"Company"
);
$client->deleteListing("Unique id");
<?php
namespace RefactoringGuru\Command\Conceptual;
/**
* The Command interface declares a method for executing a command.
*/
interface Command
{
public function execute(): void;
}
/**
* Some commands can implement simple operations on their own.
*/
class SimpleCommand implements Command
{
private $payload;
public function __construct(string $payload)
{
$this->payload = $payload;
}
public function execute(): void
{
echo "SimpleCommand: See, I can do simple things like printing (" . $this->payload . ")\n";
}
}
/**
* However, some commands can delegate more complex operations to other objects,
* called "receivers."
*/
class ComplexCommand implements Command
{
/**
* @var Receiver
*/
private $receiver;
/**
* Context data, required for launching the receiver's methods.
*/
private $a;
private $b;
/**
* Complex commands can accept one or several receiver objects along with
* any context data via the constructor.
*/
public function __construct(Receiver $receiver, string $a, string $b)
{
$this->receiver = $receiver;
$this->a = $a;
$this->b = $b;
}
/**
* Commands can delegate to any methods of a receiver.
*/
public function execute(): void
{
echo "ComplexCommand: Complex stuff should be done by a receiver object.\n";
$this->receiver->doSomething($this->a);
$this->receiver->doSomethingElse($this->b);
}
}
/**
* The Receiver classes contain some important business logic. They know how to
* perform all kinds of operations, associated with carrying out a request. In
* fact, any class may serve as a Receiver.
*/
class Receiver
{
public function doSomething(string $a): void
{
echo "Receiver: Working on (" . $a . ".)\n";
}
public function doSomethingElse(string $b): void
{
echo "Receiver: Also working on (" . $b . ".)\n";
}
}
/**
* The Invoker is associated with one or several commands. It sends a request to
* the command.
*/
class Invoker
{
/**
* @var Command
*/
private $onStart;
/**
* @var Command
*/
private $onFinish;
/**
* Initialize commands.
*/
public function setOnStart(Command $command): void
{
$this->onStart = $command;
}
public function setOnFinish(Command $command): void
{
$this->onFinish = $command;
}
/**
* The Invoker does not depend on concrete command or receiver classes. The
* Invoker passes a request to a receiver indirectly, by executing a
* command.
*/
public function doSomethingImportant(): void
{
echo "Invoker: Does anybody want something done before I begin?\n";
if ($this->onStart instanceof Command) {
$this->onStart->execute();
}
echo "Invoker: ...doing something really important...\n";
echo "Invoker: Does anybody want something done after I finish?\n";
if ($this->onFinish instanceof Command) {
$this->onFinish->execute();
}
}
}
/**
* The client code can parameterize an invoker with any commands.
*/
$invoker = new Invoker;
$invoker->setOnStart(new SimpleCommand("Say Hi!"));
$receiver = new Receiver;
$invoker->setOnFinish(new ComplexCommand($receiver, "Send email", "Save report"));
$invoker->doSomethingImportant();
<?php
interface Command
{
/**
* this is the most important method in the Command pattern,
* The Receiver goes in the constructor.
*/
public function execute();
}
interface UndoableCommand extends Command
{
/**
* This method is used to undo change made by command execution
*/
public function undo();
}
class HelloCommand implements Command
{
private Receiver $output;
/**
* Each concrete command is built with different receivers.
* There can be one, many or completely no receivers, but there can be other commands in the parameters
*/
public function __construct(Receiver $console)
{
$this->output = $console;
}
/**
* execute and output "Hello World".
*/
public function execute()
{
// sometimes, there is no receiver and this is the command which does all the work
$this->output->write('Hello World');
}
}
class AddMessageDateCommand implements UndoableCommand
{
private Receiver $output;
/**
* Each concrete command is built with different receivers.
* There can be one, many or completely no receivers, but there can be other commands in the parameters.
*/
public function __construct(Receiver $console)
{
$this->output = $console;
}
/**
* Execute and make receiver to enable displaying messages date.
*/
public function execute()
{
// sometimes, there is no receiver and this is the command which
// does all the work
$this->output->enableDate();
}
/**
* Undo the command and make receiver to disable displaying messages date.
*/
public function undo()
{
// sometimes, there is no receiver and this is the command which
// does all the work
$this->output->disableDate();
}
}
/**
* Receiver is a specific service with its own contract and can be only concrete.
*/
class Receiver
{
private bool $enableDate = false;
/**
* @var string[]
*/
private array $output = [];
public function write(string $str)
{
if ($this->enableDate) {
$str .= ' ['.date('Y-m-d').']';
}
$this->output[] = $str;
}
public function getOutput(): string
{
return join("\n", $this->output);
}
/**
* Enable receiver to display message date
*/
public function enableDate()
{
$this->enableDate = true;
}
/**
* Disable receiver to display message date
*/
public function disableDate()
{
$this->enableDate = false;
}
}
/**
* Invoker is using the command given to it.
* Example : an Application in SF2.
*/
class Invoker
{
private Command $command;
/**
* in the invoker we find this kind of method for subscribing the command
* There can be also a stack, a list, a fixed set ...
*/
public function setCommand(Command $cmd)
{
$this->command = $cmd;
}
/**
* executes the command; the invoker is the same whatever is the command
*/
public function run()
{
$this->command->execute();
}
}
// USAGE
$invoker = new Invoker();
$receiver = new Receiver();
$invoker->setCommand(new HelloCommand($receiver));
$invoker->run();
$this->assertSame('Hello World', $receiver->getOutput());
// https://designpatternsphp.readthedocs.io/en/latest/Behavioral/Command/README.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment