Skip to content

Instantly share code, notes, and snippets.

@totten
Created July 4, 2015 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save totten/65f254e4b81cc02a24d8 to your computer and use it in GitHub Desktop.
Save totten/65f254e4b81cc02a24d8 to your computer and use it in GitHub Desktop.
Proof of Concept: Dynamically tagged exceptions. (Tested in PHP 5.3, 5.4, 5.5, HHVM. Except for the syntactic sugar circa line 48, it works everywhere.)
<?php
// Core library.
namespace Psr\Exceptions {
interface IllegalAccessInterface {}
interface EntityNotFoundInterface {}
interface DatabaseConnectionFailedInterface {}
// ad nauseum...
class TaggedException extends \Exception {
/**
* @param array<string> $interfaces
* List of PHP interfaces to implement
* @return TaggedException
*/
public static function create($interfaces) {
static $cache; // array(string $sig => string $className)
sort($interfaces); // order-insensitive
$sig = implode(', ', $interfaces);
if (isset($cache[$sig])) {
$className = $cache[$sig];
}
else {
$className = $cache[$sig] = 'TaggedException' . md5($sig);
eval("class $className extends \Psr\Exceptions\TaggedException implements $sig {}");
}
return new $className();
}
// TODO: Override getMessage(), __toString(), etc to provide nicer errors.
}
}
// Example use-case
namespace MyPackage {
use Psr\Exceptions\TaggedException;
use Psr\Exceptions\IllegalAccessInterface;
interface MyHeadExplodedInterface {}
function ornery() {
throw TaggedException::create(array(
IllegalAccessInterface::class, // PHP 5.5+; otherwise, use string
MyHeadExplodedInterface::class, // PHP 5.5+; otherwise, use string
));
}
function conscientious() {
try {
ornery();
}
catch (IllegalAccessInterface $f) {
echo "The IllegalAccessInterface is the most important thing!\n";
}
catch (MyHeadExplodedInterface $b) {
echo "The MyHeadExplodedInterface is the second most important thing!\n";
}
}
conscientious();
}
@totten
Copy link
Author

totten commented Jul 6, 2015

My primary complaint with the POC is that there's no room for adding properties or methods to interfaces -- it's strictly tagging. One could resolve this if one is willing to impose some constraints on how interfaces are written, e.g.

  • Each interface must have a matching trait (e.g. IllegalAccessInterface corresponds to IllegalAccessTrait). The code-generator would then incorporate both interfaces and traits.
  • Each trait must have a partial/pseudo-constructor (function initialize($args)). The code-generator would then delegate construction to each of these.

A smaller complaint about the POC is the use of eval(). This is nice because it makes the POC simple and can be dropped-in anywhere (without any change in the build process), but eval() is also a lightning rod. I think some eval() critiques are more valid than others here, but ultimately a production implementation would probably get rid of eval().

  • Critique: eval() undermines structured programming
    • Disagree. This usage of eval() is centralized and encapsulated; the overall effect (in the example code) is to work with more interfaces and less concrete throw-away code. The code pattern in the example is still amenable to static analysis. Seems like a net-boost to structured programming.
  • Critique: eval() is slow.
    • Disagree. Exceptions are generally regarded as slow anyway. Exceptions are supposed to be exceptional -- meaning they don't happen often. If they don't happen often, then it doesn't matter if they're a bit slow.
  • Critique: eval() is a potential failure point when the generated code is broken -- who wants more failure points in error-handling code?
    • Hmm, yeah. I mean, on a gut level it feels like it would testable and would stabilize within a couple months, but yes.
  • Critique: Some sysadmins block eval() as a matter of course.
    • Hmm, yeah.

The eval() issue is resolvable if you're willing to write a pre-processor. The pre-processor scans the source-tree for TaggedException::create(...) and spits out a big .php file with the auto-generated classes. Call the pre-processor as part of composer install, grunt watch, or whatever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment