In my previous post, I wrote about wrapping a legacy application in Silex, using output buffering and Twig. Finally, to allow for better decoupling as well as lazy loading of services, we passed the actual Silex\Application
instance as the first argument of legacy controllers.
The first and quite easy way we can enhance our legacy application, is to make use of the request
service (which contains all the details about the current request, wrapped inside the Symfony HttpFoundation's Request
class). So, instead of reading directly from $_GET
and $_POST
, we can change the edit_category()
controller into the following:
use Silex\Application;
function edit_category(Application $app)
{
$warning = '';
$values = array();
$request = $app['request'];
/* @var $request \Symfony\Component\HttpFoundation\Request */
if ($request->isMethod('POST')) {
$values = $request->request->all();
if ($values['name'] == '') {
$warning .= 'Please provide a name';
}
if ($warning == '') {
if (isset($values['id'])) {
$sql = "UPDATE categories SET name='{$values['name']}' WHERE id='{$values['id']}';";
}
else {
$sql = "INSERT INTO categories SET name='{$values['name']}';";
}
$result = mysql_query($sql) or die(mysql_error());
return $app->redirect('/');
}
}
else if ($request->query->has('id')) {
$result = mysql_query("SELECT * FROM categories WHERE id={$request->query->getInt('id')};");
if ($result && mysql_num_rows($result)) {
$values = mysql_fetch_assoc($result);
}
}
// display the form...
}
Now we should get rid of this very much outdated way of storing things in a MySQL database. Doctrine2 would be my ORM of choice, but since this legacy application has no object-oriented model layer yet, migrating to an ORM would require quite a lot of work. The entire application works with arrays of data, not with objects (since I have used mysql_fetch_assoc()
everywhere).
Though we won't use an ORM, we can still take advantage of the database abstraction upon which the Doctrine ORM itself depends. The abstraction layer is called Doctrine DBAL. It is in fact a (not so very fat) layer around PDO and PDOStatement (PHP's built-in database abstraction classes).
First, add a requirement for Doctrine DBAL to composer.json
and run composer.phar update
:
{
"require": {
"silex/silex": "1.*",
"symfony/twig-bridge": "2.1.*",
"doctrine/dbal": "2.2.*"
}
}
The Doctrine DBAL Connection
class (which is a wrapper for the native PDO
class) provides some shortcuts for inserting, updating and deleting rows.
Since we have chosen Silex as the micro-framework for this legacy application, we won't have to bother with initializing the database connections ourselves. Silex is shipped with a DoctrineServiceProvider
, which adds a service called db
to the application. This service is the database connection itself.
$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
'db.options' => array(
'driver' => 'pdo_mysql',
'host' => 'localhost',
'dbname' => 'legacy',
'username' => 'root',
'password' => 'net29783',
),
));
Doctrine DBAL allows you to use prepared statements (and you should, your application will be much more secure), and also gives you some great convenience methods for manipulating data stored in your database.
For example, inside the controller (since we pass the Silex\Application
instance to it) we can do things like:
// insert
$app['db']->insert('categories', array(
'name' => $app['request']->request->get('name'),
));
// update
$app['db']->update('categories', array(
'name' => $app['request']->request->get('name'),
), array(
'id' => $app['request']->request->get('id'),
));
// select
$category = $app['db']->fetchAssoc('SELECT * FROM categories WHERE id = :id', array(
'id' => $app['request']->query->get('id'),
));
// delete
$app['db']->delete('categories', array(
'id' => $app['request']->query->get('id'),
));
So, the edit_category()
controller may now look like this:
use Silex\Application;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
function edit_category(Application $app)
{
$warning = '';
$values = array();
$request = $app['request'];
if ($request->isMethod('POST')) {
$values = $request->request->all();
if ($values['name'] == '') {
$warning .= 'Please provide a name';
}
if ($warning == '') {
if (isset($values['id'])) {
$app['db']->update('categories', array(
'name' => $values['name'],
), array(
'id' => $values['id'],
));
}
else {
$app['db']->insert('categoryes', array(
'name' => $values['name'],
));
}
return $app->redirect('/');
}
}
else if ($request->query->has('id')) {
$values = $app['db']->fetchAssoc("SELECT * FROM categories WHERE id = :id", array(
'id' => $request->query->getInt('id'),
));
if (false === $values) {
throw new NotFoundHttpException('Category not found');
}
}
if ($warning != '') {
?><p class="warning"><?php echo $warning; ?></p><?php
}
?>
<form action="/edit_category" method="post">
<?php if (isset($values['id'])) { ?>
<input type="hidden" name="id" value="<?php echo $values['id']; ?>" />
<?php } ?>
<p>
<label for="name">Name:</label>
<input type="text" name="name" id="name" value="<?php echo (isset($values['name']) ? $values['name'] : ''); ?>" />
</p>
<p>
<input type="submit" name="save" value="Save" />
</p>
</form>
<?php
}
Which is much better actually!
In my next post I will write about convenient ways to make your legacy application multi-lingual.
Did you had any time to write the suite of this blog post (going multilingual) or do you have a repo example ?
Would be curious to see that. Very good serie by the way ! :)
Thanks! Unfortunately there is no public repository for these posts.
what is the bootstrap of this app looks like? I mean how you create or ready the $app injected? it looks very nice btw, i learned how i can replace legacy code with a silex app.
really neat man, thanks!