From 02fb844b2fea31b562b7655a05c58d32bb1da665 Mon Sep 17 00:00:00 2001 From: dantleech Date: Sat, 8 Jun 2013 21:44:25 +0200 Subject: [PATCH 1/5] Workspace Node Update Command and fixes - This command updates multiple nodes in a workspace based on a given SQL query --- .../Util/Console/Command/NodeDumpCommand.php | 4 +- .../Util/Console/Command/NodeTouchCommand.php | 50 ++----- .../Command/WorkspaceNodeUpdateCommand.php | 140 ++++++++++++++++++ .../Console/Command/WorkspaceQueryCommand.php | 13 +- src/PHPCR/Util/Console/Helper/PhpcrHelper.php | 95 ++++++++++++ .../Console/Command => }/Stubs/MockNode.php | 2 +- .../Stubs/MockNodeTypeManager.php | 2 +- tests/PHPCR/Tests/Stubs/MockRow.php | 10 ++ .../Util/Console/Command/BaseCommandTest.php | 8 +- .../Command/NodeTypeListCommandTest.php | 2 +- .../Command/NodeTypeRegisterCommandTest.php | 4 +- .../WorkspaceNodeUpdateCommandTest.php | 113 ++++++++++++++ 12 files changed, 382 insertions(+), 61 deletions(-) create mode 100644 src/PHPCR/Util/Console/Command/WorkspaceNodeUpdateCommand.php rename tests/PHPCR/Tests/{Util/Console/Command => }/Stubs/MockNode.php (66%) rename tests/PHPCR/Tests/{Util/Console/Command => }/Stubs/MockNodeTypeManager.php (73%) create mode 100644 tests/PHPCR/Tests/Stubs/MockRow.php create mode 100644 tests/PHPCR/Tests/Util/Console/Command/WorkspaceNodeUpdateCommandTest.php diff --git a/src/PHPCR/Util/Console/Command/NodeDumpCommand.php b/src/PHPCR/Util/Console/Command/NodeDumpCommand.php index 97625aa..ada4450 100644 --- a/src/PHPCR/Util/Console/Command/NodeDumpCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeDumpCommand.php @@ -65,7 +65,7 @@ protected function configure() ->addOption('ref-format', 'uuid', InputOption::VALUE_REQUIRED, 'Set the way references should be displayed when dumping reference properties - either "uuid" (default) or "path"') ->addArgument('identifier', InputArgument::OPTIONAL, 'Root path to dump', '/') ->setDescription('Dump subtrees of the content repository') - ->setHelp(<<setHelp(<<dump command recursively outputs the name of the node specified by the identifier argument and its subnodes in a yaml-like style. @@ -75,7 +75,7 @@ protected function configure() By default the command filters out system nodes and properties (i.e. nodes and properties with names starting with 'jcr:'), the sys_nodes option allows to turn this filter off. -EOF +HERE ) ; } diff --git a/src/PHPCR/Util/Console/Command/NodeTouchCommand.php b/src/PHPCR/Util/Console/Command/NodeTouchCommand.php index 4dfe17b..6f7aab8 100644 --- a/src/PHPCR/Util/Console/Command/NodeTouchCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeTouchCommand.php @@ -102,8 +102,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - /** @var $session SessionInterface */ - $session = $this->getHelper('phpcr')->getSession(); + $helper = $this->getHelper('phpcr'); + $session = $helper->getSession(); $path = $input->getArgument('path'); $type = $input->getOption('type'); @@ -155,45 +155,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $node = $parentNode->addNode($nodeName, $type); } - foreach ($setProp as $set) { - $parts = explode('=', $set); - $output->writeln(sprintf( - ' > Setting property %s to %s', - $parts[0], $parts[1] - )); - $node->setProperty($parts[0], $parts[1]); - } - - foreach ($removeProp as $unset) { - $output->writeln(sprintf( - ' > Unsetting property %s', - $unset - )); - $node->setProperty($unset, null); - } - - foreach ($addMixins as $addMixin) { - $node->addMixin($addMixin); - } - - foreach ($removeMixins as $removeMixin) { - $node->removeMixin($removeMixin); - } - - if ($dump) { - $output->writeln('Node dump: '); - /** @var $property PropertyInterface */ - foreach ($node->getProperties() as $property) { - $value = $property->getValue(); - if (!is_string($value)) { - $value = print_r($value, true); - } - $output->writeln(sprintf(' - %s = %s', - $property->getName(), - $value - )); - } - } + $helper->processNode($output, $node, array( + 'setProps' => $setProp, + 'removeProps' => $removeProp, + 'addMixins' => $addMixins, + 'removeMixins' => $removeMixins, + 'dump' => $dump, + )); $session->save(); } diff --git a/src/PHPCR/Util/Console/Command/WorkspaceNodeUpdateCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceNodeUpdateCommand.php new file mode 100644 index 0000000..5707962 --- /dev/null +++ b/src/PHPCR/Util/Console/Command/WorkspaceNodeUpdateCommand.php @@ -0,0 +1,140 @@ + + */ +class WorkspaceNodeUpdateCommand extends Command +{ + /** + * {@inheritDoc} + */ + protected function configure() + { + parent::configure(); + + $this->setName('phpcr:workspace:node:update') + ->addArgument( + 'query', + InputArgument::REQUIRED, + 'A query statement to execute') + ->addOption('force', null, + InputOption::VALUE_NONE, + 'Use to bypass the confirmation dialog' + ) + ->addOption( + 'language', 'l', + InputOption::VALUE_OPTIONAL, + 'The query language (sql, jcr_sql2') + + ->addOption('set-prop', 'p', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Set node property on nodes use foo=bar' + ) + ->addOption('remove-prop', 'r', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Remove property from nodes' + ) + ->addOption('add-mixin', null, + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Add a mixin to the nodes' + ) + ->addOption('remove-mixin', null, + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Remove mixin from the nodes' + ) + ->setDescription('Command to manipulate the nodes in the workspace.') + ->setHelp(<<workspace:node:update command updates properties of nodes found +by the given JCR query. + + php bin/phpcr workspace:node:update "SELECT FROM nt:unstructured" --set-prop=foo=bar + +The options for manipulating nodes are the same as with the +node:touch command and +can be repeated to update multiple properties. +HERE +); + } + + /** + * {@inheritDoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $sql = $input->getArgument('query'); + $language = strtoupper($input->getOption('language')); + $setProp = $input->getOption('set-prop'); + $removeProp = $input->getOption('remove-prop'); + $addMixins = $input->getOption('add-mixin'); + $removeMixins = $input->getOption('remove-mixin'); + $force = $input->getOption('force'); + + $helper = $this->getHelper('phpcr'); + $session = $helper->getSession(); + + $query = $helper->createQuery($language, $sql); + + $start = microtime(true); + $result = $query->execute(); + $elapsed = microtime(true) - $start; + + if (!$force) { + $dialog = new DialogHelper(); + $force = $dialog->askConfirmation($output, sprintf( + 'About to update %d nodes, do you want to continue Y/N ?', + count($result) + ), false); + } + + foreach ($result as $i => $row) { + $output->writeln(sprintf( + "Updating node %s.", + $row->getPath() + )); + + $node = $row->getNode(); + + $helper->processNode($output, $node, array( + 'setProp' => $setProp, + 'removeProp' => $removeProp, + 'addMixins' => $addMixins, + 'removeMixins' => $removeMixins, + )); + } + + $output->writeln(sprintf('%.2f seconds', $elapsed)); + + return 0; + } +} diff --git a/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php index 0c85319..399dc3d 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php @@ -61,17 +61,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $limit = $input->getOption('limit'); $offset = $input->getOption('offset'); - $session = $this->getHelper('phpcr')->getSession(); - $qm = $session->getWorkspace()->getQueryManager(); + $helper = $this->getHelper('phpcr'); + $session = $helper->getSession(); - if (!defined('\PHPCR\Query\QueryInterface::'.$language)) { - throw new \RuntimeException(sprintf( - "Query language '\\PHPCR\\Query\\QueryInterface::%s' not defined.", - $language - )); - } - - $query = $qm->createQuery($sql, constant('\PHPCR\Query\QueryInterface::'.$language)); + $query = $helper->createQuery($language, $sql); if ($limit) { $query->setLimit($limit); diff --git a/src/PHPCR/Util/Console/Helper/PhpcrHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrHelper.php index d9c1ea3..7ce4a3d 100644 --- a/src/PHPCR/Util/Console/Helper/PhpcrHelper.php +++ b/src/PHPCR/Util/Console/Helper/PhpcrHelper.php @@ -23,6 +23,7 @@ use Symfony\Component\Console\Helper\Helper; use PHPCR\SessionInterface; +use Symfony\Component\Console\Output\OutputInterface; /** * Helper class to make the session instance available to console commands @@ -63,4 +64,98 @@ public function getName() { return 'phpcr'; } + + /** + * Process - or update - a given node. + * Provides common processing for both touch + * and update commands. + */ + public function processNode(OutputInterface $output, $node, $options) + { + $options = array_merge(array( + 'setProp' => array(), + 'removeProp' => array(), + 'addMixins' => array(), + 'removeMixins' => array(), + 'dump' => false, + ), $options); + + foreach ($options['setProp'] as $set) { + $parts = explode('=', $set); + $output->writeln(sprintf( + ' > Setting property %s to %s', + $parts[0], $parts[1] + )); + $node->setProperty($parts[0], $parts[1]); + } + + foreach ($options['removeProp'] as $unset) { + $output->writeln(sprintf( + ' > Unsetting property %s', + $unset + )); + $node->setProperty($unset, null); + } + + foreach ($options['addMixins'] as $addMixin) { + $output->writeln( + ' > Adding mixin %s', + $addMixin + ); + + $node->addMixin($addMixin); + } + + foreach ($options['removeMixins'] as $removeMixin) { + $output->writeln( + ' > Removing mixin %s', + $removeMixin + ); + + $node->removeMixin($removeMixin); + } + + if ($options['dump']) { + $output->writeln('Node dump: '); + /** @var $property PropertyInterface */ + foreach ($node->getProperties() as $property) { + $value = $property->getValue(); + if (!is_string($value)) { + $value = print_r($value, true); + } + $output->writeln(sprintf(' - %s = %s', + $property->getName(), + $value + )); + } + } + } + + /** + * Create a PHPCR query using the given language and + * query string. + * + * @param string Language type - SQL, SQL2 + * @param string JCR Query + * + * @return PHPCR/QueryInterface + */ + public function createQuery($language, $sql) + { + $session = $this->getSession(); + $qm = $session->getWorkspace()->getQueryManager(); + $language = strtoupper($language); + + $constantName = '\PHPCR\Query\QueryInterface::'.$language; + if (!defined($constantName)) { + throw new \RuntimeException(sprintf( + "Query language '\\PHPCR\\Query\\QueryInterface::%s' not defined.", + $language + )); + } + + $query = $qm->createQuery($sql, constant($constantName)); + + return $query; + } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/Stubs/MockNode.php b/tests/PHPCR/Tests/Stubs/MockNode.php similarity index 66% rename from tests/PHPCR/Tests/Util/Console/Command/Stubs/MockNode.php rename to tests/PHPCR/Tests/Stubs/MockNode.php index 13c3698..ea93721 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/Stubs/MockNode.php +++ b/tests/PHPCR/Tests/Stubs/MockNode.php @@ -1,6 +1,6 @@ workspace = $this->getMock('PHPCR\WorkspaceInterface'); $this->repository = $this->getMock('PHPCR\RepositoryInterface'); - $this->node1 = $this->getMock('PHPCR\Tests\Util\Console\Command\Stubs\MockNode'); + $this->row1 = $this->getMock('PHPCR\Tests\Stubs\MockRow'); + $this->node1 = $this->getMock('PHPCR\Tests\Stubs\MockNode'); $this->dumperHelper = $this->getMockBuilder( 'PHPCR\Util\Console\Helper\PhpcrConsoleDumperHelper' diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodeTypeListCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodeTypeListCommandTest.php index 5e7e13e..e7b28f9 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodeTypeListCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodeTypeListCommandTest.php @@ -12,7 +12,7 @@ public function setUp() parent::setUp(); $this->application->add(new NodeTypeListCommand()); $this->nodeTypeManager = $this->getMockBuilder( - 'PHPCR\Tests\Util\Console\Command\Stubs\MockNodeTypeManager' + 'PHPCR\Tests\Stubs\MockNodeTypeManager' )->disableOriginalConstructor()->getMock(); } diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodeTypeRegisterCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodeTypeRegisterCommandTest.php index 7544c2b..b023b72 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodeTypeRegisterCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodeTypeRegisterCommandTest.php @@ -5,8 +5,6 @@ use Symfony\Component\Console\Application; use PHPCR\Util\Console\Command\NodeTypeRegisterCommand; -require_once(__DIR__.'/Stubs/MockNodeTypeManager.php'); - class NodeTypeRegisterCommandTest extends BaseCommandTest { public function setUp() @@ -14,7 +12,7 @@ public function setUp() parent::setUp(); $this->application->add(new NodeTypeRegisterCommand()); $this->nodeTypeManager = $this->getMockBuilder( - 'PHPCR\Tests\Util\Console\Command\Stubs\MockNodeTypeManager' + 'PHPCR\Tests\Stubs\MockNodeTypeManager' )->disableOriginalConstructor()->getMock(); } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceNodeUpdateCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceNodeUpdateCommandTest.php new file mode 100644 index 0000000..ef9da9f --- /dev/null +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceNodeUpdateCommandTest.php @@ -0,0 +1,113 @@ +application->add(new WorkspaceNodeUpdateCommand()); + $this->queryManager = $this->getMock( + 'PHPCR\Query\QueryManagerInterface' + ); + $this->query = $this->getMock('PHPCR\Query\QueryInterface'); + } + + public function provideNodeUpdate() + { + return array( + array(array( + 'setProp' => array(array('foo', 'bar')), + 'removeProp' => array('bar'), + 'addMixin' => array('mixin1'), + 'removeMixin' => array('mixin1'), + )), + ); + } + + /** + * @dataProvider provideNodeUpdate + */ + public function testNodeUpdate($options) + { + $options = array_merge(array( + 'setProp' => array(), + 'removeProp' => array(), + 'addMixin' => array(), + 'removeMixin' => array(), + ), $options); + + $this->session->expects($this->once()) + ->method('getWorkspace') + ->will($this->returnValue($this->workspace)); + $this->workspace->expects($this->once()) + ->method('getQueryManager') + ->will($this->returnValue($this->queryManager)); + $this->queryManager->expects($this->once()) + ->method('createQuery') + ->with('SELECT foo FROM foo', 'sql') + ->will($this->returnValue($this->query)); + $this->query->expects($this->once()) + ->method('execute') + ->will($this->returnValue(array( + $this->row1, + ))); + $this->row1->expects($this->once()) + ->method('getNode') + ->will($this->returnValue($this->node1)); + + $args = array( + 'query' => 'SELECT foo FROM foo', + '--language' => 'sql', + '--force' => true, + '--set-prop' => array(), + '--remove-prop' => array(), + '--add-mixin' => array(), + '--remove-mixin' => array(), + ); + + foreach ($options['setProp'] as $setProp) + { + list($prop, $value) = $setProp; + $this->node1->expects($this->at(0)) + ->method('setProperty') + ->with($prop, $value); + + $args['--set-prop'][] = $prop.'='.$value; + } + + foreach ($options['removeProp'] as $prop) + { + $this->node1->expects($this->at(1)) + ->method('setProperty') + ->with($prop, null); + + $args['--remove-prop'][] = $prop; + } + + foreach ($options['addMixin'] as $mixin) + { + $this->node1->expects($this->once()) + ->method('addMixin') + ->with($mixin); + + $args['--add-mixin'][] = $mixin; + } + + foreach ($options['removeMixin'] as $mixin) + { + $this->node1->expects($this->once()) + ->method('removeMixin') + ->with($mixin); + + $args['--remove-mixin'][] = $mixin; + } + + $ct = $this->executeCommand('phpcr:workspace:node:update', $args); + } +} From a7e711fe867aaa03c5df2611799c4831a316646d Mon Sep 17 00:00:00 2001 From: dantleech Date: Wed, 12 Jun 2013 14:47:14 +0200 Subject: [PATCH 2/5] Refactored to use phpcr_cli helper --- .../Util/Console/Command/BaseCommand.php | 22 +++ .../Util/Console/Command/NodeDumpCommand.php | 5 +- .../Util/Console/Command/NodeTouchCommand.php | 6 +- .../Console/Command/NodeTypeListCommand.php | 4 +- .../Command/NodeTypeRegisterCommand.php | 4 +- ...dateCommand.php => NodesUpdateCommand.php} | 104 +++++++---- .../Console/Command/WorkspaceQueryCommand.php | 7 +- .../Util/Console/Helper/PhpcrCliHelper.php | 162 ++++++++++++++++++ src/PHPCR/Util/Console/Helper/PhpcrHelper.php | 94 ---------- test | 0 .../Util/Console/Command/BaseCommandTest.php | 3 +- ...andTest.php => NodesUpdateCommandTest.php} | 61 +++++-- 12 files changed, 322 insertions(+), 150 deletions(-) create mode 100644 src/PHPCR/Util/Console/Command/BaseCommand.php rename src/PHPCR/Util/Console/Command/{WorkspaceNodeUpdateCommand.php => NodesUpdateCommand.php} (56%) create mode 100644 src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php create mode 100644 test rename tests/PHPCR/Tests/Util/Console/Command/{WorkspaceNodeUpdateCommandTest.php => NodesUpdateCommandTest.php} (62%) diff --git a/src/PHPCR/Util/Console/Command/BaseCommand.php b/src/PHPCR/Util/Console/Command/BaseCommand.php new file mode 100644 index 0000000..1c60887 --- /dev/null +++ b/src/PHPCR/Util/Console/Command/BaseCommand.php @@ -0,0 +1,22 @@ +getHelper('phpcr')->getSession(); + } + + protected function getPhpcrCliHelper() + { + $phpcrCliHelper = new PhpcrCliHelper($this->getPhpcrSession()); + return $phpcrCliHelper; + } +} diff --git a/src/PHPCR/Util/Console/Command/NodeDumpCommand.php b/src/PHPCR/Util/Console/Command/NodeDumpCommand.php index ada4450..fdc5d3a 100644 --- a/src/PHPCR/Util/Console/Command/NodeDumpCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeDumpCommand.php @@ -22,7 +22,6 @@ namespace PHPCR\Util\Console\Command; use PHPCR\Util\UUIDHelper; -use Symfony\Component\Console\Command\Command; use PHPCR\ItemNotFoundException; use PHPCR\RepositoryException; use PHPCR\PathNotFoundException; @@ -42,7 +41,7 @@ * @author Daniel Barsotti * @author Daniel Leech */ -class NodeDumpCommand extends Command +class NodeDumpCommand extends BaseCommand { /** * Limit after which to cut lines when dumping properties @@ -95,7 +94,7 @@ public function setDumpMaxLineLength($length) */ protected function execute(InputInterface $input, OutputInterface $output) { - $session = $this->getHelper('phpcr')->getSession(); + $session = $this->getPhpcrSession(); $dumperHelper = $this->getHelper('phpcr_console_dumper'); // node to dump diff --git a/src/PHPCR/Util/Console/Command/NodeTouchCommand.php b/src/PHPCR/Util/Console/Command/NodeTouchCommand.php index 6f7aab8..7833658 100644 --- a/src/PHPCR/Util/Console/Command/NodeTouchCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeTouchCommand.php @@ -36,7 +36,7 @@ * * @author Daniel Leech */ -class NodeTouchCommand extends Command +class NodeTouchCommand extends BaseCommand { /** * {@inheritDoc} @@ -102,8 +102,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $helper = $this->getHelper('phpcr'); - $session = $helper->getSession(); + $helper = $this->getPhpcrCliHelper(); + $session = $this->getPhpcrSession(); $path = $input->getArgument('path'); $type = $input->getOption('type'); diff --git a/src/PHPCR/Util/Console/Command/NodeTypeListCommand.php b/src/PHPCR/Util/Console/Command/NodeTypeListCommand.php index 8bace82..d6c3fd4 100644 --- a/src/PHPCR/Util/Console/Command/NodeTypeListCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeTypeListCommand.php @@ -30,7 +30,7 @@ * * @author Daniel Leech */ -class NodeTypeListCommand extends Command +class NodeTypeListCommand extends BaseCommand { /** * {@inheritDoc} @@ -53,7 +53,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $session = $this->getHelper('phpcr')->getSession(); + $session = $this->getPhpcrSession(); $ntm = $session->getWorkspace()->getNodeTypeManager(); $nodeTypes = $ntm->getAllNodeTypes(); diff --git a/src/PHPCR/Util/Console/Command/NodeTypeRegisterCommand.php b/src/PHPCR/Util/Console/Command/NodeTypeRegisterCommand.php index 9ef93f0..f969aba 100644 --- a/src/PHPCR/Util/Console/Command/NodeTypeRegisterCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeTypeRegisterCommand.php @@ -39,7 +39,7 @@ * * @author Uwe Jäger */ -class NodeTypeRegisterCommand extends Command +class NodeTypeRegisterCommand extends BaseCommand { /** * {@inheritDoc} @@ -91,7 +91,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $cnd = file_get_contents($cnd_file); $allowUpdate = $input->getOption('allow-update'); - $session = $this->getHelper('phpcr')->getSession(); + $session = $this->getPhpcrSession(); try { $this->updateFromCnd($output, $session, $cnd, $allowUpdate); diff --git a/src/PHPCR/Util/Console/Command/WorkspaceNodeUpdateCommand.php b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php similarity index 56% rename from src/PHPCR/Util/Console/Command/WorkspaceNodeUpdateCommand.php rename to src/PHPCR/Util/Console/Command/NodesUpdateCommand.php index 5707962..7f1c304 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceNodeUpdateCommand.php +++ b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php @@ -21,12 +21,11 @@ namespace PHPCR\Util\Console\Command; -use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Helper\DialogHelper; +use PHPCR\Util\Console\Command\BaseCommand; /** * Command which can update the properties of nodes found @@ -34,7 +33,7 @@ * * @author Daniel Leech */ -class WorkspaceNodeUpdateCommand extends Command +class NodesUpdateCommand extends BaseCommand { /** * {@inheritDoc} @@ -43,19 +42,23 @@ protected function configure() { parent::configure(); - $this->setName('phpcr:workspace:node:update') - ->addArgument( - 'query', - InputArgument::REQUIRED, - 'A query statement to execute') - ->addOption('force', null, - InputOption::VALUE_NONE, - 'Use to bypass the confirmation dialog' + $this->setName('phpcr:nodes:update') + ->addOption( + 'type', 't', + InputOption::VALUE_REQUIRED, + 'Update nodes of given node type, e.g. nt:unstructured' ) ->addOption( - 'language', 'l', + 'where', 'w', + InputOption::VALUE_OPTIONAL, + 'Specify the update criteria in SQL, e.g. "foobar = \'foo\' AND barfoo = \'bar\'"' + ) + ->addOption( + 'query-language', 'l', InputOption::VALUE_OPTIONAL, - 'The query language (sql, jcr_sql2') + 'The query language (sql, jcr_sql2', + 'jcr_sql2' + ) ->addOption('set-prop', 'p', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, @@ -73,16 +76,22 @@ protected function configure() InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Remove mixin from the nodes' ) + ->addOption('force', null, + InputOption::VALUE_NONE, + 'Use to bypass the confirmation dialog' + ) ->setDescription('Command to manipulate the nodes in the workspace.') ->setHelp(<<workspace:node:update command updates properties of nodes found -by the given JCR query. +The nodes:update command updates properties of nodes of type x matching +the given select criteria. - php bin/phpcr workspace:node:update "SELECT FROM nt:unstructured" --set-prop=foo=bar + php bin/phpcr nodes:update --type="nt:unstructured" --where="foo='bar'" --set-prop=foo=bar The options for manipulating nodes are the same as with the node:touch command and can be repeated to update multiple properties. + +The --where option corresponds to the "where" part of a standard query. HERE ); } @@ -92,34 +101,45 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $sql = $input->getArgument('query'); - $language = strtoupper($input->getOption('language')); + $type = $input->getOption('type'); + $where = $input->getOption('where'); + $queryLanguage = strtoupper($input->getOption('query-language')); $setProp = $input->getOption('set-prop'); $removeProp = $input->getOption('remove-prop'); $addMixins = $input->getOption('add-mixin'); $removeMixins = $input->getOption('remove-mixin'); - $force = $input->getOption('force'); + $noInteraction = $input->getOption('no-interaction'); + $helper = $this->getPhpcrCliHelper(); + $session = $this->getPhpcrSession(); + $this->dialog = new DialogHelper(); + + if (!$type) { + throw new \InvalidArgumentException('You must provide the "type" option, to select all nodes specify the type "nt:base"'); + } - $helper = $this->getHelper('phpcr'); - $session = $helper->getSession(); + if ($where) { + $sql = sprintf('SELECT * FROM [%s] WHERE %s', $type, $where); + } else { + $sql = sprintf('SELECT * FROM [%s]', $type); + } - $query = $helper->createQuery($language, $sql); + $output->writeln($sql); + $query = $helper->createQuery($queryLanguage, $sql); $start = microtime(true); $result = $query->execute(); $elapsed = microtime(true) - $start; - if (!$force) { - $dialog = new DialogHelper(); - $force = $dialog->askConfirmation($output, sprintf( - 'About to update %d nodes, do you want to continue Y/N ?', - count($result) - ), false); + if (!$noInteraction) { + if (false === $this->getAction($output, $result)) { + return 0; + } } foreach ($result as $i => $row) { $output->writeln(sprintf( - "Updating node %s.", + "Updating node:[%d] %s.", + $i, $row->getPath() )); @@ -137,4 +157,30 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; } + + protected function getAction($output, $result) + { + $response = strtoupper($this->dialog->ask($output, sprintf( + 'About to update %d nodes. Enter "Y" to continue, "N" to cancel or "L" to list.', + count($result->getRows()) + ), false)); + + if ($response == 'L') { + foreach ($result as $i => $row) { + $output->writeln(sprintf(' - [%d] %s', $i, $row->getPath())); + } + + return $this->getAction($output, $result); + } + + if ($response == 'N') { + return false; + } + + if ($response == 'Y') { + return true; + } + + return $this->getAction($output, $result); + } } diff --git a/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php index 399dc3d..a8ed44e 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php @@ -32,8 +32,9 @@ * resulting nodes. * * @author Daniel Barsotti + * @author Daniel Leech */ -class WorkspaceQueryCommand extends Command +class WorkspaceQueryCommand extends BaseCommand { /** * {@inheritDoc} @@ -61,8 +62,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $limit = $input->getOption('limit'); $offset = $input->getOption('offset'); - $helper = $this->getHelper('phpcr'); - $session = $helper->getSession(); + $helper = $this->getPhpcrCliHelper(); + $session = $this->getPhpcrSession(); $query = $helper->createQuery($language, $sql); diff --git a/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php new file mode 100644 index 0000000..6ba7824 --- /dev/null +++ b/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php @@ -0,0 +1,162 @@ +session = $session; + } + + /** + * Get the session + * + * @return SessionInterface + */ + public function getSession() + { + return $this->session; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'phpcr_cli'; + } + + /** + * Process - or update - a given node. + * Provides common processing for both touch + * and update commands. + */ + public function processNode(OutputInterface $output, $node, $options) + { + $options = array_merge(array( + 'setProp' => array(), + 'removeProp' => array(), + 'addMixins' => array(), + 'removeMixins' => array(), + 'dump' => false, + ), $options); + + foreach ($options['setProp'] as $set) { + $parts = explode('=', $set); + $output->writeln(sprintf( + ' > Setting property %s to %s', + $parts[0], $parts[1] + )); + $node->setProperty($parts[0], $parts[1]); + } + + foreach ($options['removeProp'] as $unset) { + $output->writeln(sprintf( + ' > Unsetting property %s', + $unset + )); + $node->setProperty($unset, null); + } + + foreach ($options['addMixins'] as $addMixin) { + $output->writeln( + ' > Adding mixin %s', + $addMixin + ); + + $node->addMixin($addMixin); + } + + foreach ($options['removeMixins'] as $removeMixin) { + $output->writeln( + ' > Removing mixin %s', + $removeMixin + ); + + $node->removeMixin($removeMixin); + } + + if ($options['dump']) { + $output->writeln('Node dump: '); + /** @var $property PropertyInterface */ + foreach ($node->getProperties() as $property) { + $value = $property->getValue(); + if (!is_string($value)) { + $value = print_r($value, true); + } + $output->writeln(sprintf(' - %s = %s', + $property->getName(), + $value + )); + } + } + } + + /** + * Create a PHPCR query using the given language and + * query string. + * + * @param string Language type - SQL, SQL2 + * @param string JCR Query + * + * @return PHPCR/QueryInterface + */ + public function createQuery($language, $sql) + { + $session = $this->getSession(); + $qm = $session->getWorkspace()->getQueryManager(); + $language = strtoupper($language); + + $constantName = '\PHPCR\Query\QueryInterface::'.$language; + if (!defined($constantName)) { + throw new \RuntimeException(sprintf( + "Query language '\\PHPCR\\Query\\QueryInterface::%s' not defined.", + $language + )); + } + + $query = $qm->createQuery($sql, constant($constantName)); + + return $query; + } +} + diff --git a/src/PHPCR/Util/Console/Helper/PhpcrHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrHelper.php index 7ce4a3d..3cb5db3 100644 --- a/src/PHPCR/Util/Console/Helper/PhpcrHelper.php +++ b/src/PHPCR/Util/Console/Helper/PhpcrHelper.php @@ -64,98 +64,4 @@ public function getName() { return 'phpcr'; } - - /** - * Process - or update - a given node. - * Provides common processing for both touch - * and update commands. - */ - public function processNode(OutputInterface $output, $node, $options) - { - $options = array_merge(array( - 'setProp' => array(), - 'removeProp' => array(), - 'addMixins' => array(), - 'removeMixins' => array(), - 'dump' => false, - ), $options); - - foreach ($options['setProp'] as $set) { - $parts = explode('=', $set); - $output->writeln(sprintf( - ' > Setting property %s to %s', - $parts[0], $parts[1] - )); - $node->setProperty($parts[0], $parts[1]); - } - - foreach ($options['removeProp'] as $unset) { - $output->writeln(sprintf( - ' > Unsetting property %s', - $unset - )); - $node->setProperty($unset, null); - } - - foreach ($options['addMixins'] as $addMixin) { - $output->writeln( - ' > Adding mixin %s', - $addMixin - ); - - $node->addMixin($addMixin); - } - - foreach ($options['removeMixins'] as $removeMixin) { - $output->writeln( - ' > Removing mixin %s', - $removeMixin - ); - - $node->removeMixin($removeMixin); - } - - if ($options['dump']) { - $output->writeln('Node dump: '); - /** @var $property PropertyInterface */ - foreach ($node->getProperties() as $property) { - $value = $property->getValue(); - if (!is_string($value)) { - $value = print_r($value, true); - } - $output->writeln(sprintf(' - %s = %s', - $property->getName(), - $value - )); - } - } - } - - /** - * Create a PHPCR query using the given language and - * query string. - * - * @param string Language type - SQL, SQL2 - * @param string JCR Query - * - * @return PHPCR/QueryInterface - */ - public function createQuery($language, $sql) - { - $session = $this->getSession(); - $qm = $session->getWorkspace()->getQueryManager(); - $language = strtoupper($language); - - $constantName = '\PHPCR\Query\QueryInterface::'.$language; - if (!defined($constantName)) { - throw new \RuntimeException(sprintf( - "Query language '\\PHPCR\\Query\\QueryInterface::%s' not defined.", - $language - )); - } - - $query = $qm->createQuery($sql, constant($constantName)); - - return $query; - } } diff --git a/test b/test new file mode 100644 index 0000000..e69de29 diff --git a/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php index 1b7e358..df021ea 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php @@ -16,7 +16,6 @@ require_once(__DIR__.'/../../../Stubs/MockNodeTypeManager.php'); require_once(__DIR__.'/../../../Stubs/MockRow.php'); - abstract class BaseCommandTest extends \PHPUnit_Framework_TestCase { /** @var SessionInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -75,7 +74,7 @@ public function executeCommand($name, $args, $status = 0) { $command = $this->application->find($name); $commandTester = new CommandTester($command); - $args = $args = array_merge(array( + $args = array_merge(array( 'command' => $command->getName(), ), $args); $this->assertEquals(0, $commandTester->execute($args)); diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceNodeUpdateCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php similarity index 62% rename from tests/PHPCR/Tests/Util/Console/Command/WorkspaceNodeUpdateCommandTest.php rename to tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php index ef9da9f..d1b096a 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceNodeUpdateCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php @@ -4,14 +4,14 @@ use Symfony\Component\Console\Application; use PHPCR\RepositoryInterface; -use PHPCR\Util\Console\Command\WorkspaceNodeUpdateCommand; +use PHPCR\Util\Console\Command\NodesUpdateCommand; -class WorkspaceNodeUpdateCommandTest extends BaseCommandTest +class NodesUpdateCommandTest extends BaseCommandTest { public function setUp() { parent::setUp(); - $this->application->add(new WorkspaceNodeUpdateCommand()); + $this->application->add(new NodesUpdateCommand()); $this->queryManager = $this->getMock( 'PHPCR\Query\QueryManagerInterface' ); @@ -21,11 +21,34 @@ public function setUp() public function provideNodeUpdate() { return array( + + // no type specified array(array( + 'exception' => 'InvalidArgumentException', + )), + + // select with no WHERE + array(array( + 'type' => 'nt:unstructured', + 'expectedSql' => 'SELECT * FROM nt:unstructured', + )), + + // select with WHERE + array(array( + 'type' => 'nt:unstructured', + 'where' => 'foo="bar"', + 'expectedSql' => 'SELECT * FROM nt:unstructured WHERE foo="bar"', + )), + + // set, remote properties and mixins + array(array( + 'type' => 'nt:unstructured', 'setProp' => array(array('foo', 'bar')), 'removeProp' => array('bar'), 'addMixin' => array('mixin1'), 'removeMixin' => array('mixin1'), + + 'expectedSql' => 'SELECT * FROM nt:unstructured', )), ); } @@ -36,34 +59,44 @@ public function provideNodeUpdate() public function testNodeUpdate($options) { $options = array_merge(array( + 'type' => null, + 'where' => null, 'setProp' => array(), 'removeProp' => array(), 'addMixin' => array(), 'removeMixin' => array(), + 'expectedSql' => null, + 'exception' => null, ), $options); - $this->session->expects($this->once()) + if ($options['exception']) { + $this->setExpectedException($options['exception']); + } + + $this->session->expects($this->any()) ->method('getWorkspace') ->will($this->returnValue($this->workspace)); - $this->workspace->expects($this->once()) + $this->workspace->expects($this->any()) ->method('getQueryManager') ->will($this->returnValue($this->queryManager)); - $this->queryManager->expects($this->once()) + + $this->queryManager->expects($this->any()) ->method('createQuery') - ->with('SELECT foo FROM foo', 'sql') + ->with($options['expectedSql'], 'sql') ->will($this->returnValue($this->query)); - $this->query->expects($this->once()) + + $this->query->expects($this->any()) ->method('execute') ->will($this->returnValue(array( $this->row1, ))); - $this->row1->expects($this->once()) + $this->row1->expects($this->any()) ->method('getNode') ->will($this->returnValue($this->node1)); $args = array( - 'query' => 'SELECT foo FROM foo', - '--language' => 'sql', + '--query-language' => null, + '--where' => $options['where'], '--force' => true, '--set-prop' => array(), '--remove-prop' => array(), @@ -71,6 +104,10 @@ public function testNodeUpdate($options) '--remove-mixin' => array(), ); + if ($options['type']) { + $args['--type'] = $options['type']; + } + foreach ($options['setProp'] as $setProp) { list($prop, $value) = $setProp; @@ -108,6 +145,6 @@ public function testNodeUpdate($options) $args['--remove-mixin'][] = $mixin; } - $ct = $this->executeCommand('phpcr:workspace:node:update', $args); + $ct = $this->executeCommand('phpcr:nodes:update', $args); } } From a7cc6363b55049fea1e56972e9852c01362e721c Mon Sep 17 00:00:00 2001 From: dantleech Date: Wed, 12 Jun 2013 15:10:47 +0200 Subject: [PATCH 3/5] Basically works, need to refactor options --- src/PHPCR/Util/Console/Command/BaseCommand.php | 6 ++++++ src/PHPCR/Util/Console/Command/NodesUpdateCommand.php | 3 +++ src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php | 8 ++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/PHPCR/Util/Console/Command/BaseCommand.php b/src/PHPCR/Util/Console/Command/BaseCommand.php index 1c60887..ef977a0 100644 --- a/src/PHPCR/Util/Console/Command/BaseCommand.php +++ b/src/PHPCR/Util/Console/Command/BaseCommand.php @@ -9,11 +9,17 @@ abstract class BaseCommand extends Command { + /** + * @return PHPCR\SessionInterface + */ protected function getPhpcrSession() { return $this->getHelper('phpcr')->getSession(); } + /** + * @return PHPCR\Util\Console\Helper\PhpcrCliHelper + */ protected function getPhpcrCliHelper() { $phpcrCliHelper = new PhpcrCliHelper($this->getPhpcrSession()); diff --git a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php index 7f1c304..5066c80 100644 --- a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php +++ b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php @@ -111,6 +111,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $noInteraction = $input->getOption('no-interaction'); $helper = $this->getPhpcrCliHelper(); $session = $this->getPhpcrSession(); + $this->dialog = new DialogHelper(); if (!$type) { @@ -153,6 +154,8 @@ protected function execute(InputInterface $input, OutputInterface $output) )); } + $session->save(); + $output->writeln(sprintf('%.2f seconds', $elapsed)); return 0; diff --git a/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php index 6ba7824..9117704 100644 --- a/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php +++ b/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php @@ -98,19 +98,19 @@ public function processNode(OutputInterface $output, $node, $options) } foreach ($options['addMixins'] as $addMixin) { - $output->writeln( + $output->writeln(sprintf( ' > Adding mixin %s', $addMixin - ); + )); $node->addMixin($addMixin); } foreach ($options['removeMixins'] as $removeMixin) { - $output->writeln( + $output->writeln(sprintf( ' > Removing mixin %s', $removeMixin - ); + )); $node->removeMixin($removeMixin); } From 0fe627dfc287e1d72a8a30c678a0eb1f85df289c Mon Sep 17 00:00:00 2001 From: dantleech Date: Wed, 12 Jun 2013 15:40:59 +0200 Subject: [PATCH 4/5] Refactored input configuration for node manipulation --- .../Util/Console/Command/BaseCommand.php | 21 ++++++ .../Util/Console/Command/NodeTouchCommand.php | 21 ++---- .../Console/Command/NodesUpdateCommand.php | 64 ++++++------------- .../Util/Console/Helper/PhpcrCliHelper.php | 5 +- .../Command/NodesUpdateCommandTest.php | 32 +++------- 5 files changed, 54 insertions(+), 89 deletions(-) diff --git a/src/PHPCR/Util/Console/Command/BaseCommand.php b/src/PHPCR/Util/Console/Command/BaseCommand.php index ef977a0..f399b80 100644 --- a/src/PHPCR/Util/Console/Command/BaseCommand.php +++ b/src/PHPCR/Util/Console/Command/BaseCommand.php @@ -6,6 +6,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use PHPCR\Util\Console\Helper\PhpcrCliHelper; +use Symfony\Component\Console\Input\InputOption; abstract class BaseCommand extends Command { @@ -25,4 +26,24 @@ protected function getPhpcrCliHelper() $phpcrCliHelper = new PhpcrCliHelper($this->getPhpcrSession()); return $phpcrCliHelper; } + + public function configureNodeManipulationInput() + { + $this->addOption('set-prop', 'p', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Set node property on nodes use foo=bar' + ); + $this->addOption('remove-prop', 'r', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Remove property from nodes' + ); + $this->addOption('add-mixin', null, + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Add a mixin to the nodes' + ); + $this->addOption('remove-mixin', null, + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Remove mixin from the nodes' + ); + } } diff --git a/src/PHPCR/Util/Console/Command/NodeTouchCommand.php b/src/PHPCR/Util/Console/Command/NodeTouchCommand.php index 7833658..b7dc4b9 100644 --- a/src/PHPCR/Util/Console/Command/NodeTouchCommand.php +++ b/src/PHPCR/Util/Console/Command/NodeTouchCommand.php @@ -45,6 +45,8 @@ protected function configure() { parent::configure(); + $this->configureNodeManipulationInput(); + $this->setName('phpcr:node:touch') ->addArgument( 'path', @@ -57,26 +59,10 @@ protected function configure() 'Node type, default nt:unstructured', 'nt:unstructured' ) - ->addOption('set-prop', 'p', - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Set node property, use foo=bar' - ) - ->addOption('remove-prop', 'r', - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Remove node property' - ) ->addOption('dump', 'd', InputOption::VALUE_NONE, 'Dump a string reperesentation of the created / modified node.' ) - ->addOption('add-mixin', null, - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Add a mixin to the node' - ) - ->addOption('remove-mixin', null, - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Add a mixin to the node' - ) ->setDescription('Create or modify a node') ->setHelp(<<getArgument('path'); $type = $input->getOption('type'); + $dump = $input->getOption('dump'); + $setProp = $input->getOption('set-prop'); $removeProp = $input->getOption('remove-prop'); - $dump = $input->getOption('dump'); $addMixins = $input->getOption('add-mixin'); $removeMixins = $input->getOption('remove-mixin'); diff --git a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php index 5066c80..a68e05e 100644 --- a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php +++ b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php @@ -42,44 +42,20 @@ protected function configure() { parent::configure(); + $this->configureNodeManipulationInput($this); + $this->setName('phpcr:nodes:update') ->addOption( - 'type', 't', + 'query', null, InputOption::VALUE_REQUIRED, - 'Update nodes of given node type, e.g. nt:unstructured' - ) - ->addOption( - 'where', 'w', - InputOption::VALUE_OPTIONAL, - 'Specify the update criteria in SQL, e.g. "foobar = \'foo\' AND barfoo = \'bar\'"' + 'Query used to select the nodes' ) ->addOption( 'query-language', 'l', InputOption::VALUE_OPTIONAL, - 'The query language (sql, jcr_sql2', + 'The query language (e.g. sql, jcr_sql2)', 'jcr_sql2' ) - - ->addOption('set-prop', 'p', - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Set node property on nodes use foo=bar' - ) - ->addOption('remove-prop', 'r', - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Remove property from nodes' - ) - ->addOption('add-mixin', null, - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Add a mixin to the nodes' - ) - ->addOption('remove-mixin', null, - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Remove mixin from the nodes' - ) - ->addOption('force', null, - InputOption::VALUE_NONE, - 'Use to bypass the confirmation dialog' - ) ->setDescription('Command to manipulate the nodes in the workspace.') ->setHelp(<<nodes:update command updates properties of nodes of type x matching @@ -101,8 +77,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $type = $input->getOption('type'); - $where = $input->getOption('where'); + $query = $input->getOption('query'); $queryLanguage = strtoupper($input->getOption('query-language')); $setProp = $input->getOption('set-prop'); $removeProp = $input->getOption('remove-prop'); @@ -114,22 +89,21 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->dialog = new DialogHelper(); - if (!$type) { - throw new \InvalidArgumentException('You must provide the "type" option, to select all nodes specify the type "nt:base"'); + if (!$query) { + throw new \InvalidArgumentException( + 'You must provide a SELECT query, e.g. --select="SELECT * FROM [nt:unstructured]"' + ); } - if ($where) { - $sql = sprintf('SELECT * FROM [%s] WHERE %s', $type, $where); - } else { - $sql = sprintf('SELECT * FROM [%s]', $type); + if (strtoupper(substr($query, 0, 6) != 'SELECT')) { + throw new \InvalidArgumentException(sprintf( + 'Query doesn\'t look like a SELECT query: "%s"', + $query + )); } - $output->writeln($sql); - $query = $helper->createQuery($queryLanguage, $sql); - - $start = microtime(true); + $query = $helper->createQuery($queryLanguage, $query); $result = $query->execute(); - $elapsed = microtime(true) - $start; if (!$noInteraction) { if (false === $this->getAction($output, $result)) { @@ -139,7 +113,7 @@ protected function execute(InputInterface $input, OutputInterface $output) foreach ($result as $i => $row) { $output->writeln(sprintf( - "Updating node:[%d] %s.", + "Updating node: [%d] %s.", $i, $row->getPath() )); @@ -154,9 +128,9 @@ protected function execute(InputInterface $input, OutputInterface $output) )); } + $output->writeln('Saving session...'); $session->save(); - - $output->writeln(sprintf('%.2f seconds', $elapsed)); + $output->writeln('Done.'); return 0; } diff --git a/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php index 9117704..df0e057 100644 --- a/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php +++ b/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php @@ -99,7 +99,7 @@ public function processNode(OutputInterface $output, $node, $options) foreach ($options['addMixins'] as $addMixin) { $output->writeln(sprintf( - ' > Adding mixin %s', + ' > Adding mixin %s', $addMixin )); @@ -108,7 +108,7 @@ public function processNode(OutputInterface $output, $node, $options) foreach ($options['removeMixins'] as $removeMixin) { $output->writeln(sprintf( - ' > Removing mixin %s', + ' > Removing mixin %s', $removeMixin )); @@ -159,4 +159,3 @@ public function createQuery($language, $sql) return $query; } } - diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php index d1b096a..65ad799 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php @@ -22,33 +22,23 @@ public function provideNodeUpdate() { return array( - // no type specified + // no query specified array(array( 'exception' => 'InvalidArgumentException', )), - // select with no WHERE + // specify query array(array( - 'type' => 'nt:unstructured', - 'expectedSql' => 'SELECT * FROM nt:unstructured', - )), - - // select with WHERE - array(array( - 'type' => 'nt:unstructured', - 'where' => 'foo="bar"', - 'expectedSql' => 'SELECT * FROM nt:unstructured WHERE foo="bar"', + 'query' => 'SELECT * FROM nt:unstructured WHERE foo="bar"', )), // set, remote properties and mixins array(array( - 'type' => 'nt:unstructured', 'setProp' => array(array('foo', 'bar')), 'removeProp' => array('bar'), 'addMixin' => array('mixin1'), 'removeMixin' => array('mixin1'), - - 'expectedSql' => 'SELECT * FROM nt:unstructured', + 'query' => 'SELECT * FROM nt:unstructured', )), ); } @@ -59,13 +49,11 @@ public function provideNodeUpdate() public function testNodeUpdate($options) { $options = array_merge(array( - 'type' => null, - 'where' => null, + 'query' => null, 'setProp' => array(), 'removeProp' => array(), 'addMixin' => array(), 'removeMixin' => array(), - 'expectedSql' => null, 'exception' => null, ), $options); @@ -82,7 +70,7 @@ public function testNodeUpdate($options) $this->queryManager->expects($this->any()) ->method('createQuery') - ->with($options['expectedSql'], 'sql') + ->with($options['query'], 'JCR-SQL2') ->will($this->returnValue($this->query)); $this->query->expects($this->any()) @@ -96,18 +84,14 @@ public function testNodeUpdate($options) $args = array( '--query-language' => null, - '--where' => $options['where'], - '--force' => true, + '--query' => $options['query'], + '--no-interaction' => true, '--set-prop' => array(), '--remove-prop' => array(), '--add-mixin' => array(), '--remove-mixin' => array(), ); - if ($options['type']) { - $args['--type'] = $options['type']; - } - foreach ($options['setProp'] as $setProp) { list($prop, $value) = $setProp; From dabbb3a24f8eb6af6aebc2bcfe36101f3209aaaa Mon Sep 17 00:00:00 2001 From: dantleech Date: Wed, 12 Jun 2013 16:19:03 +0200 Subject: [PATCH 5/5] Added query language validation --- .../Console/Command/NodesUpdateCommand.php | 22 ++++++++------ .../Console/Command/WorkspaceQueryCommand.php | 2 +- .../Util/Console/Helper/PhpcrCliHelper.php | 30 +++++++++++++------ .../Util/Console/Command/BaseCommandTest.php | 9 ++++++ .../Command/NodesUpdateCommandTest.php | 3 -- .../Command/WorkspaceQueryCommandTest.php | 12 ++++---- 6 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php index a68e05e..ffcb956 100644 --- a/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php +++ b/src/PHPCR/Util/Console/Command/NodesUpdateCommand.php @@ -42,7 +42,7 @@ protected function configure() { parent::configure(); - $this->configureNodeManipulationInput($this); + $this->configureNodeManipulationInput(); $this->setName('phpcr:nodes:update') ->addOption( @@ -54,20 +54,24 @@ protected function configure() 'query-language', 'l', InputOption::VALUE_OPTIONAL, 'The query language (e.g. sql, jcr_sql2)', - 'jcr_sql2' + 'jcr-sql2' ) ->setDescription('Command to manipulate the nodes in the workspace.') ->setHelp(<<nodes:update command updates properties of nodes of type x matching -the given select criteria. +The phpcr:nodes:update can manipulate the properties of nodes +found using the given query. - php bin/phpcr nodes:update --type="nt:unstructured" --where="foo='bar'" --set-prop=foo=bar +For example, to set the property "foo" to "bar" on all unstructured nodes: + + php bin/phpcr phpcr:nodes:update --query="SELECT * FROM [nt:unstructured]" --set-prop=foo=bar + +Or to update only nodes matching a certain criteria: + + php bin/phpcr nodes:update --query="SELECT * FROM [nt:unstructured] WHERE [phpcr:class]=\"Some\\Class\\Here\" --add-mixin=mix:mimetype The options for manipulating nodes are the same as with the node:touch command and can be repeated to update multiple properties. - -The --where option corresponds to the "where" part of a standard query. HERE ); } @@ -77,6 +81,8 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $this->dialog = new DialogHelper(); + $query = $input->getOption('query'); $queryLanguage = strtoupper($input->getOption('query-language')); $setProp = $input->getOption('set-prop'); @@ -87,8 +93,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $helper = $this->getPhpcrCliHelper(); $session = $this->getPhpcrSession(); - $this->dialog = new DialogHelper(); - if (!$query) { throw new \InvalidArgumentException( 'You must provide a SELECT query, e.g. --select="SELECT * FROM [nt:unstructured]"' diff --git a/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php b/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php index a8ed44e..df22437 100644 --- a/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php +++ b/src/PHPCR/Util/Console/Command/WorkspaceQueryCommand.php @@ -45,7 +45,7 @@ protected function configure() $this->setName('phpcr:workspace:query') ->addArgument('query', InputArgument::REQUIRED, 'A query statement to execute') - ->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'The query language (sql, jcr_sql2', 'jcr_sql2') + ->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'The query language (e.g. jcr-sql2', 'jcr-sql2') ->addOption('limit', null, InputOption::VALUE_OPTIONAL, 'The query limit', 0) ->addOption('offset', null, InputOption::VALUE_OPTIONAL, 'The query offset', 0) ->setDescription('Execute a JCR SQL2 statement') diff --git a/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php b/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php index df0e057..a2048e3 100644 --- a/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php +++ b/src/PHPCR/Util/Console/Helper/PhpcrCliHelper.php @@ -142,20 +142,32 @@ public function processNode(OutputInterface $output, $node, $options) */ public function createQuery($language, $sql) { + $this->validateQueryLanguage($language); + $session = $this->getSession(); $qm = $session->getWorkspace()->getQueryManager(); $language = strtoupper($language); + $query = $qm->createQuery($sql, $language); + + return $query; + } - $constantName = '\PHPCR\Query\QueryInterface::'.$language; - if (!defined($constantName)) { - throw new \RuntimeException(sprintf( - "Query language '\\PHPCR\\Query\\QueryInterface::%s' not defined.", - $language + /** + * Validate the given query language. + * + * @param string Language type + * + * @return null + */ + protected function validateQueryLanguage($language) + { + $qm = $this->getSession()->getWorkspace()->getQueryManager(); + $langs = $qm->getSupportedQueryLanguages(); + if (!in_array($language, $langs)) { + throw new \Exception(sprintf( + 'Query language "%s" not supported, available query languages: %s', + $language, implode(',', $langs) )); } - - $query = $qm->createQuery($sql, constant($constantName)); - - return $query; } } diff --git a/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php index df021ea..b5d9ece 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/BaseCommandTest.php @@ -36,6 +36,7 @@ public function setUp() $this->session = $this->getMock('PHPCR\SessionInterface'); $this->workspace = $this->getMock('PHPCR\WorkspaceInterface'); $this->repository = $this->getMock('PHPCR\RepositoryInterface'); + $this->queryManager = $this->getMock('PHPCR\Query\QueryManagerInterface'); $this->row1 = $this->getMock('PHPCR\Tests\Stubs\MockRow'); $this->node1 = $this->getMock('PHPCR\Tests\Stubs\MockNode'); @@ -57,6 +58,14 @@ public function setUp() ->method('getName') ->will($this->returnValue('test')); + $this->workspace->expects($this->any()) + ->method('getQueryManager') + ->will($this->returnValue($this->queryManager)); + + $this->queryManager->expects($this->any()) + ->method('getSupportedQueryLanguages') + ->will($this->returnValue(array('JCR-SQL2'))); + $this->application = new Application(); $this->application->setHelperSet($this->helperSet); } diff --git a/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php index 65ad799..c5e900f 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/NodesUpdateCommandTest.php @@ -12,9 +12,6 @@ public function setUp() { parent::setUp(); $this->application->add(new NodesUpdateCommand()); - $this->queryManager = $this->getMock( - 'PHPCR\Query\QueryManagerInterface' - ); $this->query = $this->getMock('PHPCR\Query\QueryInterface'); } diff --git a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceQueryCommandTest.php b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceQueryCommandTest.php index fc2fa1f..f2d329c 100644 --- a/tests/PHPCR/Tests/Util/Console/Command/WorkspaceQueryCommandTest.php +++ b/tests/PHPCR/Tests/Util/Console/Command/WorkspaceQueryCommandTest.php @@ -12,18 +12,18 @@ public function setUp() { parent::setUp(); $this->application->add(new WorkspaceQueryCommand()); - $this->queryManager = $this->getMock( - 'PHPCR\Query\QueryManagerInterface' - ); $this->query = $this->getMock('PHPCR\Query\QueryInterface'); } - public function testNodeTypeQuery() + public function testQuery() { - $this->session->expects($this->once()) + $this->queryManager->expects($this->any()) + ->method('getSupportedQueryLanguages') + ->will($this->returnValue(array('JCR-SQL2'))); + $this->session->expects($this->any()) ->method('getWorkspace') ->will($this->returnValue($this->workspace)); - $this->workspace->expects($this->once()) + $this->workspace->expects($this->any()) ->method('getQueryManager') ->will($this->returnValue($this->queryManager)); $this->queryManager->expects($this->once())