Date post: | 08-Jan-2017 |
Category: |
Technology |
Upload: | michal-kruczek |
View: | 608 times |
Download: | 0 times |
HOW TO WORK WITHLEGACY CODE
PHPERS RZESZÓW #2
MICHAŁ SZCZURPHP Developer since 2010
bass guitarist and trumpeter
michalszczur.plCreated by / Michał Szczur @partikus
AGENDAWhat is legacy codeWhen code can be called Legacy?How to start and not dieStep by step to heaven
WHAT IS LEGACY CODE?
CODE WRITTEN MANY YEARS AGOBY MYSELF OR OTHER NINJAS
LOW QUALITY CODE
(PRE)HISTORICAL CODE USES NONEXISTINGFRAMEWORKS/LIBRARIES
NO ENVIRONMENT SEPARATION
NO TESTS !!!
WHEN CODE CAN BE CALLED
LEGACY ???
WHEN IT IS
UNMANAGED
WHEN IT IS
TOO BUGGY
WHEN IT IS
TOO HARD TO UNDERSTAND
WHEN IT IS
NONUPGRADABLE
WHEN IT IS
UNDEBUGGABLE
WHEN IT IS
UNREADABLE
WHEN IT IS
TOO COUPLED TO FRAMEWORK
WHEN IT IS
TOO HARD TO ADD NEW FEATURE
WHAT IS NEXT?
FIGHT OR DIE
NO !!!WE'RE NINJA DEVS
WE LOVE CHALLENGES
LET'S DO IT!
TAKE NEW FRAMEWORK AND START FROM THE BEGINNING
:(MAYBE SOME DAY IN THE FUTURE
WHAT IS NEXT?
LET'S REFACTOR
BUT WHY ???
EXTENDED LEGACYCODE
WEB PHP APP
ASSUMPTIONSYOU DON'T KNOW BUSINESS LOGIC
MOSTLY PROCEDURAL CODE
NO SEPARATION, JUST A FEW LARGE FILES
YOU NEED TO CHANGE STH
HOW TO START?
ANALYZE
COVERIT MEANS WRITE TESTS
WHAT SHOULD WETEST?
WHY SHOULD WE WRITE TESTS?
LOW LEVEL DOCUMENTATIONFEATURES DESCRIBED BY SCENARIOS (E.G.
GHERKIN)CLEAN CODE
EASY TO CHANGE (LESS PAIN)EASY CONTINUOUS DEPLOYMENT
START REFACTORING
# Project structure /404.php /database.php /functions.php /index.php /page.php
PROCEDURAL CODEWRITTEN IN PHP
INLINE PHP FUNCTIONS MIXED WITH HTML,CSS,JS
#/functions.php function show_all() { $db = connect_to_db(); $sql = 'SELECT * FROM receivers'; $result = mysql_query($sql) or die(mysql_error()); while ($row = mysql_fetch_array($result)) { //echo 'ID: ' . $row['id'] . ', mail: ' . $row['mail']; echo ''.$row['mail'].''; echo '<form id="'.$row['id'].'" name="n_ID" method="POST" action=<input type="hidden" name="id" value="'.$row['id'].'" /> <input type="submit" name="delete" value="Delete" /> </form>'; } if (isset($_POST['delete'])) { $n_ID = $_POST['id']; $sql = "DELETE FROM receivers WHERE id = $n_ID"; mysql_query($sql) or die(mysql_error());
WRITE FUNCTIONALTEST
// show_all_receivers.jscasper.test.begin('List all receivers', 5, function suite(test) { casper.start("http://myapp.dev/mailsender.php", function() { test.assertHttpStatus(200); test.assertTitle("Homepage | Mail Sender", "Homepage title is expected" }); casper.thenClick('a#show-all', function() { test.assertHttpStatus(200); test.assertTitle( "Receivers list | Mail Sender", "Page title is correct" ); }); casper.run(function() { test.done(); }); });
PROCEDURAL CODE??
YOU NEED STH MORE
IOCINVERSION OF CONTROL
DEPENDENCYINJECTION
IOC IMPLEMENTATION
DON'T REINVENT THEWHEEL
PACKAGE MANAGERCOMPOSER
RUBYGEMS
MAVEN
COMPONENTSDEPENDENCY INJECTION
/ / SYMFONY DI PIMPLE AURA DI
WHY SYMFONYCOMPONENTS?
WELL TESTED
DECOUPLED
REUSABLE
WELL DOCUMENTEDAND KNOWN BY COMMUNITY
NEXT STEP IS...
MOVE APP TO /WEB/# new project structure / /web/404.php /web/database.php /web/functions.php /web/index.php /web/page.php
INSTALL COMPOSERphp -r "readfile('https://getcomposer.org/installer');" > composer-setup.phpphp -r "if (hash('SHA384', file_get_contents('composer-setup.php')) === php composer-setup.php php -r "unlink('composer-setup.php');"
INIT COMPOSER CONFIG IN THE ROOT DIRphp composer.phar init
PROJECT STRUCTUREls -l / /composer.json /web/
COMPOSER.JSON
{ "name": "michalszczur/legacy-demo", "authors": [ { "name": "Michal Szczur", "email": "[email protected]" } ], "require": {}, "autoload": { "psr-4": { "": "src/" } }, "description": "Legacy app demo", "type": "project", "license": "proprietary"
ADD DEPENDENCIESphp composer.phar require symfony/dependency-injection php composer.phar require symfony/config php composer.phar require symfony/yaml
<?php # /web/container.php use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
require __DIR__.'/../vendor/autoload.php';
$container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../app/config'$loader->load('services.yml');
$container->compile();
WHICH SERVICES DO WE REALLYNEED?
function show_all() { # Database connection needed $db = connect_to_db(); $sql = 'SELECT * FROM receivers'; $result = mysql_query($sql) or die(mysql_error()); //...
CREATE SEVICES.YML DEFINITION
SERVICES.YMLparameters: db_host: localhost db_port: 3306 db_name: legacyapp db_user: myuser db_pass: mypass
services: db: class: \PDO arguments: ['mysql:port=%db_port%;host=%db_host%;dbname=%db_name%' receiver_repository: class: PDOReceiverRepository arguments: ['@db']
USE CONTAINER AND REPOSITORY
#index.php ... require 'container.php'; ... function show_all() { # Database connection needed $receiverRepository = $container->get('receiver_repository'); $result = $receiverRepository->findAll(); ...
#index.php ... $result = $receiverRepository->findAll(); foreach ($result as $row) { echo ''.$row['mail'].''; echo '<form id="'.$row['id'].'" name="n_ID" method="POST" action="mail_sender.php?page=show_all"><input type="hidden" name="id" value="'.$row['id'].'" /> <input type="submit" name="delete" value="Usuń" /> </form>'; } ...
PHP ~ HTMLSTILL MIXED TOGETHER
TWIGTEMPLATE ENGINE FOR PHP
INSTALL TWIG USING COMPOSERphp composer.phar require twig/twig
SERVICES.YMLparameters: ... twig_paths: - app/Resources/views services: ... twig.loader: class: Twig_Loader_Filesystem arguments: ['%twig_paths%'] twig: class: Twig_Environment arguments: ['@twig.loader']
#index.php ... $twig = $container->get('@twig'); $result = $receiverRepository->findAll(); echo $twig->render( 'receiver_list.html.twig', ['receivers' => $result] ); ...
{% for receiver in receivers %} <form id="{{ receiver.id }}" name="n_ID" method="POST" action="mail_sender.php?page=show_all"<input type="hidden" name="id" value="{{ receiver.id }}" /> <input type="submit" name="delete" value="Delete" /> </form> {% endfor %}
BEFOREfunction show_all() { $db = connect_to_db(); $sql = 'SELECT * FROM mail_sender'; $result = mysql_query($sql) or die(mysql_error()); while ($row = mysql_fetch_array($result)) { //echo 'ID: ' . $row['id'] . ', mail: ' . $row['mail']; echo ''.$row['mail'].''; echo '<form id="'.$row['id'].'" name="n_ID" method="POST" action="mail_sender.php?page=show_all"><input type="hidden" name="id" value="'.$row['id'].'" /> <input type="submit" name="delete" value="Usuń" /> </form>'; } ... mysql_free_result($result); mysql_close($db); }
AFTERfunction show_all() { $receiverRepository = $container->get('receiver_repository'); $twig = $container->get('@twig'); $result = $receiverRepository->findAll(); echo $twig->render( 'receiver_list.html.twig', ['receivers' => $result] ... );
LET'S SUM UP
COMMUNICATION
ANALYZE
TESTS
SMALL STEPS
NOT EVERYTHING SIMUNTANOUSLY
PACKAGE MANAGER
DEPENDENCY INJECTION
THIRD PARTY LIBRARIES
DESIGN PATTERNS
SOLID
DECOUPLE
TESTS
THANKS!
QUESTIONS?SLIDES: SLIDESHARE.NET/MICHASZCZUR
TWITTER: @PARTIKUS
HOMEPAGE: MICHALSZCZUR.PL
LINKSCasperJSSymfony ComponentsWorking Effectively with Legacy Code by Michael FeathersPage Object Pattern (Martin Fowler)Page Object Pattern (Selenium Docs)Marek Matulka - Modernising the Legacy