Refactoring legacy PHP


by Enrico Zimuel
Principal Software Engineer @ Elastic


PUG Torino, Feb. 19, 2020

About me

Refactoring legacy applications

Refactoring

Refactoring is the process of changing the structure of code without changing its behavior

Legacy code

Legacy code is source code that relates to a no-longer supported or manufactured operating system or other computer technology
Code that developers are afraid to change

PHP

Unit test

Never refactor a production code that does not have unit tests

Tool: PHPUnit

Mock

For example, using the following class:


class Foo
{
    public function __construct(BarInterface $bar)
    {
        $this->bar = $bar;
    }

    public function baz()
    {
        return $this->bar->doSomething(/* ... */);
    }
}

Mock (2)


use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testStub()
    {
        $stub = $this->getMockBuilder(BarInterface::class)
            ->getMock();

        $stub->method('doSomething')
            ->willReturn('foo');

        $foo = new Foo($stub);
        $this->assertSame('foo', $foo->baz());
    }
}

Mockery

Method Stubs


class Book {}

interface BookRepository {
    function find($id): Book;
    function findAll(): array;
    function add(Book $book): void;
}
	
$double = Mockery::mock(BookRepository::class);

$double->allows()->find(123)->andReturns(new Book());
$book = $double->find(123);

Call Expectations


class Temperature
{
    private $service;

    public function __construct($service)
    {
        $this->service = $service;
    }
    public function average()
    {
        $total = 0;
        for ($i=0; $i<3; $i++) {
            $total += $this->service->readTemp();
        }
        return $total/3;
    }
}

Call Expectations (2)


class TemperatureTest extends \PHPUnit\Framework\TestCase
{
    public function tearDown()
    {
        Mockery::close();
    }
    public function testGetsAvgTemperatureFrom3ServiceReadings()
    {
        $service = Mockery::mock('service');
        $service->shouldReceive('readTemp')
            ->times(3)
            ->andReturn(10, 12, 14);

        $temperature = new Temperature($service);
        $this->assertEquals(12, $temperature->average());
    }
}

Test Spies


$spy = \Mockery::spy('MyDependency');
$sut = new MyClass($spy);

// act
$sut->callFoo();

// assert
$spy->shouldHaveReceived()
	->foo()
	->with('bar');	

Code coverage

Feature of PHPUnit to measure how much code is covered by the unit test

Autoloading

Composer

Many options for legacy code (not PSR-4)

* deprecated, but still useful

Classmap example


{
    "autoload": {
        "classmap": ["src/", "lib/", "Something.php"]
    }
}	

Tools for refactoring

Some tools

  • PHPCPD, Copy & Paste detector
  • PHPLOC, measuring and analyzing PHP project
  • Rector, reconstructor tool, it does instant upgrades and instant refactoring of your code
  • PHPStan, static analysis tool
  • Phan, static analyzer for PHP
  • Exakat, automated code reviewing engine for PHP

References

Thanks!

Follow me: @ezimuel



Creative Commons License
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
I used reveal.js to make this presentation.