Years ago my co-worker Maurits introduced me to the term “magic” in programming. He also provided the valuable dichotomy of convention and configuration (or in fact, he’d choose configuration over convention…). I think this distinction could be very helpful in psychological research, figuring out why some people prefer framework X over framework Y. One requires the developer to spell out everything they want in elaborate configuration files, the other relies on convention: placing certain files with certain names and certain methods in certain places will make everything work “magically”.
And there we are: magic. Often used in code reviews and discussions: “there’s too much magic here”. Yesterday the word popped up in a Twitter thread as well:
“symfony has too much magic, to its own detriment…” @bazinder
This was answered with:
“I’d say that everything is magic until you start to understand it :D” @iosifch
It made me wonder, what should we consider to be “magic” in programming? Is magic in code okay, or should it be avoided at all cost?
As an example of magic, the fact that you can define a controller like this, is already magical:
/** * @Route("/task") */
final class TaskController
{ /** * @Route("/new") */ public function new(Request $request): Response { // ... }
}
Who invokes it? Why, and when? You can’t figure that out by clicking “Find usages…” in PhpStorm!
This innocent example shows how quick we are to accept magic from a framework. Just do things “the framework way”, put this file there, add these annotations, and it’ll work. As an alternative, we could set up an HTTP kernel that doesn’t need any magic. For instance, we could write the dispatching logic ourselves:
$routes = [ '#^/task/new$#' => function (Request $request): Response { $controller = new TaskController(); return $controller->new($request); }, // ...
]; foreach ($routes as $path => $dispatch) { if (/* request URI matches path regex */) { $response = $dispatch($request); // Render $response to the client exit(); }
} // Show 404 page
Of course we wouldn’t or shouldn’t do this, but if we did, at least we’d be able to pinpoint the place where the controller is invoked, and we’d be able to inject the right dependencies as constructor arguments, or pass additional method arguments. Of course, a framework saves us from writing all these lines. It takes over the instantiation logic for the controller, and analyzes annotations to build up something similar to that $routes
array. It allows other services to do work before the controller is invoked, or to post-process the Response
object before it’s rendered to the client.
The more a framework is going to do before or after the controller is invoked, the more magical the framework will be. That’s because of all the dynamic programming that’s involved when you make things generic. E.g. you can add your own event subscribers that modify the Request
or Response
, or even by-pass the controller completely. It’s unclear if and when such an event subscriber will be invoked, because it happens in a dynamic way, by looping over a list of event subscriber services. If you have ever step-debugged your way from index.php
to the controller, you know that you’ll encounter a lot of abstract code, that is hard to relate to. It’s even hard to figure what exactly happens there.
I’m afraid there’s no way around magic. If you want to use a framework, then you import magic into your project. Circling back to Iosif’s comment (“everything is magic until you start to understand it”), I agree that the way to deal with your framework’s magic is to understand it, know how everything works under the hood. It doesn’t make the magic go away, but at least you know how the trick works. Personally I don’t think this justifies relying on all the magic a framework has to offer. I think developers should need as little information as possible to go ahead and change any piece of code. If they want to learn more about it,
- They should be able to “click” on method calls, to zoom in on what happens behind the call.
- They should be able to click on “Find usages” to zoom out and figure out how and where a method is used.
When you get to the magical part of your code base, usually the part that integrates with the framework or the ORM, then none of this is possible. You can’t click on anything,
Truncated by Planet PHP, read more at the original (another 3806 bytes)