Legacy applicationsPiotr Pasich @
więc...przychodzi klient do lekarza
ZACZNIJMY OD KLIENTA
Piotr Pasich @
żeby działałona wczoraj
ewentualnie ASAP
CZEGO OCZEKUJE?
Piotr Pasich @
Ile to będzie trwało?
9 miesięcy
PRZEPISZMY TO!
Piotr Pasich @
Piotr Pasich @
Ile to będzie trwało?
2 miesiące
NADPISZMY TO!
Piotr Pasich @
FROM SPAGHETTI TO CODEczyli dziedziczymy aplikację
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
namespacenamespace Legacy { (...)}
Piotr Pasich @
LegacyBundleapp/console generate:bundleBundle 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 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 @
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 @
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 @
RENDER{% render url('latest_news', { 'max': 5 }) with {}, {'standalone': true} %}
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 @
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 anotation
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 @
PRZEPISUJEMY{% extends 'XsolveLegacyBundle:Legacy:layout.html.twig' %}{% block content %}<div class="boxer_main anons_set"> {% for offer in offers %} <div class="anons"> <a href="{{ path('legacy_offers_view' , {'id': offer.id, 'city': offer.city.name|makeUri, 'district': offer.district.name|makeUri, 'slug': offer.name|makeUri}) }}" target="_blank"> <img src="{{ STATIC_URL }}thumbs/gallery/{{ offer.galleryId }}/{{ offer.getRandomPhotoName() }}.128x128" alt="" {#popup_text offer=$offer criteria=$criteria#} /></a><br /> {% if offer.isRealphoto %} <a style="display : inline-block;" class="sprite sprite-ptaszek" href="{{ path('legacy_offers_view' , {'id': offer.id, 'city': offer.city.name|makeUri, 'district': offer.district.name|makeUri, 'slug': offer.name|makeUri}) }}" > </a> {% endif %} <a title="{{ offer.name }}" href="{{ path('legacy_offers_view' , {'id': offer.id, 'city': offer.city.name|makeUri, 'district': offer.district.name|makeUri, 'slug': offer.name|makeUri}) }}" target="_blank"> {{ offer.name }} </a> </div> {% endfor %} <div class="clear"></div></div>{% endblock %}
Piotr Pasich @
I TO DZIAŁAPiotr Pasich @