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

Achieving wise architecture with Symfony2

Achieving wise architecture with Symfony2

Web application's sector is exploding nowadays showing that browser based systems are most common approach to satisfy business needs. Building a product which is going to work on a long mile requires good architectural decisions and appropriate tools stack.
Symfony2 is the best shot, when it comes to framework. Accompanied by some modern tools, good design practises and well structured code base creates easy way for the system, to be open for extensions, robust and maintainable, prepared to handle high load.
In this talk some real life examples from working Symfony2 applications will be used to ilustrate ingredients of wise architecture.

Wojciech Sznapka

April 12, 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 in PHP 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); } }