{"action":"created","issue":{"url":"https://api.github.com/repos/Codegyre/Robo/issues/252","labels_url":"https://api.github.com/repos/Codegyre/Robo/issues/252/labels{/name}","comments_url":"https://api.github.com/repos/Codegyre/Robo/issues/252/comments","events_url":"https://api.github.com/repos/Codegyre/Robo/issues/252/events","html_url":"https://github.com/Codegyre/Robo/pull/252","id":124436843,"number":252,"title":"Add task collections to Robo, to support rollbacks and transient objects.","user":{"login":"greg-1-anderson","id":612191,"avatar_url":"https://avatars.githubusercontent.com/u/612191?v=3","gravatar_id":"","url":"https://api.github.com/users/greg-1-anderson","html_url":"https://github.com/greg-1-anderson","followers_url":"https://api.github.com/users/greg-1-anderson/followers","following_url":"https://api.github.com/users/greg-1-anderson/following{/other_user}","gists_url":"https://api.github.com/users/greg-1-anderson/gists{/gist_id}","starred_url":"https://api.github.com/users/greg-1-anderson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/greg-1-anderson/subscriptions","organizations_url":"https://api.github.com/users/greg-1-anderson/orgs","repos_url":"https://api.github.com/users/greg-1-anderson/repos","events_url":"https://api.github.com/users/greg-1-anderson/events{/privacy}","received_events_url":"https://api.github.com/users/greg-1-anderson/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/Codegyre/Robo/labels/WIP","name":"WIP","color":"bfd4f2"}],"state":"open","locked":false,"assignee":null,"milestone":{"url":"https://api.github.com/repos/Codegyre/Robo/milestones/3","html_url":"https://github.com/Codegyre/Robo/milestones/1.0","labels_url":"https://api.github.com/repos/Codegyre/Robo/milestones/3/labels","id":1498461,"number":3,"title":"1.0","description":"Stable version of Robo","creator":{"login":"DavertMik","id":220264,"avatar_url":"https://avatars.githubusercontent.com/u/220264?v=3","gravatar_id":"","url":"https://api.github.com/users/DavertMik","html_url":"https://github.com/DavertMik","followers_url":"https://api.github.com/users/DavertMik/followers","following_url":"https://api.github.com/users/DavertMik/following{/other_user}","gists_url":"https://api.github.com/users/DavertMik/gists{/gist_id}","starred_url":"https://api.github.com/users/DavertMik/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DavertMik/subscriptions","organizations_url":"https://api.github.com/users/DavertMik/orgs","repos_url":"https://api.github.com/users/DavertMik/repos","events_url":"https://api.github.com/users/DavertMik/events{/privacy}","received_events_url":"https://api.github.com/users/DavertMik/received_events","type":"User","site_admin":false},"open_issues":4,"closed_issues":0,"state":"open","created_at":"2016-01-11T22:43:32Z","updated_at":"2016-01-11T22:46:28Z","due_on":null,"closed_at":null},"comments":34,"created_at":"2015-12-31T06:18:18Z","updated_at":"2016-01-30T17:02:30Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/Codegyre/Robo/pulls/252","html_url":"https://github.com/Codegyre/Robo/pull/252","diff_url":"https://github.com/Codegyre/Robo/pull/252.diff","patch_url":"https://github.com/Codegyre/Robo/pull/252.patch"},"body":"## Motivation ##\r\n\r\nRobo provides a very useful way to create quick scripts that are easy to write, and easy to read. Exception handling is avoided, so the script flow is generally linear and free from unnecessary conditionals.\r\n\r\nWhat if you wanted to ensure that a script always cleaned up its transient objects, and did not continue execution after one phase failed?  Robo has a useful stop-on-fail mechanism, supported in the `Result` class (hard exit after any task fails), and some stacks (taskFileSystemStack) also have a stop-on-fail switch. This does not always give the opportunity to clean up easily. More complicated solutions run the risk of adding a lot of conditionals to scripts. We want to keep things simple and linear, even as scripts become more complex.\r\n\r\n## Task Collections ##\r\n\r\nThis PR expands on the idea of a task stack (which currently only works for operations that are all of the same kind) to also allow the creation of task collections.  Multiple tasks of different types can be queued up in a single collection, and then executed.  If one task fails, the rest of the tasks in the chain are not called.  Rollback and completion tasks may also be provided to help clean up.\r\n\r\n## Example ##\r\n```\r\n$collection = $this->taskCollection();\r\n$this->taskFileSystemStack()\r\n      ->mkdir('logs')\r\n      ->touch('logs/.gitignore')\r\n      ->chgrp('logs', 'www-data')\r\n      ->symlink('/var/log/nginx/error.log', 'logs/error.log')\r\n      ->collect($collection, $this->taskDeleteDir('logs'));\r\n/// ... collect other tasks\r\n$collection->run();\r\n```\r\nThe idea here is not to try to record every last `chmod`, and roll them back individually, but rather to collect operation results together in a target directory that can be cleaned up if the tasks do not finish, or left intact if all goes well.  In the example above, `collect()` replaces the `run()` method; it serves the same purpose, except that execution is deferred, and a second task is provided to clean up in the event of a rollback. (n.b. Scripts that do not care about rollback can continue to use direct execution via the `run()` method.)\r\n\r\n`collect()` is provided via the Collectable trait, which is used by the BaseTask class, so all Robo tasks are collectable.  If the collected task implements the RollbackInterface, then it will be called on any rollback. If the collected task implements the CompletionInterface, then it will be called if all tasks in a collection complete without errors, or if there is a rollback.  Rollback and completion tasks are only called if the task itself was executed (or attempted to be executed).\r\n\r\nThis PR also provides for transient objects, such as temporary directories, which are always cleaned up at the completion phase of a task collection. Simple single-threaded scripts may create temporary directories without using a collection, and these will be removed when the program terminates.\r\n\r\n## Naming Things ##\r\n\r\nI put the collections in the Robo\\TaskCollection namespace, although Robo\\Task\\Collection might have done as well.  A collection of tasks technically isa task, and task collections can be nested; all the same, perhaps `$collection = $this->taskCollection();` should just be `$collection = new Collection();`.  Instead of `collect()`, I might have used `defer()`; other names might also do here.  As it stands, everything fits in nicely with existing Robo code, which can continue to work as it currently does.\r\n\r\nLet me know if you have any suggestions or feedback here."},"comment":{"url":"https://api.github.com/repos/Codegyre/Robo/issues/comments/177246037","html_url":"https://github.com/Codegyre/Robo/pull/252#issuecomment-177246037","issue_url":"https://api.github.com/repos/Codegyre/Robo/issues/252","id":177246037,"user":{"login":"greg-1-anderson","id":612191,"avatar_url":"https://avatars.githubusercontent.com/u/612191?v=3","gravatar_id":"","url":"https://api.github.com/users/greg-1-anderson","html_url":"https://github.com/greg-1-anderson","followers_url":"https://api.github.com/users/greg-1-anderson/followers","following_url":"https://api.github.com/users/greg-1-anderson/following{/other_user}","gists_url":"https://api.github.com/users/greg-1-anderson/gists{/gist_id}","starred_url":"https://api.github.com/users/greg-1-anderson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/greg-1-anderson/subscriptions","organizations_url":"https://api.github.com/users/greg-1-anderson/orgs","repos_url":"https://api.github.com/users/greg-1-anderson/repos","events_url":"https://api.github.com/users/greg-1-anderson/events{/privacy}","received_events_url":"https://api.github.com/users/greg-1-anderson/received_events","type":"User","site_admin":false},"created_at":"2016-01-30T17:02:30Z","updated_at":"2016-01-30T17:02:30Z","body":"Actually, the niftier thing to do would be to store the entire Result object from each task in the data of the top-level Result.  Then you would be able to just access `$result['a']['bar']`."}}