REFACTORING
PHP/SYMFONY2
APPS
Thank you to all our sponsors!
Raúl Fraile
• Software developer at
• PHP 5.3 Zend Certified Engineer
• Symfony Certified Developer
• LadybugPHP
raulfraile
1. Refactoring 101
2. Coding Standard
3. IDE
4. Code/Data separation
5. Environment coupling
6. Don’t Repeat Yourself
7. Fat controllers
Agenda
Test project
APIJokes (I)
GET /api/list
Get the jokes list in JSON
POST /api/add
Add a new joke
POST /api/edit
Edit an existing joke
GET /
Public website with the joke list in HTML
APIJokes (II)
Special cases
Every time a joke is added or edited,
an email is sent to the administrator.
We don’t allow jokes about Java.
raulfraile/apijokes
There are tags for each step!
Refactoring 101
Rewrite VS Refactor
Rewrite
http://www.flickr.com/photos/meliah/2601885140/
http://www.flickr.com/photos/90692443@N05/8239219385/
Refactor
Refactoring is a
continuous process
Always taking a software
that works...
...and which has tests
Coding Standard
http://www.flickr.com/photos/zpeckler/2835570492/
Change my CS?!
Important: Choose a CS
and be consistent
Open source projects:
share the same CS
PSR-1/2
http://cs.sensiolabs.org
php-cs-fixer fix ApiJokesBundle/ --level=all --dry-run --diff -v
1) Controller/WebsiteController.php (braces, return)
---------- begin diff ----------
--- Original
+++ New
@@ @@
-class WebsiteController extends Controller {
- public function indexAction() {
+class WebsiteController extends Controller
+{
+ public function indexAction()
+ {
$em = $this->getDoctrine()->getManager();
$jokes = $em->getRepository('...')->findAll();
+
return $this->render('...', array(
'jokes' => $jokes
));
}
}
---------- end diff ----------
IDE
Problems: DIC, foreach,
repositories...
<?php
 
$mailer = $this->get('mailer');
$mailer->send($message);
<?php
 
/** @var $mailer Swift_Mailer */
$mailer = $this->get('mailer');
$mailer->send($message);
Type hints, not only to
help the IDE...
<?php
 
$data = array_map(function ($item) {
return array(
'id' => $item->getId(),
'content' => $item->getContent()
);
}, $jokes);
<?php
 
$data = array_map(function (Joke $item)
{
return array(
'id' => $item->getId(),
'content' => $item->getContent()
);
}, $jokes);
Code/Data
separation
They are different
http://www.flickr.com/photos/yukariryu/121153772/
A change in the data or
configuration should’t
require changing the code
public function load(ObjectManager $manager)
{
$jokes = array(
'There’s no place like 127.0.0.1',
'If at first you don’t succeed; call it version 1.0',
'Beware of programmers that carry screwdrivers',
'What color do you want that database?'
);
 
foreach ($jokes as $item) {
$joke = new Joke();
$joke->setContent($item);
 
$manager->persist($joke);
}
 
$manager->flush();
}
# fixtures/joke.yml
jokes:
- 'I would love to change the world, but they won’t...'
- 'There’s no place like 127.0.0.1'
- 'If at first you don’t succeed; call it version 1.0'
- 'You know it’s love when you memorize her IP...'
- 'Beware of programmers that carry screwdrivers'
- 'Best file compression around: “rm *.*” = 100...'
- 'The truth is out there…anybody got the URL?'
- 'What color do you want that database?'
public function load(ObjectManager $manager)
{
$jokes = Yaml::parse(__DIR__ . '/fixtures/joke.yml');
 
foreach ($jokes['jokes'] as $item) {
$joke = new Joke();
$joke->setContent($item);
 
$manager->persist($joke);
}
 
$manager->flush();
}
Environment coupling
http://www.flickr.com/photos/darkhornet/4945282009/
FAIL
{% if app.environment == 'prod' %}
<script type="text/javascript">
// Google Analytics code
</script>
{% endif %}
The software shouldn’t
take any decision based
on the environment
Better approach:
different configuration
for each environment
{% if enable_analytics %}
<script type="text/javascript">
// Google Analytics code
</script>
{% endif %}
Don’t Repeat
Yourself
// do not allow jokes about java)
if (stripos($content, 'java') !== false) {
throw new BadRequestHttpException(
'Java jokes are not allowed'
);
}
use SymfonyComponentValidatorConstraint;
 
/**
* @Annotation
*/
class ContainsJava extends Constraint
{
public $message = 'Java jokes are not allowed';
}
 
use SymfonyComponentValidatorConstraint;
use SymfonyComponentValidatorConstraintValidator;
 
class ContainsJavaValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (stripos($value, 'java') !== false) {
$this->context->addViolation($constraint->message);
}
}
}
Fat controllers
Go on a diet!
http://www.flickr.com/photos/golf_pictures/3017331832/
Configuration, events,
annotations, services, simpler
methods, inheritance, param
converters...
https://joind.in/8835
Questions?

Refactoring PHP/Symfony2 apps