Legacy applications - 4Developes konferencja, Piotr Pasich
-
Upload
piotr-pasich -
Category
Technology
-
view
538 -
download
0
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 <[email protected]>*/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