-
-
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.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
IllegalAccessInterface
corresponds toIllegalAccessTrait
). The code-generator would then incorporate both interfaces and traits.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), buteval()
is also a lightning rod. I think someeval()
critiques are more valid than others here, but ultimately a production implementation would probably get rid ofeval()
.eval()
undermines structured programmingeval()
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.eval()
is slow.eval()
is a potential failure point when the generated code is broken -- who wants more failure points in error-handling code?The
eval()
issue is resolvable if you're willing to write a pre-processor. The pre-processor scans the source-tree forTaggedException::create(...)
and spits out a big .php file with the auto-generated classes. Call the pre-processor as part ofcomposer install
,grunt watch
, or whatever.