From b9f035f5d36ba470f4239b7b0fd75ca3d493a75e Mon Sep 17 00:00:00 2001 From: Kotlyar Maksim Date: Mon, 26 May 2014 10:32:00 +0000 Subject: [PATCH] [action] add ability to add action by container tag --- .../Compiler/PayumActionsPass.php | 99 +++++ .../Payment/PaymentFactoryInterface.php | 7 +- DependencyInjection/PayumExtension.php | 7 +- PayumBundle.php | 3 + Resources/doc/custom_action_usage.md | 90 +++++ Resources/doc/index.md | 1 + .../Compiler/PayumActionsPassTest.php | 355 ++++++++++++++++++ .../PayumExtensionTest.php | 57 +++ Tests/PayumBundleTest.php | 25 ++ 9 files changed, 640 insertions(+), 4 deletions(-) create mode 100644 DependencyInjection/Compiler/PayumActionsPass.php create mode 100644 Resources/doc/custom_action_usage.md create mode 100644 Tests/DependencyInjection/Compiler/PayumActionsPassTest.php create mode 100644 Tests/PayumBundleTest.php diff --git a/DependencyInjection/Compiler/PayumActionsPass.php b/DependencyInjection/Compiler/PayumActionsPass.php new file mode 100644 index 00000000..214c7025 --- /dev/null +++ b/DependencyInjection/Compiler/PayumActionsPass.php @@ -0,0 +1,99 @@ +findTaggedServiceIds('payum.action') as $id => $tagAttributes) { + + foreach ($tagAttributes as $attributes) { + $paymentIds = array(); + + if (isset($attributes['all']) && $attributes['all']) { + $paymentIds = array_merge($paymentIds, $this->findAllPaymentIds($container)); + } + + if (isset($attributes['factory']) && $attributes['factory']) { + $paymentIds = array_merge( + $paymentIds, + $this->findPaymentIdsByFactory($container, $attributes['factory']) + ); + } + if (isset($attributes['context']) && $attributes['context']) { + $paymentIds = array_merge( + $paymentIds, + $this->findPaymentIdsByContext($container, $attributes['context']) + ); + } + + $paymentIds = array_filter(array_unique($paymentIds)); + foreach ($paymentIds as $paymentId) { + $payment = $container->getDefinition($paymentId); + $payment->addMethodCall('addAction', array( + new Reference($id), + isset($attributes['prepend']) && $attributes['prepend'] + )); + } + } + } + } + + /** + * @param ContainerBuilder $container + * @param string $factoryName + * + * @return string[] + */ + protected function findPaymentIdsByFactory(ContainerBuilder $container, $factoryName) + { + $paymentIds = array(); + foreach ($container->findTaggedServiceIds('payum.payment') as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (isset($attributes['factory']) && $attributes['factory'] == $factoryName) { + $paymentIds[] = $id; + } + } + } + + return $paymentIds; + } + + /** + * @param ContainerBuilder $container + * @param string $contextName + * + * @return string[] + */ + protected function findPaymentIdsByContext(ContainerBuilder $container, $contextName) + { + $paymentIds = array(); + foreach ($container->findTaggedServiceIds('payum.payment') as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (isset($attributes['context']) && $attributes['context'] == $contextName) { + $paymentIds[] = $id; + } + } + } + + return $paymentIds; + } + + /** + * @param ContainerBuilder $container + * + * @return string[] + */ + protected function findAllPaymentIds(ContainerBuilder $container) + { + return array_keys($container->findTaggedServiceIds('payum.payment')); + } +} \ No newline at end of file diff --git a/DependencyInjection/Factory/Payment/PaymentFactoryInterface.php b/DependencyInjection/Factory/Payment/PaymentFactoryInterface.php index 107de0ce..1f11787d 100644 --- a/DependencyInjection/Factory/Payment/PaymentFactoryInterface.php +++ b/DependencyInjection/Factory/Payment/PaymentFactoryInterface.php @@ -1,13 +1,14 @@ getDefinition($paymentId)->addTag('payum.payment', array( + 'factory' => $paymentFactoryName, + 'context' => $contextName + )); + foreach ($contextConfig['storages'] as $modelClass => $storageConfig) { $storageFactoryName = $this->findSelectedStorageFactoryNameInStorageConfig($storageConfig); $storageId = $this->storageFactories[$storageFactoryName]->create( diff --git a/PayumBundle.php b/PayumBundle.php index 4a42ad84..1065a40e 100644 --- a/PayumBundle.php +++ b/PayumBundle.php @@ -1,6 +1,7 @@ addStorageFactory(new FilesystemStorageFactory); $extension->addStorageFactory(new DoctrineStorageFactory); + + $container->addCompilerPass(new PayumActionsPass); } } diff --git a/Resources/doc/custom_action_usage.md b/Resources/doc/custom_action_usage.md new file mode 100644 index 00000000..bd7620c5 --- /dev/null +++ b/Resources/doc/custom_action_usage.md @@ -0,0 +1,90 @@ +# Custom action usage + +Payment comes with built in actions but sometime you have to add your own. First you have to define a service: + +```yaml +# src/Acme/PaymentBundle/Resources/config/services.yml + +services: + acme.payum.action.foo: + class: Acme\PaymentBundle\Payum\Action\FooAction +``` + +There are several ways to add it to a payment: + +* Set it explicitly in config.yml. + + ```yaml + # app/config/config.yml + + payum: + contexts: + a_context: + a_factory: + actions: + - acme.payum.action.foo + ``` + +* Tag it + + + More powerful method is to add a tag `payum.action` to action server. Payum will do the reset. + You can define a `factory` attribute inside that tag. + In this case the action will be added to all payments created by requested factory. + + ```yaml + # app/config/config.yml + + payum: + contexts: + a_context: + a_factory: ~ + ``` + + ```yaml + # src/Acme/PaymentBundle/Resources/config/services.yml + + services: + acme.payum.action.foo: + class: Acme\PaymentBundle\Payum\Action\FooAction + tags: + - {payum.action, { factory: a_factory }} + + ``` + + Or you can set concrete `context` name. + In this case the action will be added only to the payment with requested context name. + + ```yaml + # app/config/config.yml + + payum: + contexts: + a_context: + a_factory: ~ + ``` + + ```yaml + # src/Acme/PaymentBundle/Resources/config/services.yml + + services: + acme.payum.action.foo: + class: Acme\PaymentBundle\Payum\Action\FooAction + tags: + - {payum.action, {context: a_context}} + ``` + + If `prepend` set to true the action is added before the rest. + If you want to add the action to all configured payments set `all` to true. + + ```yaml + # src/Acme/PaymentBundle/Resources/config/services.yml + + services: + acme.payum.action.foo: + class: Acme\PaymentBundle\Payum\Action\FooAction + tags: + - {payum.action, { prepend: true, all: true }} + ``` + +Back to [index](index.md). diff --git a/Resources/doc/index.md b/Resources/doc/index.md index b17d5c97..ac5e2162 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -3,6 +3,7 @@ * [Get it started](get_it_started.md) * [Simple purchase examples](simple_purchase_examples.md). * [Purchase done action](purchase_done_action.md). +* [Custom action usage](custom_action_usage.md). * [Custom api usage](custom_api_usage.md). * [Sandbox](sandbox.md). * [Console commands](console_commands.md). diff --git a/Tests/DependencyInjection/Compiler/PayumActionsPassTest.php b/Tests/DependencyInjection/Compiler/PayumActionsPassTest.php new file mode 100644 index 00000000..a2ff38ac --- /dev/null +++ b/Tests/DependencyInjection/Compiler/PayumActionsPassTest.php @@ -0,0 +1,355 @@ +assertTrue($rc->implementsInterface('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface')); + } + + /** + * @test + */ + public function couldBeConstructedWithoutAnyArguments() + { + new PayumActionsPass(); + } + + /** + * @test + */ + public function shouldAddSingleActionToPaymentsByFactoryName() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array( + 'factory' => 'foo', + )); + $container->setDefinition('payment.foo', $paymentFoo); + + $paymentBar = new Definition; + $paymentBar->addTag('payum.payment', array( + 'factory' => 'bar', + )); + $container->setDefinition('payment.bar', $paymentBar); + + $actionFoo = new Definition; + $actionFoo->addTag('payum.action', array( + 'factory' => 'foo', + )); + $container->setDefinition('action.foo', $actionFoo); + + $actionBar = new Definition; + $actionBar->addTag('payum.action', array( + 'factory' => 'bar', + )); + $container->setDefinition('action.bar', $actionBar); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(1, $fooPaymentMethodCalls); + $this->assertEquals('addAction', $fooPaymentMethodCalls[0][0]); + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $fooPaymentMethodCalls[0][1][0]); + $this->assertEquals('action.foo', (string) $fooPaymentMethodCalls[0][1][0]); + + $barPaymentMethodCalls = $paymentBar->getMethodCalls(); + $this->assertCount(1, $barPaymentMethodCalls); + $this->assertEquals('addAction', $barPaymentMethodCalls[0][0]); + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $barPaymentMethodCalls[0][1][0]); + $this->assertEquals('action.bar', (string) $barPaymentMethodCalls[0][1][0]); + } + + /** + * @test + */ + public function shouldAddSeveralActionsToPayment() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array( + 'factory' => 'foo', + )); + $container->setDefinition('payment.foo', $paymentFoo); + + $actionFoo1 = new Definition; + $actionFoo1->addTag('payum.action', array( + 'factory' => 'foo', + )); + $container->setDefinition('action.foo1', $actionFoo1); + + $actionFoo2 = new Definition; + $actionFoo2->addTag('payum.action', array( + 'factory' => 'foo', + )); + $container->setDefinition('action.foo2', $actionFoo2); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(2, $fooPaymentMethodCalls); + + $this->assertEquals('addAction', $fooPaymentMethodCalls[0][0]); + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $fooPaymentMethodCalls[0][1][0]); + $this->assertEquals('action.foo1', (string) $fooPaymentMethodCalls[0][1][0]); + + $this->assertEquals('addAction', $fooPaymentMethodCalls[1][0]); + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $fooPaymentMethodCalls[1][1][0]); + $this->assertEquals('action.foo2', (string) $fooPaymentMethodCalls[1][1][0]); + } + + /** + * @test + */ + public function shouldAddSingleActionWithPrependTrueToPayment() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array( + 'factory' => 'foo', + )); + $container->setDefinition('payment.foo', $paymentFoo); + + $actionFoo = new Definition; + $actionFoo->addTag('payum.action', array( + 'factory' => 'foo', 'prepend' => true + )); + $container->setDefinition('action.foo', $actionFoo); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(1, $fooPaymentMethodCalls); + + $this->assertEquals('addAction', $fooPaymentMethodCalls[0][0]); + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $fooPaymentMethodCalls[0][1][0]); + $this->assertEquals('action.foo', (string) $fooPaymentMethodCalls[0][1][0]); + $this->assertTrue($fooPaymentMethodCalls[0][1][1]); + } + + /** + * @test + */ + public function shouldAddSingleActionWithPrependFalseToPayment() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array( + 'factory' => 'foo', + )); + $container->setDefinition('payment.foo', $paymentFoo); + + $actionFoo = new Definition; + $actionFoo->addTag('payum.action', array( + 'factory' => 'foo', 'prepend' => false + )); + $container->setDefinition('action.foo', $actionFoo); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(1, $fooPaymentMethodCalls); + + $this->assertEquals('addAction', $fooPaymentMethodCalls[0][0]); + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $fooPaymentMethodCalls[0][1][0]); + $this->assertEquals('action.foo', (string) $fooPaymentMethodCalls[0][1][0]); + $this->assertFalse($fooPaymentMethodCalls[0][1][1]); + } + + /** + * @test + */ + public function shouldAddSingleActionToPaymentsByContextName() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array( + 'context' => 'foo', + )); + $container->setDefinition('payment.foo', $paymentFoo); + + $paymentBar = new Definition; + $paymentBar->addTag('payum.payment', array( + 'context' => 'bar', + )); + $container->setDefinition('payment.bar', $paymentBar); + + $actionFoo = new Definition; + $actionFoo->addTag('payum.action', array( + 'context' => 'foo', + )); + $container->setDefinition('action.foo', $actionFoo); + + $actionBar = new Definition; + $actionBar->addTag('payum.action', array( + 'context' => 'bar', + )); + $container->setDefinition('action.bar', $actionBar); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(1, $fooPaymentMethodCalls); + $this->assertEquals('addAction', $fooPaymentMethodCalls[0][0]); + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $fooPaymentMethodCalls[0][1][0]); + $this->assertEquals('action.foo', (string) $fooPaymentMethodCalls[0][1][0]); + + $barPaymentMethodCalls = $paymentBar->getMethodCalls(); + $this->assertCount(1, $barPaymentMethodCalls); + $this->assertEquals('addAction', $barPaymentMethodCalls[0][0]); + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $barPaymentMethodCalls[0][1][0]); + $this->assertEquals('action.bar', (string) $barPaymentMethodCalls[0][1][0]); + } + + /** + * @test + */ + public function shouldNotAddActionTwiceByFactoryAndContext() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array( + 'context' => 'foo_context', 'factory' => 'foo_factory' + )); + $container->setDefinition('payment.foo', $paymentFoo); + + $actionFoo = new Definition; + $actionFoo->addTag('payum.action', array( + 'context' => 'foo_context', 'factory' => 'foo_factory' + )); + $container->setDefinition('action.foo', $actionFoo); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(1, $fooPaymentMethodCalls); + } + + /** + * @test + */ + public function shouldAddActionToAllPayments() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array()); + $container->setDefinition('payment.foo', $paymentFoo); + + $paymentBar = new Definition; + $paymentBar->addTag('payum.payment', array()); + $container->setDefinition('payment.bar', $paymentBar); + + $actionFoo = new Definition; + $actionFoo->addTag('payum.action', array( + 'all' => true + )); + $container->setDefinition('action.foo', $actionFoo); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(1, $fooPaymentMethodCalls); + + $barPaymentMethodCalls = $paymentBar->getMethodCalls(); + $this->assertCount(1, $barPaymentMethodCalls); + } + + /** + * @test + */ + public function shouldNotAddActionTwiceIfAllAndFactorySet() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array( + 'factory' => 'foo' + )); + $container->setDefinition('payment.foo', $paymentFoo); + + $paymentBar = new Definition; + $paymentBar->addTag('payum.payment', array()); + $container->setDefinition('payment.bar', $paymentBar); + + $actionFoo = new Definition; + $actionFoo->addTag('payum.action', array( + 'all' => true, 'factory' => 'foo' + )); + $container->setDefinition('action.foo', $actionFoo); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(1, $fooPaymentMethodCalls); + + $barPaymentMethodCalls = $paymentBar->getMethodCalls(); + $this->assertCount(1, $barPaymentMethodCalls); + } + + /** + * @test + */ + public function shouldNotAddActionTwiceIfAllAndContextSet() + { + $container = new ContainerBuilder; + + $paymentFoo = new Definition; + $paymentFoo->addTag('payum.payment', array( + 'context' => 'foo' + )); + $container->setDefinition('payment.foo', $paymentFoo); + + $paymentBar = new Definition; + $paymentBar->addTag('payum.payment', array()); + $container->setDefinition('payment.bar', $paymentBar); + + $actionFoo = new Definition; + $actionFoo->addTag('payum.action', array( + 'all' => true, 'context' => 'foo' + )); + $container->setDefinition('action.foo', $actionFoo); + + $pass = new PayumActionsPass; + + $pass->process($container); + + $fooPaymentMethodCalls = $paymentFoo->getMethodCalls(); + $this->assertCount(1, $fooPaymentMethodCalls); + + $barPaymentMethodCalls = $paymentBar->getMethodCalls(); + $this->assertCount(1, $barPaymentMethodCalls); + } +} diff --git a/Tests/Functional/DependencyInjection/PayumExtensionTest.php b/Tests/Functional/DependencyInjection/PayumExtensionTest.php index a4a17182..f2f4f4b1 100644 --- a/Tests/Functional/DependencyInjection/PayumExtensionTest.php +++ b/Tests/Functional/DependencyInjection/PayumExtensionTest.php @@ -404,6 +404,63 @@ public function shouldLoadExtensionWithKlarnaCheckoutConfiguredPayment() ); } + /** + * @test + */ + public function shouldAddPaymentTagWithCorrectContextAndFactoryNamesSet() + { + $config = array( + 'security' => array( + 'token_storage' => array( + 'Payum\Core\Model\Token' => array( + 'filesystem' => array( + 'storage_dir' => sys_get_temp_dir(), + 'id_property' => 'hash' + ) + ) + ) + ), + 'contexts' => array( + 'the_paypal_context' => array( + 'paypal_express_checkout_nvp' => array( + 'api' => array( + 'options' => array( + 'username' => 'a_username', + 'password' => 'a_password', + 'signature' => 'a_signature', + 'sandbox' => true + ) + ) + ), + ) + ) + ); + + $configs = array($config); + + $containerBuilder = new ContainerBuilder(new ParameterBag); + + $extension = new PayumExtension; + $extension->addPaymentFactory(new PaypalExpressCheckoutNvpPaymentFactory); + $extension->addStorageFactory(new FilesystemStorageFactory); + + $extension->load($configs, $containerBuilder); + + $paymentDefinition = $containerBuilder->getDefinition('payum.context.the_paypal_context.payment'); + + $tagAttributes = $paymentDefinition->getTag('payum.payment'); + + $this->assertCount(1, $tagAttributes); + + $attributes = $tagAttributes[0]; + + $this->assertArrayHasKey('factory', $attributes); + $this->assertEquals('paypal_express_checkout_nvp', $attributes['factory']); + + $this->assertArrayHasKey('context', $attributes); + $this->assertEquals('the_paypal_context', $attributes['context']); + } + protected function assertDefinitionContainsMethodCall(Definition $serviceDefinition, $expectedMethod, $expectedFirstArgument) { foreach ($serviceDefinition->getMethodCalls() as $methodCall) { diff --git a/Tests/PayumBundleTest.php b/Tests/PayumBundleTest.php new file mode 100644 index 00000000..d0e19978 --- /dev/null +++ b/Tests/PayumBundleTest.php @@ -0,0 +1,25 @@ +assertTrue($rc->isSubclassOf('Symfony\Component\HttpKernel\Bundle\Bundle')); + } + + /** + * @test + */ + public function couldBeConstructedWithoutAnyArguments() + { + new PayumBundle; + } +} \ No newline at end of file