SlideShare ist ein Scribd-Unternehmen logo
1 von 40
Downloaden Sie, um offline zu lesen
Legacy applications
Piotr Pasich @
więc...
przychodzi klient do lekarza
ZACZNIJMY OD KLIENTA
Piotr Pasich @
żeby działało
na 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 CODE
PREVENTING REGRESSIONS
czyli nic nie ruszać
Piotr Pasich @
TESTY FUNKCJONALNOŚCI
Selenium IDE, behat, testy jednostkowe
Piotr Pasich @
ŚRODOWISKO
minimum PHP 5.3.3
Sqlite3, JSON, ctype
php app/check.php
date.timezone set in php.ini
Phpcs CodeSniffs
tutaj po raz pierwszy korzystamy z testów
Piotr Pasich @
INSTALACJA SYMFONY 2
katalog legacy
namespace
namespace Legacy {
(...)
}
Piotr Pasich @
LegacyBundle
app/console generate:bundle
Bundle namespace: XsolveLegacyBundle
Piotr Pasich @
AUTOLOADER
<?php
namespace XsolveLegacyBundle;
require_once(__DIR__ . "/../../../legacy/index.php"); //disabled execute::run
use Legacy;
use SymfonyComponentHttpKernelBundleBundle;
use SymfonyComponentDependencyInjectionContainerBuilder;
class XsolveLegacyBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
spl_autoload_register(array('Kohana', 'auto_load'));
}
}
Piotr Pasich @
MainAction
class 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 @
LAYOUT
esi
Varnish
Guzzle Client
Crawler
Piotr Pasich @
ESI + VARNISH
http://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 SERVICE
class LegacyClient
{
(...)
public function requestElement($url, $element)
{
$html = $this->request($url);
return $this->filter($html, $element);
}
(...)
Piotr Pasich @
ESI SERVICE
/**
* @return SymfonyComponentDomCrawlerCrawler
*/
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.php
require_once __DIR__.'/AppKernel.php';
use SymfonyBundleFrameworkBundleHttpCacheHttpCache;
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.php
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';
require_once __DIR__.'/../app/AppCache.php';
use SymfonyComponentHttpFoundationRequest;
$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 CACHE
class 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 @
SESSION
Gdzie jest problem?
_s2_(...)
Piotr Pasich @
SESSION
class 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.
>
<services>
<service id="xsolve.legacy.listener.request" class="XsolveLegacyBundleRequestListener">
<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ŁA
class Session_Core {
// (...)
public function create($vars = NULL)
{
//$this->destroy();
// (...)
}
}
Piotr Pasich @
TERAZ NIE DZIAŁA
Piotr Pasich @
TERAZ DZIAŁA
class 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 DANYCH
ponad 100 tabel = 100 encji
brak odpowiednich relacji
brak pełnej zgodności z wymogami Doctrine 2
Piotr Pasich @
BAZA DANYCH
app/console doctrine:mapping:import XsolveLegacyBundle anotation
Piotr Pasich @
KONFLIKTY I BŁĘDY
naprawiamy 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ŁA
Piotr Pasich @
piotr.pasich@xsolve.pl
www.xsolve.pl
www.xlab.pl

Weitere ähnliche Inhalte

Was ist angesagt?

BASH Variables Part 1: Basic Interpolation
BASH Variables Part 1: Basic InterpolationBASH Variables Part 1: Basic Interpolation
BASH Variables Part 1: Basic InterpolationWorkhorse Computing
 
Nigel hamilton-megameet-2013
Nigel hamilton-megameet-2013Nigel hamilton-megameet-2013
Nigel hamilton-megameet-2013trexy
 
Selenium sandwich-3: Being where you aren't.
Selenium sandwich-3: Being where you aren't.Selenium sandwich-3: Being where you aren't.
Selenium sandwich-3: Being where you aren't.Workhorse Computing
 
Europython 2011 - Playing tasks with Django & Celery
Europython 2011 - Playing tasks with Django & CeleryEuropython 2011 - Playing tasks with Django & Celery
Europython 2011 - Playing tasks with Django & CeleryMauro Rocco
 
An Introduction to Celery
An Introduction to CeleryAn Introduction to Celery
An Introduction to CeleryIdan Gazit
 
Puppet: What _not_ to do
Puppet: What _not_ to doPuppet: What _not_ to do
Puppet: What _not_ to doPuppet
 
PuppetCamp Ghent - What Not to Do with Puppet
PuppetCamp Ghent - What Not to Do with PuppetPuppetCamp Ghent - What Not to Do with Puppet
PuppetCamp Ghent - What Not to Do with PuppetWalter Heck
 
Django Celery - A distributed task queue
Django Celery - A distributed task queueDjango Celery - A distributed task queue
Django Celery - A distributed task queueAlex Eftimie
 
V2 and beyond
V2 and beyondV2 and beyond
V2 and beyondjimi-c
 
Perl Sucks - and what to do about it
Perl Sucks - and what to do about itPerl Sucks - and what to do about it
Perl Sucks - and what to do about it2shortplanks
 
Great Developers Steal
Great Developers StealGreat Developers Steal
Great Developers StealBen Scofield
 
Building Cloud Castles - LRUG
Building Cloud Castles - LRUGBuilding Cloud Castles - LRUG
Building Cloud Castles - LRUGBen Scofield
 
Perl web app 테스트전략
Perl web app 테스트전략Perl web app 테스트전략
Perl web app 테스트전략Jeen Lee
 
Kansai.pm 10周年記念 Plack/PSGI 入門
Kansai.pm 10周年記念 Plack/PSGI 入門Kansai.pm 10周年記念 Plack/PSGI 入門
Kansai.pm 10周年記念 Plack/PSGI 入門lestrrat
 

Was ist angesagt? (20)

Memory Manglement in Raku
Memory Manglement in RakuMemory Manglement in Raku
Memory Manglement in Raku
 
Lies, Damn Lies, and Benchmarks
Lies, Damn Lies, and BenchmarksLies, Damn Lies, and Benchmarks
Lies, Damn Lies, and Benchmarks
 
Nubilus Perl
Nubilus PerlNubilus Perl
Nubilus Perl
 
BASH Variables Part 1: Basic Interpolation
BASH Variables Part 1: Basic InterpolationBASH Variables Part 1: Basic Interpolation
BASH Variables Part 1: Basic Interpolation
 
Nigel hamilton-megameet-2013
Nigel hamilton-megameet-2013Nigel hamilton-megameet-2013
Nigel hamilton-megameet-2013
 
Selenium sandwich-3: Being where you aren't.
Selenium sandwich-3: Being where you aren't.Selenium sandwich-3: Being where you aren't.
Selenium sandwich-3: Being where you aren't.
 
Europython 2011 - Playing tasks with Django & Celery
Europython 2011 - Playing tasks with Django & CeleryEuropython 2011 - Playing tasks with Django & Celery
Europython 2011 - Playing tasks with Django & Celery
 
An Introduction to Celery
An Introduction to CeleryAn Introduction to Celery
An Introduction to Celery
 
Puppet: What _not_ to do
Puppet: What _not_ to doPuppet: What _not_ to do
Puppet: What _not_ to do
 
PuppetCamp Ghent - What Not to Do with Puppet
PuppetCamp Ghent - What Not to Do with PuppetPuppetCamp Ghent - What Not to Do with Puppet
PuppetCamp Ghent - What Not to Do with Puppet
 
Django Celery - A distributed task queue
Django Celery - A distributed task queueDjango Celery - A distributed task queue
Django Celery - A distributed task queue
 
Fake My Party
Fake My PartyFake My Party
Fake My Party
 
Getting testy with Perl
Getting testy with PerlGetting testy with Perl
Getting testy with Perl
 
V2 and beyond
V2 and beyondV2 and beyond
V2 and beyond
 
Perl Sucks - and what to do about it
Perl Sucks - and what to do about itPerl Sucks - and what to do about it
Perl Sucks - and what to do about it
 
Great Developers Steal
Great Developers StealGreat Developers Steal
Great Developers Steal
 
Building Cloud Castles - LRUG
Building Cloud Castles - LRUGBuilding Cloud Castles - LRUG
Building Cloud Castles - LRUG
 
Findbin libs
Findbin libsFindbin libs
Findbin libs
 
Perl web app 테스트전략
Perl web app 테스트전략Perl web app 테스트전략
Perl web app 테스트전략
 
Kansai.pm 10周年記念 Plack/PSGI 入門
Kansai.pm 10周年記念 Plack/PSGI 入門Kansai.pm 10周年記念 Plack/PSGI 入門
Kansai.pm 10周年記念 Plack/PSGI 入門
 

Andere mochten auch

A2 Media Studies - Music Video Analysis
A2 Media Studies - Music Video AnalysisA2 Media Studies - Music Video Analysis
A2 Media Studies - Music Video AnalysisPheebs023
 
2008 smid
2008 smid2008 smid
2008 smid604381
 
eToro DevOps presentation
eToro DevOps presentationeToro DevOps presentation
eToro DevOps presentationDavid Virtser
 
Project management and unveiling risks
Project management and unveiling risksProject management and unveiling risks
Project management and unveiling risksmds-web
 
васькин мнск13
васькин мнск13васькин мнск13
васькин мнск13vaskinyy
 
Reaching & Connecting with the BOOMER CONSUMER - by EtMM Aaron D. Murphy
Reaching & Connecting with the BOOMER CONSUMER - by EtMM Aaron D. MurphyReaching & Connecting with the BOOMER CONSUMER - by EtMM Aaron D. Murphy
Reaching & Connecting with the BOOMER CONSUMER - by EtMM Aaron D. MurphyAaron D. Murphy, Architect / CAPS
 
PHPConPl 2013 - Allowed memory size of X bytes exhausted
PHPConPl 2013 - Allowed memory size of X bytes exhaustedPHPConPl 2013 - Allowed memory size of X bytes exhausted
PHPConPl 2013 - Allowed memory size of X bytes exhaustedPiotr Pasich
 
Performance
PerformancePerformance
PerformanceKoay Hui
 
Tracking norm
Tracking normTracking norm
Tracking normy_anni
 
ідеї для вечірнього макіяжу
ідеї для вечірнього макіяжуідеї для вечірнього макіяжу
ідеї для вечірнього макіяжуAnnaBoyko1
 
устюгова катя 145 группа
устюгова катя 145 группаустюгова катя 145 группа
устюгова катя 145 группаKatya Ustyugova
 
Water customer presentation
Water customer presentationWater customer presentation
Water customer presentationSuj Kang
 
PPM (Private Equtiy/Stock Offering)
PPM (Private Equtiy/Stock Offering)PPM (Private Equtiy/Stock Offering)
PPM (Private Equtiy/Stock Offering)Scott Davies
 

Andere mochten auch (19)

A2 Media Studies - Music Video Analysis
A2 Media Studies - Music Video AnalysisA2 Media Studies - Music Video Analysis
A2 Media Studies - Music Video Analysis
 
2008 smid
2008 smid2008 smid
2008 smid
 
eToro DevOps presentation
eToro DevOps presentationeToro DevOps presentation
eToro DevOps presentation
 
Project management and unveiling risks
Project management and unveiling risksProject management and unveiling risks
Project management and unveiling risks
 
Twitter
TwitterTwitter
Twitter
 
Empowering The Mature Mind - SUMMER 2014 Newsletter
Empowering The Mature Mind - SUMMER 2014 NewsletterEmpowering The Mature Mind - SUMMER 2014 Newsletter
Empowering The Mature Mind - SUMMER 2014 Newsletter
 
васькин мнск13
васькин мнск13васькин мнск13
васькин мнск13
 
Reaching & Connecting with the BOOMER CONSUMER - by EtMM Aaron D. Murphy
Reaching & Connecting with the BOOMER CONSUMER - by EtMM Aaron D. MurphyReaching & Connecting with the BOOMER CONSUMER - by EtMM Aaron D. Murphy
Reaching & Connecting with the BOOMER CONSUMER - by EtMM Aaron D. Murphy
 
PHPConPl 2013 - Allowed memory size of X bytes exhausted
PHPConPl 2013 - Allowed memory size of X bytes exhaustedPHPConPl 2013 - Allowed memory size of X bytes exhausted
PHPConPl 2013 - Allowed memory size of X bytes exhausted
 
Performance
PerformancePerformance
Performance
 
Time management
Time managementTime management
Time management
 
Tracking norm
Tracking normTracking norm
Tracking norm
 
ідеї для вечірнього макіяжу
ідеї для вечірнього макіяжуідеї для вечірнього макіяжу
ідеї для вечірнього макіяжу
 
Tugas kkpi
Tugas kkpiTugas kkpi
Tugas kkpi
 
Task groups
Task groupsTask groups
Task groups
 
Users in casual
Users in casualUsers in casual
Users in casual
 
устюгова катя 145 группа
устюгова катя 145 группаустюгова катя 145 группа
устюгова катя 145 группа
 
Water customer presentation
Water customer presentationWater customer presentation
Water customer presentation
 
PPM (Private Equtiy/Stock Offering)
PPM (Private Equtiy/Stock Offering)PPM (Private Equtiy/Stock Offering)
PPM (Private Equtiy/Stock Offering)
 

Ähnlich wie #SPUG - Legacy applications

Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit TestingMike Lively
 
Ch ch-changes cake php2
Ch ch-changes cake php2Ch ch-changes cake php2
Ch ch-changes cake php2markstory
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEnterprise PHP Center
 
Building Testable PHP Applications
Building Testable PHP ApplicationsBuilding Testable PHP Applications
Building Testable PHP Applicationschartjes
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Michelangelo van Dam
 
Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Designunodelostrece
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryTatsuhiko Miyagawa
 
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastWorkshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastMichelangelo van Dam
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Elena Kolevska
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and PythonPiXeL16
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015Fernando Daciuk
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Michelangelo van Dam
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)andrewnacin
 
Debugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 VersionDebugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 VersionIan Barber
 
Introduction to PHP
Introduction to PHPIntroduction to PHP
Introduction to PHPBradley Holt
 
Debugging: Rules & Tools
Debugging: Rules & ToolsDebugging: Rules & Tools
Debugging: Rules & ToolsIan Barber
 
Build restful ap is with python and flask
Build restful ap is with python and flaskBuild restful ap is with python and flask
Build restful ap is with python and flaskJeetendra singh
 

Ähnlich wie #SPUG - Legacy applications (20)

Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit Testing
 
Ch ch-changes cake php2
Ch ch-changes cake php2Ch ch-changes cake php2
Ch ch-changes cake php2
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 
Building Testable PHP Applications
Building Testable PHP ApplicationsBuilding Testable PHP Applications
Building Testable PHP Applications
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
 
Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Design
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
 
Workshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfastWorkshop quality assurance for php projects - phpbelfast
Workshop quality assurance for php projects - phpbelfast
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
 
Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012Quality Assurance for PHP projects - ZendCon 2012
Quality Assurance for PHP projects - ZendCon 2012
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)Best Practices in Plugin Development (WordCamp Seattle)
Best Practices in Plugin Development (WordCamp Seattle)
 
Debugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 VersionDebugging: Rules And Tools - PHPTek 11 Version
Debugging: Rules And Tools - PHPTek 11 Version
 
Introduction to PHP
Introduction to PHPIntroduction to PHP
Introduction to PHP
 
Fatc
FatcFatc
Fatc
 
Debugging: Rules & Tools
Debugging: Rules & ToolsDebugging: Rules & Tools
Debugging: Rules & Tools
 
Build restful ap is with python and flask
Build restful ap is with python and flaskBuild restful ap is with python and flask
Build restful ap is with python and flask
 

Mehr von Piotr Pasich

Messaging queues with RabbitMQ
Messaging queues with RabbitMQMessaging queues with RabbitMQ
Messaging queues with RabbitMQPiotr Pasich
 
How to write applications prepared for every cataclysm with Event Sourcing an...
How to write applications prepared for every cataclysm with Event Sourcing an...How to write applications prepared for every cataclysm with Event Sourcing an...
How to write applications prepared for every cataclysm with Event Sourcing an...Piotr Pasich
 
Varnish - Tips & Tricks - 4Developers 2015
Varnish - Tips & Tricks - 4Developers 2015Varnish - Tips & Tricks - 4Developers 2015
Varnish - Tips & Tricks - 4Developers 2015Piotr Pasich
 
Arduino - SymfonyCon 2014 Lighting Talks
Arduino - SymfonyCon 2014 Lighting TalksArduino - SymfonyCon 2014 Lighting Talks
Arduino - SymfonyCon 2014 Lighting TalksPiotr Pasich
 
Game of performance - Message Brokers behind the scenes
Game of performance - Message Brokers behind the scenesGame of performance - Message Brokers behind the scenes
Game of performance - Message Brokers behind the scenesPiotr Pasich
 
Simplify your code with annotations - SymfonyCon Warsaw 2013
Simplify your code with annotations - SymfonyCon Warsaw 2013Simplify your code with annotations - SymfonyCon Warsaw 2013
Simplify your code with annotations - SymfonyCon Warsaw 2013Piotr Pasich
 
SpreadIT - Make your project SOLID!
SpreadIT - Make your project SOLID! SpreadIT - Make your project SOLID!
SpreadIT - Make your project SOLID! Piotr Pasich
 

Mehr von Piotr Pasich (7)

Messaging queues with RabbitMQ
Messaging queues with RabbitMQMessaging queues with RabbitMQ
Messaging queues with RabbitMQ
 
How to write applications prepared for every cataclysm with Event Sourcing an...
How to write applications prepared for every cataclysm with Event Sourcing an...How to write applications prepared for every cataclysm with Event Sourcing an...
How to write applications prepared for every cataclysm with Event Sourcing an...
 
Varnish - Tips & Tricks - 4Developers 2015
Varnish - Tips & Tricks - 4Developers 2015Varnish - Tips & Tricks - 4Developers 2015
Varnish - Tips & Tricks - 4Developers 2015
 
Arduino - SymfonyCon 2014 Lighting Talks
Arduino - SymfonyCon 2014 Lighting TalksArduino - SymfonyCon 2014 Lighting Talks
Arduino - SymfonyCon 2014 Lighting Talks
 
Game of performance - Message Brokers behind the scenes
Game of performance - Message Brokers behind the scenesGame of performance - Message Brokers behind the scenes
Game of performance - Message Brokers behind the scenes
 
Simplify your code with annotations - SymfonyCon Warsaw 2013
Simplify your code with annotations - SymfonyCon Warsaw 2013Simplify your code with annotations - SymfonyCon Warsaw 2013
Simplify your code with annotations - SymfonyCon Warsaw 2013
 
SpreadIT - Make your project SOLID!
SpreadIT - Make your project SOLID! SpreadIT - Make your project SOLID!
SpreadIT - Make your project SOLID!
 

Kürzlich hochgeladen

08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?Igalia
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 

Kürzlich hochgeladen (20)

08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 

#SPUG - Legacy applications

  • 2. więc... przychodzi klient do lekarza ZACZNIJMY OD KLIENTA Piotr Pasich @
  • 3. żeby działało na wczoraj ewentualnie ASAP CZEGO OCZEKUJE? Piotr Pasich @
  • 4. Ile to będzie trwało? 9 miesięcy PRZEPISZMY TO! Piotr Pasich @
  • 6. Ile to będzie trwało? 2 miesiące NADPISZMY TO! Piotr Pasich @
  • 8. PREVENTING REGRESSIONS czyli nic nie ruszać Piotr Pasich @
  • 9. TESTY FUNKCJONALNOŚCI Selenium IDE, behat, testy jednostkowe Piotr Pasich @
  • 10. ŚRODOWISKO minimum PHP 5.3.3 Sqlite3, JSON, ctype php app/check.php date.timezone set in php.ini Phpcs CodeSniffs tutaj po raz pierwszy korzystamy z testów Piotr Pasich @
  • 11. INSTALACJA SYMFONY 2 katalog legacy namespace namespace Legacy { (...) } Piotr Pasich @
  • 13. AUTOLOADER <?php namespace XsolveLegacyBundle; require_once(__DIR__ . "/../../../legacy/index.php"); //disabled execute::run use Legacy; use SymfonyComponentHttpKernelBundleBundle; use SymfonyComponentDependencyInjectionContainerBuilder; class XsolveLegacyBundle extends Bundle { public function build(ContainerBuilder $container) { spl_autoload_register(array('Kohana', 'auto_load')); } } Piotr Pasich @
  • 14. MainAction class 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 @
  • 18. 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 @
  • 19. ESI SERVICE class LegacyClient { (...) public function requestElement($url, $element) { $html = $this->request($url); return $this->filter($html, $element); } (...) Piotr Pasich @
  • 20. ESI SERVICE /** * @return SymfonyComponentDomCrawlerCrawler */ 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 @
  • 21. 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 @
  • 22. REVERSE PROXY CACHE // app/AppCache.php require_once __DIR__.'/AppKernel.php'; use SymfonyBundleFrameworkBundleHttpCacheHttpCache; 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 @
  • 23. REVERSE PROXY CACHE <?php // web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; require_once __DIR__.'/../app/AppCache.php'; use SymfonyComponentHttpFoundationRequest; $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 @
  • 24. REVERSE PROXY CACHE class 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 @
  • 25. 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 @
  • 26. RENDER {% render url('latest_news', { 'max': 5 }) with {}, {'standalone': true} %} Piotr Pasich @
  • 28. SESSION class 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 @
  • 29. 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. > <services> <service id="xsolve.legacy.listener.request" class="XsolveLegacyBundleRequestListener"> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest"/> <argument type="service" id="session" /> </service> </services> </container> Piotr Pasich @
  • 31. BO KOHANA!class Session_Core { // (...) public function create($vars = NULL) { $this->destroy(); // (...) } } Piotr Pasich @
  • 32. TERAZ DZIAŁA class Session_Core { // (...) public function create($vars = NULL) { //$this->destroy(); // (...) } } Piotr Pasich @
  • 34. TERAZ DZIAŁA class 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 @
  • 35. BAZA DANYCH ponad 100 tabel = 100 encji brak odpowiednich relacji brak pełnej zgodności z wymogami Doctrine 2 Piotr Pasich @
  • 36. BAZA DANYCH app/console doctrine:mapping:import XsolveLegacyBundle anotation Piotr Pasich @
  • 37. KONFLIKTY I BŁĘDY naprawiamy ręcznie :( Piotr Pasich @
  • 38. 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 @
  • 39. 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 @
  • 40. I TO DZIAŁA Piotr Pasich @ piotr.pasich@xsolve.pl www.xsolve.pl www.xlab.pl