Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Achieving wise architecture with Symfony2 @ SPUG#5

Achieving wise architecture with Symfony2 @ SPUG#5

Wojciech Sznapka

November 26, 2013
Tweet

More Decks by Wojciech Sznapka

Other Decks in Programming

Transcript

  1. Wojciech Sznapka Technical Manager @ XSolve Zend Certified Engineer since

    2010 Symfony user since 2008 Coding since 2004 Besides: ice hockey, windsurfing, skiing, crime stories
  2. ingle responsibility principle S O L I D pen/closed principle

    iskov substitution principle nterface segregation principle ependency inversion principle
  3. class MailingManager implements MailingManagerInterface { protected $templateManager; public function __construct(TemplateManagerInterface

    $templateManager) { $this->templateManager = $templateManager; } public function createMailing($data, Mailing $mailing) { $body = $this->templateManager->applyTemplate( $mailing->getBody(), $mailing->getTitle()); $mailing->setBody($body); $mailing->setSubject($data['subject']); return $mailing; } }
  4. class TemplateManager implements TemplateManagerInterface { protected $templateContentTag = '{{ content

    }}'; protected $templateTitleTag = '{{ title }}'; public function applyTemplate($body, $title) { return str_replace( array($this->templateContentTag, $this->templateTitleTag), array($body, $title), $this->getActiveTemplateBody() ); } }
  5. class ComplicatedBusinessLogic { public function __construct(DependencyOne $depOne, DependencyTwo $depTwo, DependencyThree

    $depThree, DependencyFour $depFour) { $this->depOne = $depOne; $this->depTwo = $depTwo; $this->depThree = $depThree; $this->depFour = $depFour; } public function performLogic($param1, $param2) { $partialResult = $this->getPartialResult($param1, $param2); // [...] do some really complicated logic return $finalResult; } protected function getPartialResult($param1, $param2) { // [...] produce partial result return $partialResult; } }
  6. class SpecyficBusinessLogic extends ComplicatedBusinessLogic { protected function getPartialResult($param1, $param2) {

    // produce partial result in a specific way // to change whole class behavior return $partialResultAltered; } }
  7. parameters: complicated_business_logic.class: ComplicatedBusinessLogic specyfic_business_logic.class: SpecyficBusinessLogic services: complicated_business_logic: class: %complicated_business_logic.class% arguments:

    - @dependency.one - @dependency.two - @dependency.three - @dependency.four specyfic_business_logic: class: %specyfic_business_logic.class% parent: complicated_business_logic
  8. class Calculator { protected $evaluators; public function __construct() { $this->evaluators

    = array(); } public function registerEvaluator(EvaluatorInterface $evaluator) { $this->evaluator[] = $evaluator; } public function calculate($expression) { for ($this->evaluators as $evaluator) { if ($evaluator->accepts($expression)) { return $expression->calculate($expression); } } throw new \RuntimeException('No suitable evaluator here'); } }
  9. class PlusEvaluator implements EvaluatorInterface { public function accepts($expression) { return

    strpos($expression, '+') !== false; } public function calculate($expression) { // do some calculations return $result; } }
  10. class MinusEvaluator implements EvaluatorInterface { public function accepts($expression) { return

    strpos($expression, '-') !== false; } public function calculate($expression) { // do some calculations return $result; } }
  11. parameters: plus_evaluator.class: PlusEvaluator minus_evaluator.class: MinusEvaluator calculator.class: Calculator services: calculator: class:

    %calculator.class% plus_evaluator: class: %plus_evaluator.class% tags: - {name: calculator.evaluator} minus_evaluator: class: %minus_evaluator.class% tags: - {name: calculator.evaluator}
  12. use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; class EvaluatorCompilerPass implements CompilerPassInterface

    { public function process(ContainerBuilder $container) { $definition = $container->getDefinition('calculator'); $svcs = $container->findTaggedServiceIds('calculator.evaluator'); foreach ($svcs as $id => $attributes) { $definition->addMethodCall( 'registerEvaluator', array(new Reference($id)) ); } } }
  13. ############## # services.yml ############## parameters: webservice_consumer.class: Wowo\Webservice\Consumer webservice_api_client.class: Wowo\Webservice\ApiClient services:

    webservice_consumer: class: %webservice_consumer.class% arguments: - @webservice_api_client webservice_api_client: class: %webservice_api_client.class% ################ # config_dev.yml ################ parameters: webservice_api_client: Wowo\Mocks\FakeDataMockProvider
  14. interface PlaceholderProcessorInterface { public function process($object, $body); } interface TemplateManagerInterface

    { public function setAvailableTemplates(array $templates); public function getAvailableTemplates(); public function applyTemplate($body, $title); } interface MediaManagerInterface { public function embed($body, \Swift_Message $message); public function getRegex($name); } interface BuilderInterface { public function buildMessage($mailingId, $contactId, $contactClass); } interface SenderInterface { public function send($mailingId, $contactId, $contactClass); }
  15. class WebserviceConsumer { protected $client; protected $logger public function __construct(WebserviceApiClient

    $client, Logger $logger) { $this->client = $client; $this->logger = $logger; } public function getUser($name) { try { $this->logger->info(sprintf('Attempting get user %s', $name)); $user = $this->client->callGetUserDataFunction($name); // do something with data // wrap into suitable class // or transform it somehow $this->logger->info(sprintf('Got user data for %s', $name), array('user' => $user)); return $user; } catch (\Exception $e) { $this->logger->error(sprintf('%s occured while getting user: %s', get_class($e), $e->getMessage), array('exception' => $e)); throw new WebserviceException('GetUser error' , 0, $e); } } }
  16. class WebserviceTest extends \PHPUnit_Framework_TestCase { public function tearDown() { \Mockery::close();

    } public function testSuccessfulGetUser() { $testData = array('name' => 'Wojciech', 'company' => 'Xsolve'); $clientMock = \Mockery::mock('WebserviceApiClient'); $clientMock ->shouldReceive('callGetUserDataFunction') ->times(1) ->andReturn($testData); $loggerMock = \Mockery::mock('\Monolog\Logger') ->shouldReceive('info') ->times(2); $consumer = new WebserviceApiClient($clientMock, $loggerMock); $this->assertEquals($testData, $consumer->getUser('Wojciech')); } }
  17. class WebserviceTest extends \PHPUnit_Framework_TestCase { public function tearDown() { \Mockery::close();

    } /** @expectedException WebserviceException */ public function testFailuerWhileGettingUser() { $clientMock = \Mockery::mock('WebserviceApiClient'); $clientMock ->shouldReceive('callGetUserDataFunction') ->times(1) ->andThrow('\RuntimeException'); $loggerMock = \Mockery::mock('\Monolog\Logger') ->shouldReceive('info') ->times(1); $loggerMock ->shouldReceive('error') ->times(1); $consumer = new WebserviceApiClient($clientMock, $loggerMock); } }