{"id":13616708,"url":"https://github.com/phpflo/phpflo","last_synced_at":"2025-04-05T22:06:25.027Z","repository":{"id":55016913,"uuid":"2221233","full_name":"phpflo/phpflo","owner":"phpflo","description":"Flow-based programming for PHP","archived":false,"fork":false,"pushed_at":"2017-05-06T07:02:32.000Z","size":261,"stargazers_count":218,"open_issues_count":3,"forks_count":21,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-03-29T21:04:52.389Z","etag":null,"topics":["fbp","flow","flow-based-programming","graph","noflo","php","php7"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phpflo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-08-17T11:08:01.000Z","updated_at":"2025-01-21T22:48:16.000Z","dependencies_parsed_at":"2022-08-14T09:10:36.392Z","dependency_job_id":null,"html_url":"https://github.com/phpflo/phpflo","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpflo%2Fphpflo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpflo%2Fphpflo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpflo%2Fphpflo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpflo%2Fphpflo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phpflo","download_url":"https://codeload.github.com/phpflo/phpflo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247406087,"owners_count":20933803,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fbp","flow","flow-based-programming","graph","noflo","php","php7"],"created_at":"2024-08-01T20:01:32.239Z","updated_at":"2025-04-05T22:06:25.007Z","avatar_url":"https://github.com/phpflo.png","language":"PHP","funding_links":[],"categories":["PHP"],"sub_categories":[],"readme":"PhpFlo: Flow-based programming for PHP\n==============================================\n\n[![Build Status](https://secure.travis-ci.org/phpflo/phpflo.png)](http://travis-ci.org/phpflo/phpflo)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phpflo/phpflo/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phpflo/phpflo/?branch=master)\n[![Code Coverage](https://scrutinizer-ci.com/g/phpflo/phpflo/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/phpflo/phpflo/?branch=master)\n[![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org)\n\nPhpFlo is a simple [flow-based programming](http://en.wikipedia.org/wiki/Flow-based_programming) implementation for PHP. It is a PHP port of [NoFlo](https://noflojs.org), a similar tool for Node.js. From WikiPedia:\n\n\u003e In computer science, flow-based programming (FBP) is a programming paradigm that defines applications as networks of \"black box\" processes, which exchange data across predefined connections by message passing, where the connections are specified externally to the processes. These black box processes can be reconnected endlessly to form different applications without having to be changed internally. FBP is thus naturally component-oriented.\n\nDevelopers used to the [Unix philosophy](http://en.wikipedia.org/wiki/Unix_philosophy) should be immediately familiar with FBP:\n\n\u003e This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.\n\nIt also fits well in Alan Kay's [original idea of object-oriented programming](http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en):\n\n\u003e I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages (so messaging came at the very beginning -- it took a while to see how to do messaging in a programming language efficiently enough to be useful).\n\nThe system has been heavily inspired by [J. Paul Morrison's](http://www.jpaulmorrison.com/) book [Flow-Based Programming](http://www.jpaulmorrison.com/fbp/#More).\n\nPhpFlo is still quite experimental, but may be useful for implementing flow control in PHP applications.\n\n## Installing\n\nPhpFlo can be installed from [Packagist.org](http://packagist.org/view/PhpFlo/PhpFlo) with the [composer](https://github.com/composer/composer) package manager. Just ensure your `composer.json` has the following:\n\n```sh\nphp composer.phar require phpflo/phpflo\n```\n\nThis gives you phpflo, [common](https://github.com/phpflo/phpflo-common), [fbp](https://github.com/phpflo/phpflo-fbp) and [flowtrace](https://github.com/phpflo/phpflo-flowtrace) packages, provided by a [monorepository](https://developer.atlassian.com/blog/2015/10/monorepos-in-git/). This helps us to manage the code in a easier way.\nEvery package is also split as a separate repository on github and is installable by itself via packagist.\n\n## Autoloading\n\nTo use PhpFlo, you need a [PHP Standards Group -compatible autoloader](http://groups.google.com/group/php-standards/web/psr-0-final-proposal). You can use the Composer-supplied autoloader:\n\n```php\n\u003c?php\n\nrequire 'vendor/autoload.php';\n```\n\n## Examples\n\nYou can find examples on how to use phpflo in the [phpflo-component](https://github.com/phpflo/phpflo-component) package.\n\n## Terminology\n\n* Component: individual, pluggable and reusable piece of software. In this case a PHP class implementing `PhpFlo\\Common\\ComponentInterface`\n* Graph: the control logic of a FBP application, can be either in programmatical or file format\n* Inport: inbound port of a component\n* Network: collection of processes connected by sockets. A running version of a graph\n* Outport: outbound port of a component\n* Process: an instance of a component that is running as part of a graph\n\n## Components\n\nA component is the main ingredient of flow-based programming. Component is a PHP class providing a set of input and output port handlers. These ports are used for connecting components to each other.\n\nPhpFlo processes (the boxes of a flow graph) are instances of a component, with the graph controlling connections between ports of components.\n\n### Structure of a component\n\nFunctionality a component provides:\n\n* List of inports (named inbound ports)\n* List of outports (named outbound ports)\n* Handler for component initialization that accepts configuration\n* Handler for connections for each inport\n\nA minimal component would look like the following:\n\n```php\n\u003c?php\n\nuse PhpFlo\\Core\\ComponentTrait;\nuse PhpFlo\\Common\\ComponentInterface;\n\nclass Forwarder implements ComponentInterface\n{\n    use ComponentTrait;\n    protected $description = \"This component receives data on a single input port and sends the same data out to the output port\";\n\n    public function __construct()\n    {\n        // Register ports\n        $this-\u003einPorts()-\u003eadd('in', ['datatype' =\u003e 'all']);\n        $this-\u003eoutPorts()-\u003eadd('out', ['datatype' =\u003e 'all']);\n\n        // Forward data when we receive it\n        $this-\u003einPorts()-\u003ein-\u003eon('data', array($this, 'forward'));\n\n        // Disconnect output port when input port disconnects\n        $this-\u003einPorts()-\u003ein-\u003eon('disconnect', array($this, 'disconnect'));\n    }\n\n    public function forward($data)\n    {\n        $this-\u003eoutPorts()-\u003eout-\u003esend($data);\n    }\n\n    public function disconnect()\n    {\n        $this-\u003eoutPorts-\u003eout-\u003edisconnect();\n    }\n}\n```\n\nAlternatively you can use ```PhpFlo\\Core\\Component``` via direct inheritance, which internally uses the trait.\nThis example component register two ports: _in_ and _out_. When it receives data in the _in_ port, it opens the _out_ port and sends the same data there. When the _in_ connection closes, it will also close the _out_ connection. So basically this component would be a simple repeater.\nYou can find more examples of components in the [phpflo-compoent](https://github.com/phpflo/phpflo-component) package.\nPlease mind that there's an mandatory second parameter for the \"add\" command. This array receives the port's meta information and has following defaults:\n \n``` php\n    $defaultAttributes = [\n        'datatype' =\u003e 'all',\n        'required' =\u003e false,\n        'cached' =\u003e false,\n        'addressable' =\u003e false,\n    ];\n```\nThis is but a subset of the available attributes, a noflo port can take.\n\n* _datatype_ defines the \"to be expected\" datatype of the dataflow. Currently _all_ datatypes from noflo are implemented\n* _required_ not implemented yet\n* _cached_ not implemented yet\n* _addressable_ decides if a port needs to be either an instance of Port (false) or ArrayPort (true)\n\nDefining the datatype is mandatory, since there is a port matching check during graph building, according to this matrix:\n\n| out\\in   | all      | bang     | string   | bool     | number   | int      | object   | array    | date     | function |\n| -------- |:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:| --------:|\n| all      |    x     |    x     |          |          |          |          |          |          |          |          |\n| bang     |    x     |    x     |          |          |          |          |          |          |          |          |\n| string   |    x     |    x     |    x     |          |          |          |          |          |          |          |\n| bool     |    x     |    x     |          |    x     |          |          |          |          |          |          |\n| number   |    x     |    x     |          |          |    x     |          |          |          |          |          |\n| int      |    x     |    x     |          |          |    x     |    x     |          |          |          |          |\n| object   |    x     |    x     |          |          |          |          |    x     |          |          |          |\n| array    |    x     |    x     |          |          |          |          |          |    x     |          |          |\n| date     |    x     |    x     |          |          |          |          |          |          |    x     |          |\n| function |    x     |    x     |          |          |          |          |          |          |          |    x     |\n\nThese types are only implicitly checked. There is no data validation during runtime!\n\n### Some words on component design\n\nComponents should aim to be reusable, to do one thing and do it well. This is why often it is a good idea to split functionality traditionally done in one function to multiple components. For example, counting lines in a text file could happen in the following way:\n\n* Filename is sent to a _Read File_ component\n* _Read File_ reads it and sends the contents onwards to _Split String_ component\n* _Split String_ splits the contents by newlines, and sends each line separately to a _Count_ component\n* _Count_ counts the number of packets it received, and sends the total to a _Output_ component\n* _Output_ displays the number\n\nThis way the whole logic of the application is in the graph, in how the components are wired together. And each of the components is easily reusable for other purposes.\n\nIf a component requires configuration, the good approach is to set sensible defaults in the component, and to allow them to be overridden via an input port. This method of configuration allows the settings to be kept in the graph itself, or for example to be read from a file or database, depending on the needs of the application.\n\nThe components should not depend on a particular global state, either, but instead attempt to keep the input and output ports their sole interface to the external world. There may be some exceptions, like a component that listens for HTTP requests or Redis pub-sub messages, but even in these cases the server, or subscription should be set up by the component itself.\n\n### Ports and events\n\nBeing a flow-based programming environment, the main action in PhpFlo happens through ports and their connections. There are five events that can be associated with ports:\n\n* _Attach_: there is a connection to the port\n* _Connect_: the port has started sending or receiving a data transmission\n* _Data_: an individual data packet in a transmission. There might be multiple depending on how a component operates\n* _Disconnect_: end of data transmission\n* _Detach_: A connection to the port has been removed\n\nIt depends on the nature of the component how these events may be handled. Most typical components do operations on a whole transmission, meaning that they should wait for the _disconnect_ event on inports before they act, but some components can also act on single _data_ packets coming in.\n\nWhen a port has no connections, meaning that it was initialized without a connection, or a _detach_ event has happened, it should do no operations regarding that port.\n\n## Graph file format\n\nIn addition to using PhpFlo in _embedded mode_ where you create the FBP graph programmatically (see [example](https://github.com/phpflo/phpflo/blob/master/examples/linecount/count.php)), you can also initialize and run graphs defined using a FBP file.\nThis format gives you the advantage of much less definition work, compared to the deprecated (but still valid) JSON files.\n\nIf you have older JSON definitions, you can still use them or convert then to FBP, using the dumper wrapped by the graph or directly from definition:\n```php\n$builder = new \\PhpFlo\\Core\\Builder\\ComponentFactory();\n$network = new PhpFlo\\Core\\Network($builder);\n$network-\u003eboot(__DIR__.'/count.json', $builder);\nfile_put_contents('./count.fbp', $network-\u003egetGraph()-\u003etoFbp());\n```\n\nThe PhpFlo FBP files declare the processes used in the FBP graph, and the connections between them. The file format is shared between PhpFlo and NoFlo, and looks like the following:\n\n```\nReadFile(ReadFile) out -\u003e in SplitbyLines(SplitStr)\nReadFile(ReadFile) error -\u003e in Display(Output)\nSplitbyLines(SplitStr) out -\u003e in CountLines(Counter)\nCountLines(Counter) count -\u003e in Display(Output)\n```\nOther supported formats are YAML and JSON, but keep in mind that the FBP domain specific language (DSL) should be the way to go if you want to use something like the noflo ui.\n\nJSON example:\n```json\n{\n    \"properties\": {\n        \"name\": \"Count lines in a file\"\n    },\n    \"processes\": {\n        \"ReadFile\": {\n            \"component\": \"ReadFile\"\n        },\n        \"SplitbyLines\": {\n            \"component\": \"SplitStr\"\n        },\n        \"CountLines\": {\n            \"component\": \"Counter\"\n        },\n        \"Display\": {\n            \"component\": \"Output\"\n        }\n    },\n    \"connections\": [\n        {\n            \"src\": {\n                \"process\": \"ReadFile\",\n                \"port\": \"out\"\n            },\n            \"tgt\": {\n                \"process\": \"SplitbyLines\",\n                \"port\": \"in\"\n            }\n        },\n        {\n            \"src\": {\n                \"process\": \"ReadFile\",\n                \"port\": \"error\"\n            },\n            \"tgt\": {\n                \"process\": \"Display\",\n                \"port\": \"in\"\n            }\n        },\n        {\n            \"src\": {\n                \"process\": \"SplitbyLines\",\n                \"port\": \"out\"\n            },\n            \"tgt\": {\n                \"process\": \"CountLines\",\n                \"port\": \"in\"\n            }\n        },\n        {\n            \"src\": {\n                \"process\": \"CountLines\",\n                \"port\": \"count\"\n            },\n            \"tgt\": {\n                \"process\": \"Display\",\n                \"port\": \"in\"\n            }\n        }\n    ]\n}\n```\n\nYAML example:\n```yaml\nproperties:\n    name: 'Count lines in a file'\ninitializers: {  }\nprocesses:\n    ReadFile:\n        component: ReadFile\n        metadata: { label: ReadFile }\n    SplitbyLines:\n        component: SplitStr\n        metadata: { label: SplitStr }\n    Display:\n        component: Output\n        metadata: { label: Output }\n    CountLines:\n        component: Counter\n        metadata: { label: Counter }\nconnections:\n    -\n        src: { process: ReadFile, port: OUT }\n        tgt: { process: SplitbyLines, port: IN }\n    -\n        src: { process: ReadFile, port: ERROR }\n        tgt: { process: Display, port: IN }\n    -\n        src: { process: SplitbyLines, port: OUT }\n        tgt: { process: CountLines, port: IN }\n    -\n        src: { process: CountLines, port: COUNT }\n        tgt: { process: Display, port: IN }\n```\n\nTo run a graph file, load it via the PhpFlow API:\n\n```php\n\u003c?php\n\n$builder = new PhpFlo\\Core\\Builder\\ComponentFactory();\n\n// create network\n$network = new PhpFlo\\Core\\Network($builder);\n$network\n    -\u003eboot(__DIR__.'/count.fbp')\n    -\u003erun($fileName, \"ReadFile\", \"source\")\n    -\u003eshutdown();\n```\n\nNote that after this the graph is _live_, meaning that you can add and remove nodes and connections, or send new _initial data_ to it. See [example](https://github.com/phpflo/phpflo/blob/master/examples/linecount/countFromJson.php).\n\nSince the network now also features the ```HookableInterface```, you can easily add callbacks on events for e.g. debugging purposes:\n\n```php\n\u003c?php\n$builder = new PhpFlo\\Core\\Builder\\ComponentFactory();\n\n// create network\n$network = new PhpFlo\\Core\\Network($builder);\n$network\n    -\u003ehook(\n        'data',\n        'trace',\n        function ($data, $socket) {\n            echo $socket-\u003egetId() . print_r($data, true) . \"\\n\";\n        }\n    )\n    -\u003eboot(__DIR__.'/count.fbp')\n    -\u003erun($fileName, \"ReadFile\", \"source\")\n    -\u003eshutdown();\n```\n\nAs you can see, there's a lot of potential in the callbacks, since they can also use object references to store and/or manipulate data, but natively receive socket and data from the supported events.\nThis feature is also used in [phpflo/flowtrace](https://github.com/phpflo/flowtrace) library, which decorates the ```Network``` class and adds PSR-3 compatible logging.\n\n## Testing\n\nTo be able to test your components, a trait is provided in [phpflo-common](https://github.com/phpflo/phpflo-core) which is automatically included as a dependency for phpflo. \n```PhpFlo\\Test\\ComponentTestTrait``` and ```PhpFlo\\Test\\Stub\\Trait``` contain the necessary tools to make testing easier.\n\n```php\n\u003c?php\nnamespace Tests\\PhpFlo\\Component;\n\nuse PhpFlo\\Common\\Test\\TestUtilityTrait;\n\nclass CounterTest extends \\PHPUnit_Framework_TestCase\n{\n    use TestUtilityTrait;\n\n    public function testBehavior()\n    {\n        $counter = new Counter();\n        $this-\u003econnectPorts($counter);\n\n        $this-\u003eassertTrue($counter-\u003einPorts()-\u003ehas('in'));\n        $this-\u003eassertTrue($counter-\u003eoutPorts()-\u003ehas('count'));\n\n        $counter-\u003eappendCount(1);\n        $counter-\u003eappendCount(\"2\");\n        $counter-\u003eappendCount(null);\n\n        $counter-\u003esendCount();\n\n        $countData = $this-\u003egetOutPortData('count');\n        $this-\u003eassertEquals(3, $countData[0]);\n    }\n}\n\n```\n\nWithin this code example you can see that the outPorts are available via ```getOutportData('alias'')``` method. This will always return an array of data sent to that specific port, because you can iteratively call ports within a component.\nOn codelevel this is nothing more than callbacks with an internal storage of your data, so you can test a component and its interaction in isolation.\nTo be able to use the component in testing you will first need to ```connectPorts($component)``` or separately ```connectInPorts($component); connectOutPorts($component)```, so phpflo won't throw any \"port not connected\" errors at you.\n\n## Development\n\nPhpFlo development happens on GitHub. Just fork the [main repository](https://github.com/phpflo/phpflo), make modifications and send a pull request.\n\nTo run the unit tests you need PHPUnit. Run the tests with in development:\n\n```sh\n$ bin/phpunit\n```\n\n### Some ideas\n\n* Use [phpDaemon](http://daemon.io/) to make the network run asynchronously, Node.js -like\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphpflo%2Fphpflo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphpflo%2Fphpflo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphpflo%2Fphpflo/lists"}