Legacy applications - 4Developes konferencja, Piotr Pasich

Post on 24-Jun-2015

538 views 0 download

Transcript of Legacy applications - 4Developes konferencja, Piotr Pasich

LEGACY APPLICATIONS

Piotr Pasich

Piotr Pasich @

FROM SPAGHETTI TO CODEczyli dziedziczymy aplikację

DLACZEGO TEGO NIE PRZEPISAĆ?

Piotr Pasich @

dużo koduduża ilość funkcjonalności

czaspieniądze

Piotr Pasich @

PREVENTING REGRESSIONSczyli nic nie ruszać

Piotr Pasich @

TESTY FUNKCJONALNOŚCISelenium IDE, behat, testy jednostkowe

Piotr Pasich @

ŚRODOWISKOminimum PHP 5.3.3Sqlite3, JSON, ctypephp app/check.php

date.timezone set in php.iniPhpcs CodeSniffs

tutaj po raz pierwszy korzystamy z testów

Piotr Pasich @

INSTALACJA SYMFONY 2katalog legacy

namespace

Piotr Pasich @

namespace Legacy {

(...)

}

LegacyBundleapp/console generate:bundle

Bundle namespace: Xsolve\LegacyBundle

Piotr Pasich @

AUTOLOADER<?php

namespace Xsolve\LegacyBundle;

require_once(__DIR__ . "/../../../legacy/index.php"); //disabled execute::runuse Legacy;use Symfony\Component\HttpKernel\Bundle\Bundle;use Symfony\Component\DependencyInjection\ContainerBuilder;

class XsolveLegacyBundle extends Bundle{ public function build(ContainerBuilder $container) { spl_autoload_register(array('Kohana', 'auto_load')); }

}

Piotr Pasich @

MainActionclass LegacyController extends Controller{ /** * @Route("/", name="main_page") * @Route("/{filename}.html", name="proxy_html", requirements={"filename" = ".+"}) * @Route("/{filename}", name="proxy", requirements={"filename" = ".+"}) */ public function indexAction($filename='index') {

$_SERVER['SCRIPT_URL'] = $filename.'.html'; $_SERVER['REQUEST_URI'] = $filename.'.html';

ob_start();// include_once ('../legacy/index.php'); \Event::run('system.routing'); \Benchmark::stop(SYSTEM_BENCHMARK.'_system_initialization'); \Event::run('system.execute'); $response = new Response(ob_get_clean());

return $response; }}

Piotr Pasich @

KOHANA?

system/core/Bootstrap.php

//Event::run('system.routing'); //Benchmark::stop(SYSTEM_BENCHMARK.'_system_initialization');//Event::run('system.shutdown');

DIE; //!

Piotr Pasich @

LAYOUTesi

VarnishGuzzle Client

Crawler

Piotr Pasich @

ESI + VARNISHhttp://todsul.com/symfony2-esi-varnish

framework: { esi: true }

Piotr Pasich @

ESI CONTROLLER/*** @Route(name="esi_center_column")*/public function getCenterColumnAction(Request $request){ $url = $request->get('url'); //almost like proxy.php $html = $this->get('xsolve.legacy.client')->requestElement($url, '.span-center');

return $this->get('xsolve.response.cache')->getResponseWithCache($html, 10);}

Piotr Pasich @

ESI SERVICEclass LegacyClient{ (...) public function requestElement($url, $element) { $html = $this->request($url); return $this->filter($html, $element); }

(...)

Piotr Pasich @

ESI SERVICE /** * @return \Symfony\Component\DomCrawler\Crawler */ public function request($url) { if (!isset($this->response[$url])) { $client = $this->getClient();

$request = $client->get($url); $request->setHeader('Cookie', null);

$this->response[$url] = $request->send(); }

return $this->response[$url]->getBody(); }

Piotr Pasich @

ESI SERVICE /** * @return \Guzzle\Http\Client */ public function getClient() { $client = new Client(); return $client; }

Piotr Pasich @

ESI SERVICE public function filter($html, $element) { $crawler = new Crawler(); $crawler->addHtmlContent($html); $crawler = $crawler->filter($element); $html = '';

foreach ($crawler as $domElement) { $html.= $domElement->ownerDocument->saveHTML($domElement); }

return $html; }

Piotr Pasich @

ESI SERVICE public function filter($html, $element) { $crawler = new Crawler(); $crawler->addHtmlContent($html); $crawler = $crawler->filter($element); $html = '';

foreach ($crawler as $domElement) { $html.= $domElement->ownerDocument->saveHTML($domElement); }

return $html; }

Piotr Pasich @

HOW TO USE IT?<!DOCTYPE html><html> <esi:include src="{{ path('esi_head') }}" /> <body> <div class="container with-background"> <esi:include src="{{ path('esi_left_column') }}" /> <div class="span-center"> {% block content %} {% endblock %} </div> <esi:include src="{{ path('esi_right_column') }}" /> <esi:include src="{{ path('esi_footer') }}" /> </div> </body></html>

Piotr Pasich @

REVERSE PROXY CACHE// app/AppCache.phprequire_once __DIR__.'/AppKernel.php';

use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;

class AppCache extends HttpCache{ protected function getOptions() { return array( 'debug' => false, 'default_ttl' => 0, 'private_headers' => array('Authorization', 'Cookie'), 'allow_reload' => true, 'allow_revalidate' => false, 'stale_while_revalidate' => 2, 'stale_if_error' => 60, ); }}

Piotr Pasich @

REVERSE PROXY CACHE<?php

// web/app.phprequire_once __DIR__.'/../app/bootstrap.php.cache';require_once __DIR__.'/../app/AppKernel.php';require_once __DIR__.'/../app/AppCache.php';

use Symfony\Component\HttpFoundation\Request;

$kernel = new AppKernel('prod', false);$kernel->loadClassCache();// wrap the default AppKernel with the AppCache one$kernel = new AppCache($kernel);$request = Request::createFromGlobals();$response = $kernel->handle($request);$response->send();$kernel->terminate($request, $response);

Piotr Pasich @

REVERSE PROXY CACHEclass ResponseCache { public function getResponseWithCache($html, $cacheTime=1) { $response = new Response($html); $response->setMaxAge($cacheTime); $response->setSharedMaxAge($cacheTime); $date = new \DateTime(); $date->modify("+$cacheTime seconds"); $response->setExpires($date);

return $response; } }

Piotr Pasich @

RENDER{% render url('latest_news', { 'max': 5 }) with {}, {'standalone': true} %}

Piotr Pasich @

ROUTINGnamespace Xsolve\LegacyBundle\Service;

class LegacyRouter{ /** @var array Legacy routes configuration */ protected $config;

public function __construct(Router $router) { include __DIR__.'/../../../../legacy/application/config/urls.php';

$this->config = $config; $this->router = $router; $this->locale = $this->router->getContext()->getParameter('_locale'); } // (..)}

Piotr Pasich @

KONFIGURACJAclass LegacyConfiguration{ protected $configuration; public function __construct($configuration) { $this->configuration = $configuration; }

public function onKernelRequest(GetResponseEvent $event) { global $legacyConfig; $legacyConfig = $this->configuration; }}

Piotr Pasich @

KONFIGURACJAglobal $legacyConfig;

$config['default'] = $legacyConfig['database']['default'];$config['import'] = $legacyConfig['database']['import'];

Piotr Pasich @

SESSIONGdzie jest problem?

_s2_(...)

Piotr Pasich @

SESSIONclass RequestListener { public function onKernelRequest(GetResponseEvent $event) { $bags = array( 'total_hits', '_kf_flash_', 'user_agent', 'last_activity', 'search.criteria', 'category.name', 'auth_user' ); foreach ($bags as $namespace) { $bag = new AttributeBag($namespace, '.'); $bag->setName($namespace); $this->session->registerBag($bag); } }}

Piotr Pasich @

SESSIONnamespace Xsolve\LegacyBundle\Session;

use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;use Symfony\Component\HttpFoundation\Session\SessionBagInterface;use Xsolve\LegacyBundle\Session\ScalarBagInterface;/*** This class provides scalar storage of session attributes using* a name spacing character in the key.** @author Piotr Pasich <piotr.pasich@xsolve.pl>*/class ScalarBag implements ScalarBagInterface, SessionBagInterface{ // (...)}

Piotr Pasich @

REQUEST LISTENER<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="xsolve.legacy.listener.request" class="Xsolve\LegacyBundle\RequestListener"> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest"/> <argument type="service" id="session" /> </service> </services>

</container>

Piotr Pasich @

NIE DZIAŁA!DLACZEGO?

Piotr Pasich @

BO KOHANA!class Session_Core { // (...) public function create($vars = NULL) { $this->destroy(); // (...) }}

Piotr Pasich @

TERAZ DZIAŁAclass Session_Core { // (...) public function create($vars = NULL) { //$this->destroy(); // (...) }}

Piotr Pasich @

TERAZ NIE DZIAŁA

Piotr Pasich @

TERAZ DZIAŁAclass Session_Core { // (...) public function create($vars = NULL) { // Destroy any current sessions self::$createCall = self::$createCall+1;

if (self::$createCall > 10){ $_SESSION = array(); } // this->destroy(); // (...) }}

Piotr Pasich @

BAZA DANYCHponad 100 tabel = 100 encjibrak odpowiednich relacji

brak pełnej zgodności z wymogami Doctrine 2

Piotr Pasich @

BAZA DANYCHapp/console doctrine:mapping:import XsolveLegacyBundle annotation

Piotr Pasich @

KONFLIKTY I BŁĘDYnaprawiamy ręcznie :(

Piotr Pasich @

PRZEPISUJEMY/*** @Route("/{categoryName}.html")*/public function indexAction($categoryName){ $criterias = array( 'category' => $categoryName );

$offers = $this->get('legacy.offers')->getRandomOffers($criterias);

$view = $this->renderView('XsolveOfferBundle:Default:index.html.twig', array( 'offers' => $offers ));

return $this->get('legacy.response.cache')->getResponseWithCache($view, 2);}

Piotr Pasich @

I TO DZIAŁA!

Piotr Pasich @

DZIĘKUJĘ ZA UWAGĘPiotr Pasich

piotr.pasich@xsolve.plwww.xsolve.pl