foundry / masonry
A micro framework for building single use or continuous asynchronous task runners
Requires
- php: ^5.5 || ^7.0
- foundry/masonry-register: ^1.0.0
- psr/log: ^1.0
- react/promise: ^2.2
- symfony/console: ^2.0
- symfony/filesystem: ^2.0
- symfony/yaml: ^2.0
Requires (Dev)
- pdepend/pdepend: 2.1
- phploc/phploc: ^2.1
- phpmd/phpmd: ^2.2
- phpunit/phpunit: >=4.8.21
- sebastian/phpcpd: ^2.0
- squizlabs/php_codesniffer: ^2.3
This package is not auto-updated.
Last update: 2016-11-25 08:15:39 UTC
README
PHP Masonry is a way of building up a service using blocks of functionality. Tasks are retrieved from a pool, and are processed by workers. You can have any number of Workers, and any number of Tasks.
Installation
Install Masonry with Composer
composer require foundry/masonry -n
Contents
Architecture
╭──────────────────────────────────╮
│ (i) Mediator │
╭──────────────────────────────────╮ ├──────────────────────────────────┤
│ (i) Worker ├───→───┤ <Mediator> addWorker(<Worker>) │
├──────────────────────────────────┤ ┌─→──┤ <Promise> process(<Task>) │
┌─→──┤ <Promise> process(<Task>) │ │ ╰──────────────────────────────────╯
│ │ <string[]> getDescriptionTypes() │ │
│ ╰──────────────────────────────────╯ │
│ │
│ │
├──────────────────────────────────────────┴─────────────────────────────────┐ ╭───────────────────────────────╮
│ │ ┌─→─┤ (i) Task\Description │
│ ╭────────────────────────────────╮ │ │ ├───────────────────────────────┤
│ ┌─→─┤ (i) Task ├─→─┘ │ ╰───────────────────────────────╯
│ │ ├────────────────────────────────┤ │
│ │ │ <Description> getDescription() ├───→───┘ ╭────────────────────────────────╮
│ │ │ <Status> getStatus() ├─────→─────┤ (i) Task\Status │
│ │ │ <History> getHistory() ├───→───┐ ├────────────────────────────────┤
│ │ ╰────────────────────────────────╯ │ │ __construct(<string>) │
│ │ │ │ <string> getStatus() │
│ ╭──────────────────────────╮ │ │ │ <string> __toString() │
│ │ (i) Pool │ │ │ ╰────────────────────────────────╯
│ ├──────────────────────────┤ │ │
└─→──┤ <Pool> addTask(<Task>) │ │ │
│ <Task> getTask() ├─→─┘ ╭────────────────────────────────╮ │
│ <Status> getStatus() ├───→───┤ (i) Pool\Status │ │
╰──────────────────────────╯ ├────────────────────────────────┤ │
│ __construct(<string>) │ │
│ <string> getStatus() │ │
│ <string> __toString() │ │
╰────────────────────────────────╯ │
│
│
│
╭────────────────────────────╮ │
│ (i) Task\History ├──────────────────────┘
├────────────────────────────┤
┌────→─────┤ <History> addEvent(<Event>)│
│ │ <Event[]> getEvents() ├─────→─────┐
│ │ <Event> getLastEvent() ├─────→─────┤
│ ╰────────────────────────────╯ │
│ │
│ ╭───────────────────────────────────────╮ │
└──←──┤ (i) Task\History\Event ├──←──┘
├───────────────────────────────────────┤
│ <Event> startEvent() │
├───────────────────────────────────────┤
┌───┬───→──┤ <Event> endEvent(<Result>, <Reason>) │
│ │ │ <float> getStartTime() │
│ │ │ <float> getEndTime() │
│ │ │ <Result> getResult() ├────→────┐
│ │ │ <Reason> getReason() ├──→──┐ │
│ │ │ <string> __toString() │ │ │
│ │ ╰───────────────────────────────────────╯ │ │
│ │ │ │
│ │ ╭────────────────────────────────╮ │ │
│ └────←────┤ (i) Task\History\Reason ├────←────┘ │
│ ├────────────────────────────────┤ │
│ │ __construct(<string>) │ │
│ │ <string> getReason() │ │
│ │ <string> __toString() │ │
│ ╰────────────────────────────────╯ │
│ │
│ ╭────────────────────────────────╮ │
└──────←──────┤ (i) Task\History\Result ├──────←──────┘
├────────────────────────────────┤
│ __construct(<string>) │
│ <string> getResult() │
│ <string> __toString() │
╰────────────────────────────────╯
Task
A task represents a piece of work that needs to happen. It tells you the history of the task, its current status, and what it wants to happen, but does not specify how to do it.
╭────────────────────────────────╮
│ (i) Task │
├────────────────────────────────┤
│ <Description> getDescription() │ ← Description tells a worker what needs to happen
│ <Status> getStatus() │ ← The current status of the Task: 'new', 'in progress', 'complete', or 'deferred'
│ <History> getHistory() │ ← The history of the Task so far
╰────────────────────────────────╯
Task - Description
Description is an empty interface simply used for control. It should be extended to provide the information require for a specific task.
╭───────────────────────────────╮
│ (i) Task\Description │
├───────────────────────────────┤
╰───────────────────────────────╯
For example, if you wanted a worker that could move files, you could define a MoveFile interface. The interface tells
you where the file is now, and where it should be. Your worker should then add the interface name (MoveFile
) to it's
list of Descriptions it knows how to deal with, provided by Worker::getDescriptionTypes()
.
╭───────────────────────────────╮
│ (i) MoveFile : Description │
├───────────────────────────────┤
│ <string> fromLocation() │
│ <string> toLocation() │
╰───────────────────────────────╯
Task - Status
The task maintains a reference in the Task Pool until it is complete. At any point a Task may be asked for it's Status. A task may have one of four statuses.
new
: The tasks has not been touched since it was createdin progress
: The task has been removed from the pool to be processedcomplete
: The task has been processed. This does not specify whether the task succeeded or failed, that is covered by the [Result][#task-history]deferred
: The task has been attempted previously but for whatever reason has been put back in the queue.
╭────────────────────────────────╮
│ (i) Task\Status │
├────────────────────────────────┤
│ __construct(<string>) │ ← Initialise the Status with: 'new', 'in progress', 'complete', or 'deferred'
│ <string> getStatus() │ ← The string representation of the status
│ <string> __toString() │ ← must return getStatus()
╰────────────────────────────────╯
Task - History
The history is simply a collection of Events
╭────────────────────────────╮
│ (i) Task\History │
├────────────────────────────┤
│ <History> addEvent(<Event>)│ ← Add a new event to the history
│ <Event[]> getEvents() │ ← Returns an array of all events
│ <Event> getLastEvent() │ ← Returns the last event or null if there is nothing in the history
╰────────────────────────────╯
Task - History - Event
A single event in the tasks history. A new event should be created when the Task leaves the Pool to be processed. The event is completed when the Pool is asked to complete or defer the task.
╭───────────────────────────────────────╮
│ (i) Task\History\Event │
├───────────────────────────────────────┤
│ <Event> startEvent() │ ← Events should only be created through this static method
├───────────────────────────────────────┤
│ <Event> endEvent(<Result>, <Reason>) │ ← End the event with a Result, and optionally Reason
│ <float> getStartTime() │ ← Get the event start time. This should be the time the event was created
│ <float> getEndTime() │ ← Get the end time or null if it's in progress
│ <Result> getResult() │ ← Always returns a Result, though it defaults to 'incomplete'
│ <Reason> getReason() │ ← Always returns a Reason, though it's empty by default
│ <string> __toString() │ ← Should return a string representation of the event for logging purposes
╰───────────────────────────────────────╯
Task - History - Result
The result of trying to process the Task, recorded against an Event in History.
╭────────────────────────────────╮
│ (i) Task\History\Result │
├────────────────────────────────┤
│ __construct(<string>) │ ← Initialise the Result with a value: 'succeeded', 'failed' or 'incomplete'
│ <string> getResult() │ ← The string representation of the result: 'succeeded', 'failed' or 'incomplete'
│ <string> __toString() │ ← must return getResult()
╰────────────────────────────────╯
Task - History - Reason
The reason for the Result of trying to process the Task, recorded against an Event in History. This is optional and can be any string. It is mostly useful for logging failures.
╭────────────────────────────────╮
│ (i) Task\History\Reason │
├────────────────────────────────┤
│ __construct(<string>) │ ← Initialise the Reason with any string value
│ <string> getReason() │ ← The string representation of the Reason
│ <string> __toString() │ ← must return getReason()
╰────────────────────────────────╯
Pool
The Task Pool is designed to store and retrieve tasks. It could represent a queue, a stack, a heap or something else.
╭──────────────────────────╮
│ (i) Pool │
├──────────────────────────┤
│ <Pool> addTask(<Task>) │ ← returns a reference to itself for chaining
│ <Task> getTask() │ ← get the next Task from the pool
│ <Status> getStatus() │ ← tells you if the pool has tasks 'pending' or is 'empty'
╰──────────────────────────╯
Pool - Status
The State of the Task Pool is represented by the Status Value Class. It could be in two states.
pending
There are tasks waiting to be processedempty
There are no tasks waiting to be processed
Note: Tasks with the status in progress
or complete
should not be considered waiting.
╭────────────────────────────────╮
│ (i) Pool\Status │
├────────────────────────────────┤
│ __construct(<string>) │ ← Initialise the Status with: 'pending' or 'empty'
│ <string> getStatus() │ ← The string representation of the status
│ <string> __toString() │ ← must return getStatus()
╰────────────────────────────────╯
Worker
Workers are where the Tasks are processed. They may or may not be asynchronous and therefore should always return a Promise.
Workers may also be able to handle multiple types of Task Descriptions, and will therefore always return an array of strings. The best practice, however, should be to have one worker per Task Description
╭──────────────────────────────────╮
│ (i) Worker │
├──────────────────────────────────┤
│ <Promise> process(<Task>) │ ← Workers always return Promises, regardless of whether they are asynchronous.
│ <string[]> getDescriptionTypes() │ ← This should return an array of Descriptions Types the worker knows how to handle.
╰──────────────────────────────────╯
Mediator
The mediator is responsible for taking a task and passing it to the correct worker.
╭──────────────────────────────────╮
│ (i) Mediator │
├──────────────────────────────────┤
│ <Mediator> addWorker(<Worker>) │ ← Inform the mediator about a Worker. Returns a reference to itself
│ <Promise> process(<Task>) │ ← Pass a task. This will return a promise from a Worker to do the task or throw an
╰──────────────────────────────────╯ exception if no appropriate Worker was found.
Promise
Workers return React Promises. See the documentation here: https://github.com/reactphp/promise
Worker Modules
You can create a module of workers for Masonry using the WorkerModule class. It is a cross between a Worker and a Mediator mediating between it's internal workers, but exposing itself to other mediators as a worker.
╭─────────────────────────────────────╮
│ (i) WorkerModule : Mediator, Worker │
├─────────────────────────────────────┤
│ __construct([<Worker>]) │
│ <Mediator> addWorker(<Worker>) │
│ <Promise> process(<Task>) │
│ <string[]> getDescriptionTypes() │
╰─────────────────────────────────────╯