Qafoo GmbH - passion for software quality

Help you and your team benefit from new perspectives on cutting-edge quality engineering techniques and tools through the Qafoo team weblog.

By Benjamin Eberlei, first published at Mon, 08 Apr 2013 09:56:18 +0200

PHP Refactoring Browser Alpha Release

Without continuous refactoring, code maintainability and extensibility will start to decrease fast, even if it has tests. Until now, only IDEs contained functionality to perform automated refactorings. And then even only PHPStorm contains the most important refactorings such as "extract method".

Refactoring is an important skill for your team to make software maintainable and keep it like that. Base these capabilities on the sound basis of an individual Qafoo training for your team.

Today we release the PHP Refactoring Browser, a refactoring tool written completely in PHP. It is based on several outstanding open-source libraries:

The Browser currently supports the following refactorings:

  • Extract Method

  • Rename Local Variable

  • Convert Local to Instance Variable

The result of each refactoring is a patch, which is printed to the screen. You can review the patch and then apply it to your source code using Unixs patch command.

Let's discuss a sample code and apply some refactorings to it:

<?php // tests/FooServiceTest.php class FooServiceTest extends PHPUnit_Framework_TestCase { public function testFoo() { $dependency = $this->getMock('DependencyService'); $service = new FooService($dependency); $dependency->expects($this->once())->method('doSomething'); $value = $service->perform(); $this->assertEquals(42, $value); } }

For our next test, we need the same test setup, so we want to refactor the first two lines into the setUp() method and convert the local variable into an instance variable:

$ php refactor.phar convert-local-to-instance-variable tests/FooServiceTest.php 6 dependency

Prints:

--- a/tests/FooService.php +++ b/tests/FooService.php @@ -2,11 +2,13 @@ // tests/FooServiceTest.php class FooServiceTest extends PHPUnit_Framework_TestCase { + private $dependency; + public function testFoo() { - $dependency = $this->getMock('DependencyService'); + $this->dependency = $this->getMock('DependencyService'); - $service = new FooService($dependency); + $service = new FooService($this->dependency); - $dependency->expects($this->once())->method('doSomething'); + $this->dependency->expects($this->once())->method('doSomething'); $value = $service->perform();

We can apply the patch by calling the command again and pipe to |patch -p1. Now we want to do the same to the $service variable:

$ php refactor.phar convert-local-to-instance-variable tests/FooServiceTest.php 10 service

We get the patch:

--- a/tests/FooService.php +++ b/tests/FooService.php @@ -3,5 +3,7 @@ class FooServiceTest extends PHPUnit_Framework_TestCase { private $dependency; + private $service; + public function testFoo() @@ -8,5 +8,5 @@ { $this->dependency = $this->getMock('DependencyService'); - $service = new FooService($this->dependency); + $this->service = new FooService($this->dependency); $this->dependency->expects($this->once())->method('doSomething'); @@ -12,5 +12,5 @@ $this->dependency->expects($this->once())->method('doSomething'); - $value = $service->perform(); + $value = $this->service->perform(); $this->assertEquals(42, $value);

Now we extract the first two lines into the setUp() method:

$ php refactor.phar extract-method tests/FooServiceTest.php 11-12 setUp

We get the patch:

--- a/test.php +++ b/test.php @@ -9,6 +9,5 @@ public function testFoo() { - $this->dependency = $this->getMock('DependencyService'); - $this->service = new FooService($this->dependency); + $this->setUp(); $this->dependency->expects($this->once())->method('doSomething'); @@ -17,5 +17,11 @@ $this->assertEquals(42, $value); } + + private function setUp() + { + $this->dependency = $this->getMock('DependencyService'); + $this->service = new FooService($this->dependency); + } }

And we can see the setUp() method is created in the class and called in testFoo(). When we apply the patch we know that PHPUnit calls setUp() itself and requires it to be public, so we change this code manually to end up with:

<?php // tests/FooServiceTest.php class FooServiceTest extends PHPUnit_Framework_TestCase { private $dependency; private $service; public function testFoo() { $this->dependency->expects($this->once())->method('doSomething'); $value = $this->service->perform(); $this->assertEquals(42, $value); } public function setUp() { $this->dependency = $this->getMock('DependencyService'); $this->service = new FooService($this->dependency); } }

This project already has been a valuable tool for us in a customer project and we intent to make it much more useful over time:

  • Integration into Vim, using hot-keys to perform refactorings

  • Introducing more refactorings and improving the existing ones

Currently you can easily break the refactorings when coming up with weird code-scenarios. We will try to handle as much edge-cases as PHP Parser, PHP Token Reflection and PHP Analyzer allow us to.

To install the Refactoring Browser, head over to it's website on Github and download the PHAR file from there.

Of course, contributions are highly welcome via pull requests on Github.

Comments

  • hyh on Thu, 11 Apr 2013 09:47:52 +0200

    When the final version is out?

  • beberlei on Thu, 11 Apr 2013 11:10:11 +0200

    @hyh there will probably never be a final version, rather the individual refactorings reach stability or are considered experimental.

    I suppose thats a good feature to add, mentioning the stabillity of refactorings.