diff --git a/README.md b/README.md index b089539..e3d05c9 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,28 @@ reopen task: $tm->reopen($task); ``` +dependencies: + +```php +$task1 = new Task(); +$task1->setDescription('a'); + +$task2 = new Task(); +$task2->setDescription('b'); + +$task1->addDependency($task2); + +// the order is important! +$tm->save($task2); +$tm->save($task1); + +$tm->clear(); // clear object cache + +$task1 = $tm->find('uuid-from-task1'); +$task2 = $task1->getDependencies()[0]; +echo $task2->getDesciption(); // "b" <- lazy loading +``` + ### QueryBuilder example: diff --git a/composer.json b/composer.json index f08e741..1e1e6bd 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ "nesbot/carbon": "^1.14", "doctrine/collections": "^1.3", "webmozart/path-util": "^2.0", - "webmozart/assert": "^1.0.1" + "webmozart/assert": "^1.0.1", + "ocramius/proxy-manager": "^1.0" }, "require-dev": { "phpunit/phpunit": "^4.0", diff --git a/src/Exception/ReferenceException.php b/src/Exception/ReferenceException.php new file mode 100644 index 0000000..a72e981 --- /dev/null +++ b/src/Exception/ReferenceException.php @@ -0,0 +1,10 @@ + + */ +class ReferenceException extends TaskwarriorException +{ +} diff --git a/src/Serializer/Handler/DependsHandler.php b/src/Serializer/Handler/DependsHandler.php new file mode 100644 index 0000000..fbda42d --- /dev/null +++ b/src/Serializer/Handler/DependsHandler.php @@ -0,0 +1,99 @@ + + */ +class DependsHandler implements SubscribingHandlerInterface +{ + /** + * @var TaskManager + */ + protected $taskManager; + + /** + * @param TaskManager $taskManager + */ + function __construct(TaskManager $taskManager) + { + $this->taskManager = $taskManager; + } + + /** + * @return array + */ + public static function getSubscribingMethods() + { + $methods = array(); + + $methods[] = array( + 'type' => 'Depends', + 'format' => 'json', + 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, + 'method' => 'deserialize' + ); + + $methods[] = array( + 'type' => 'Depends', + 'format' => 'json', + 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, + 'method' => 'serialize' + ); + + return $methods; + } + + /** + * @param VisitorInterface $visitor + * @param Task[] $tasks + * @param array $type + * @param Context $context + * @return string + * @throws ReferenceException + */ + public function serialize(VisitorInterface $visitor, $tasks, array $type, Context $context) + { + $list = []; + + foreach ($tasks as $task) { + if (!$task->getUuid()) { + throw new ReferenceException("you can't save a task that has dependencies to tasks that have not been saved"); + } + + $list[] = $task->getUuid(); + } + + return $visitor->visitString(implode(',', $list), $type, $context); + } + + /** + * @param VisitorInterface $visitor + * @param string $data + * @param array $type + * @return ArrayCollection + */ + public function deserialize(VisitorInterface $visitor, $data, array $type) + { + if (!$data) { + return new ArrayCollection(); + } + + $tasks = []; + + foreach (explode(',', $data) as $uuid) { + $tasks[] = $this->taskManager->getReference($uuid); + } + + return new ArrayCollection($tasks); + } +} diff --git a/src/Task.php b/src/Task.php index b6a265d..adcc979 100644 --- a/src/Task.php +++ b/src/Task.php @@ -4,6 +4,7 @@ namespace DavidBadura\Taskwarrior; use Carbon\Carbon; use DavidBadura\Taskwarrior\Exception\DatetimeParseException; +use Doctrine\Common\Collections\ArrayCollection; use JMS\Serializer\Annotation as JMS; /** @@ -126,6 +127,13 @@ class Task */ private $status; + /** + * @var Task[]|ArrayCollection + * + * @JMS\Type("Depends") + */ + private $depends; + /** * */ @@ -134,6 +142,7 @@ class Task $this->urgency = 0; $this->entry = new Carbon('now'); $this->status = self::STATUS_PENDING; + $this->depends = new ArrayCollection(); } /** @@ -268,6 +277,42 @@ class Task } } + /** + * @return Task[]|ArrayCollection + */ + public function getDependencies() + { + return $this->depends; + } + + /** + * @param Task $task + */ + public function addDependency(Task $task) + { + $this->depends->add($task); + } + + /** + * @param Task $task + */ + public function removeDependency(Task $task) + { + $this->depends->removeElement($task); + } + + /** + * @param Task[] $tasks + */ + public function setDependencies(array $tasks) + { + $this->depends = new ArrayCollection(); + + foreach ($tasks as $task) { + $this->addDependency($task); + } + } + /** * @return Recurring */ @@ -429,4 +474,4 @@ class Task $this->entry = new Carbon('now'); $this->status = self::STATUS_PENDING; } -} \ No newline at end of file +} diff --git a/src/TaskManager.php b/src/TaskManager.php index 8379c2a..e0b798c 100644 --- a/src/TaskManager.php +++ b/src/TaskManager.php @@ -4,10 +4,13 @@ namespace DavidBadura\Taskwarrior; use DavidBadura\Taskwarrior\Config\Context; use DavidBadura\Taskwarrior\Config\Report; +use DavidBadura\Taskwarrior\Exception\ReferenceException; use DavidBadura\Taskwarrior\Exception\TaskwarriorException; use DavidBadura\Taskwarrior\Query\QueryBuilder; use DavidBadura\Taskwarrior\Serializer\Handler\CarbonHandler; +use DavidBadura\Taskwarrior\Serializer\Handler\DependsHandler; use DavidBadura\Taskwarrior\Serializer\Handler\RecurringHandler; +use DavidBadura\Taskwarrior\Serializer\Handler\TaskHandler; use Doctrine\Common\Collections\ArrayCollection; use JMS\Serializer\Handler\HandlerRegistryInterface; use JMS\Serializer\JsonSerializationVisitor; @@ -15,6 +18,9 @@ use JMS\Serializer\Naming\CamelCaseNamingStrategy; use JMS\Serializer\Naming\SerializedNameAnnotationStrategy; use JMS\Serializer\Serializer; use JMS\Serializer\SerializerBuilder; +use ProxyManager\Factory\LazyLoadingGhostFactory; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; +use ProxyManager\Proxy\LazyLoadingInterface; /** * @author David Badura @@ -285,6 +291,38 @@ class TaskManager ->getResult(); } + /** + * @param string $uuid + * @return Task + */ + public function getReference($uuid) + { + if (isset($this->tasks[$uuid])) { + return $this->tasks[$uuid]; + } + + $self = $this; + $factory = new LazyLoadingValueHolderFactory(); + + $initializer = function ( + & $wrappedObject, + LazyLoadingInterface $proxy, + $method, + array $parameters, + & $initializer + ) use ($self, $uuid) { + + $initializer = null; + $wrappedObject = $this->export($uuid)[0]; + + return true; + }; + + $task = $factory->createProxy('DavidBadura\Taskwarrior\Task', $initializer); + + return $this->tasks[$uuid] = $task; + } + /** * @param Task $task */ @@ -302,7 +340,17 @@ class TaskManager { $json = $this->taskwarrior->export($filter); - return $this->getSerializer()->deserialize($json, 'array', 'json'); + $tasks = $this->getSerializer()->deserialize($json, 'array', 'json'); + + foreach ($tasks as $task) { + if ($task->getDependencies()) { + continue; + } + + $task->setDependencies(array()); + } + + return $tasks; } /** @@ -338,6 +386,16 @@ class TaskManager $params['recur'] = $task->getRecurring()->getValue(); } + $params['depends'] = []; + + foreach ($task->getDependencies() as $depend) { + if (!$depend->getUuid()) { + throw new ReferenceException("you can't save a task that has dependencies to tasks that have not been saved"); + } + + $params['depends'][] = $depend->getUuid(); + } + $this->taskwarrior->modify($params, $task->getUuid()); } @@ -419,6 +477,7 @@ class TaskManager ->configureHandlers(function (HandlerRegistryInterface $registry) { $registry->registerSubscribingHandler(new CarbonHandler()); $registry->registerSubscribingHandler(new RecurringHandler()); + $registry->registerSubscribingHandler(new DependsHandler($this)); }) ->addDefaultHandlers() ->setSerializationVisitor('json', $visitor) @@ -433,4 +492,4 @@ class TaskManager { return new self(new Taskwarrior()); } -} \ No newline at end of file +} diff --git a/src/Taskwarrior.php b/src/Taskwarrior.php index 0de5b32..d8ae36b 100644 --- a/src/Taskwarrior.php +++ b/src/Taskwarrior.php @@ -309,6 +309,14 @@ class Taskwarrior } } + if (array_key_exists('depends', $params)) { + if (is_array($params['depends'])) { + $options[] = 'depends:' . implode(',', $params['depends']); + } else { + $options[] = 'depends:' . $params['depends']; + } + } + if (array_key_exists('status', $params)) { $options[] = 'status:' . $params['status']; } diff --git a/tests/TaskManagerTest.php b/tests/TaskManagerTest.php index 740770a..6955328 100644 --- a/tests/TaskManagerTest.php +++ b/tests/TaskManagerTest.php @@ -787,6 +787,59 @@ class TaskManagerTest extends \PHPUnit_Framework_TestCase $this->assertCount(1, $this->taskManager->filterPending('(status:pending or status:waiting)')); } + public function testDependenciesException() + { + $this->setExpectedException('DavidBadura\Taskwarrior\Exception\ReferenceException'); + + $task1 = new Task(); + $task1->setDescription("a"); + + $task2 = new Task(); + $task2->setDescription("b"); + + $task3 = new Task(); + $task3->setDescription("c"); + + $task1->addDependency($task2); + $task1->addDependency($task3); + + $this->taskManager->save($task1); + } + + + public function testDependencies() + { + $task1 = new Task(); + $task1->setDescription("a"); + + $task2 = new Task(); + $task2->setDescription("b"); + + $task1->addDependency($task2); + + $this->taskManager->save($task2); + $this->taskManager->save($task1); + + $this->taskManager->clear(); + $temp1 = $this->taskManager->find($task1->getUuid()); + + $this->assertCount(1, $temp1->getDependencies()); + + $temp2 = $temp1->getDependencies()[0]; + + $this->assertInstanceOf('DavidBadura\Taskwarrior\Task', $temp2); + $this->assertEquals('b', $temp2->getDescription()); + + $temp1->removeDependency($temp2); + + $this->taskManager->save($temp1); + + $this->taskManager->clear(); + $temp1 = $this->taskManager->find($task1->getUuid()); + + $this->assertCount(0, $temp1->getDependencies()); + } + /** * @param string $string * @return \DateTime @@ -795,4 +848,4 @@ class TaskManagerTest extends \PHPUnit_Framework_TestCase { return new \Carbon\Carbon($string); } -} \ No newline at end of file +}