--- /dev/null
+<?php
+
+namespace Robo\Collection;
+
+use Robo\Task\BaseTask;
+use Robo\Contract\TaskInterface;
+use Robo\Contract\RollbackInterface;
+use Robo\Contract\CompletionInterface;
+use Robo\Contract\WrappedTaskInterface;
+
+/**
+ * Creates a task wrapper that will manage rollback and collection
+ * management to a task when it runs. Tasks are automatically
+ * wrapped in a CompletionWrapper when added to a task collection.
+ *
+ * Clients may need to wrap their task in a CompletionWrapper if it
+ * creates temporary objects.
+ *
+ * @see \Robo\Task\Filesystem\loadTasks::taskTmpDir
+ */
+class CompletionWrapper extends BaseTask implements WrappedTaskInterface
+{
+ /**
+ * @var \Robo\Collection\Collection
+ */
+ private $collection;
+
+ /**
+ * @var \Robo\Contract\TaskInterface
+ */
+ private $task;
+
+ /**
+ * @var NULL|\Robo\Contract\TaskInterface
+ */
+ private $rollbackTask;
+
+ /**
+ * Create a CompletionWrapper.
+ *
+ * Temporary tasks are always wrapped in a CompletionWrapper, as are
+ * any tasks that are added to a collection. If a temporary task
+ * is added to a collection, then it is first unwrapped from its
+ * CompletionWrapper (via its original() method), and then added to a
+ * new CompletionWrapper for the collection it is added to.
+ *
+ * In this way, when the CompletionWrapper is finally executed, the
+ * task's rollback and completion handlers will be registered on
+ * whichever collection it was registered on.
+ *
+ * @todo Why not CollectionInterface the type of the $collection argument?
+ *
+ * @param \Robo\Collection\Collection $collection
+ * @param \Robo\Contract\TaskInterface $task
+ * @param \Robo\Contract\TaskInterface|NULL $rollbackTask
+ */
+ public function __construct(Collection $collection, TaskInterface $task, TaskInterface $rollbackTask = null)
+ {
+ $this->collection = $collection;
+ $this->task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
+ $this->rollbackTask = $rollbackTask;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function original()
+ {
+ return $this->task;
+ }
+
+ /**
+ * Before running this task, register its rollback and completion
+ * handlers on its collection. The reason this class exists is to
+ * defer registration of rollback and completion tasks until 'run()' time.
+ *
+ * @return \Robo\Result
+ */
+ public function run()
+ {
+ if ($this->rollbackTask) {
+ $this->collection->registerRollback($this->rollbackTask);
+ }
+ if ($this->task instanceof RollbackInterface) {
+ $this->collection->registerRollback(new CallableTask([$this->task, 'rollback'], $this->task));
+ }
+ if ($this->task instanceof CompletionInterface) {
+ $this->collection->registerCompletion(new CallableTask([$this->task, 'complete'], $this->task));
+ }
+
+ return $this->task->run();
+ }
+
+ /**
+ * Make this wrapper object act like the class it wraps.
+ *
+ * @param string $function
+ * @param array $args
+ *
+ * @return mixed
+ */
+ public function __call($function, $args)
+ {
+ return call_user_func_array(array($this->task, $function), $args);
+ }
+}