Context Managers in PHP

These days, I spend far more time writing and reviewing code in Python than I do PHP. It's been a refreshing change of pace, and it's fun to learn the different patterns present in various programming languages. One of the nicer patterns in Python is the Context Manager. It's a pattern that PHP can learn from and leverage.

These days, I spend far more time writing and reviewing code in Python than I do PHP. It’s been a refreshing change of pace, and it’s fun to learn the different patterns present in various programming languages.

If you haven’t taken the time to look around to see what other languages or frameworks are doing, I’d highly recommend it.

One of the nicer patterns in Python is the Context Manager. In Python, certain objects and functions can be wrapped in a with block used to provide specific context to the code running within it. Opening files, for example, becomes remarkably easy:

with open('output.txt', 'w') as f:
f.write('Hello world!')

The beauty of this pattern is the automated resource cleanup. You don’t have to manage (and free) file pointers manually. You don’t have to initialize and disconnect a database session. The context manager handles things for you automatically.

A database context manager could manage creating and closing a connection to the database itself (or the connection pool it uses). Or it could be used to wrap a transaction and automatically commit the results at the end of the block. Either way, you don’t have to manage things on your own.

PHP doesn’t have a pattern like this natively. I think adding a with construct would be a huge value add for the language, but my lower-level coding skills are a bit too rusty to code up a quick proof of concept. Instead, I can abuse generators and loops to simulate similar functionality with an existing system.

The Python example above used the open() function to demonstrate context management as applied to files. We can do something similar in PHP with the following helper function:

function open($file, $mode = 'r')
{
$f = fopen($file, $mode);
yield $f;

fclose($f);
}

Since this function is a generator, we can use it natively inside a foreach loop. The thing is, we only ever loop once, so foreach in this example is really a parallel to Python’s with.

foreach(open('output.txt', 'w') as $file) {
fwrite($file, 'Hello, world!');
}

Since the generator only yields once, we only get a single item to iterate across in our foreach loop. The loop will also automatically return back to the generator after it iterates, which gives us the opportunity to clean up our context and scope.

It’s a bit more verbose than the Python, and we have to build our own context managers, but it’s a much cleaner way to ensure we’re cleaning up after ourselves.

Taking things a step further, assume we need to work with a PDO connection. We want to connect to the database, do some work within a transaction, and automatically commit the result. Doing so imperatively would look something like:

try {
$dbh = new PDO($dsn, $user, $pass, $options);
} catch (Exception $e) {
die("Unable to connect: " . $e->getMessage());
}

try {
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->beginTransaction();
$dbh->exec("insert into users (id, first, last) values (5, 'Eric', 'Mann')");
$dbh->exec('insert into authors (id) values (5)');
$dbh->commit();
} catch (Exception $e) {
$dbh->rollBack();
echo "Failed: " . $e->getMessage();
}

The same operation, leveraging a context manager pattern, would be far simpler:

function transaction()
{
$dbh = new PDO($dsn, $user, $pass, $options);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->beginTransaction();

yield $dbh;

try {
$dbh->commit();
} catch (Exception $e) {
$dbh->rollBack();
}
}

// Now use the transaction
foreach(transaction() as $dbh) {
$dbh->exec("insert into users (id, first, last) values (5, 'Eric', 'Mann')");
$dbh->exec('insert into authors (id) values (5)');
}

This is more complex than the first example. The gist is that the setup and teardown code for the database connection and the transaction happens outside of your business logic. The transaction() function here could be in a separate file or namespace from the rest of your code, which keeps the execution logic clean and removes the need to worry about setup/teardown at all.

Files, databases, remote connections to resources, locks, threads. These are all intensive operations that could benefit from the use of this pattern – and which do leverage this pattern in Python and other languages.

Is this a pattern that would make a difference in your code? It’s certainly different than the way many of us write PHP today. How else might you leverage this pattern? Would it make more sense if we extended the language with our own implementation of with?