On 8/3/15 4:34 AM, David Négrier wrote:
> Hey Larry,
> Sorry, I previously did not take time to answer your proposal about a
> JSON string to define services, but I see two major problems that make
> your proposal quite unlikely to reach a consensus: the /format/ and
> the /features/.
> Regarding the format:
> You chose JSON. I'm confident enough that most people will not find it
> a good idea. If you look at the problem *from the point-of-view of
> containers that have a compiler* (SF, Nette, ZF), JSON has many drawbacks:
> - it does not feature comments (unlike XML or YML)
> - it does not have a file descriptor (unlike XSD for XML)
> - it can be more verbose than other alternatives (YML for instance)
I chose JSON as an example because it's simple, universally supported in
PHP, and very fast to parse (because it's built-in to PHP). I am not
wedded to it, however. My point was simply that we should focus on a
declaration file, rather than executable code with interfaces. Believe
me, I am *very* acutely aware of the bikeshed potential of picking a
config file format. :-)
> Some framework have come to great length to invent a new file format
> that is specially tailored for DI (I think Nette has)... so just
> saying "let's go with JSON" will certainly be heavily challenged...
> but wait, I've only been talking about containers that have a compiler.
> If you look at your proposal *from the perspective of containers that
> do not have a compiler*, the sole fact of reading a configuration file
> is a complete show-stopper.
> You said using a JSON file is "marginally" slower and I beg to
> disagree. Just imagine an application with 50 packages, each coming
> with its own JSON configuration file. For runtime containers, it means
> 50 calls to "file_get_content" and 50 calls to "json_decode" on EACH
> HTTP request. This will simply be way too slow.
Runtime containers have a setup cost for every service, period. If data
is pulled from somewhere, it needs to get mapped into the container.
That mapping process has cost. Without building them all and
benchmarking it I cannot say with certainty what the relative costs are,
but my gut feeling is that mapping everything through interface calls is
going to be slower than you think, ie, not as much faster than parsing a
file (especially if you're on a modern kernel that properly caches disk
IO). Without comparable implementations though we're both guessing.
> From the perspective of runtime containers, there are only 2 options:
> using PHP arrays to store the configuration, or compiling JSON files
> into a PHP container.
> Solution 1 does work. Rather than using a JSON file, you put your
> configuration in a PHP file that returns an array.
> For instance, using your sample, we could convert your JSON into this
> PHP file:
> return [
> 'services' => [
> 'dwarf_factory' => [
> 'class' => 'Disney\DwarfFactory'
> 'dwarf_manager' => [
> 'class' => 'Disney\DwarfManager',
> 'arguments' => ['@dwarf_factory', 4]
> PHP files are cached by the OPCache and therefore are loaded
> instantly. Mouf has been using this very technique for the past 6
> years and I can tell you performance is ok. The problem, of course, is
> that the frameworks that have a compiler will be less than thrilled to
> see a PHP configuration file. It is harder to analyze than YML or JSON
> as you cannot see what it contains without actually executing the PHP
> in it (no static analysis)... so it features a number of issues.
Is that really harder, though? You can tokenize the file if you want to
avoid loading it into memory:
But if you're going to be loading it into your container anyway, you'll
want to load/parse it and then manipulate the resulting array.
> The other solution is to compile JSON files into a PHP container...
> but remember, I'm specifically speaking about containers that have no
> compiler (which by the way, are a majority out there). Do you think
> Pimple would implement a compiler? (it would simply explode its
> 80-lines of code). Hopefully, there is another way around this.
> Someone could write an external library that would compile all
> configurations files into a PHP container. Then every framework could
> hook that container to their own container... which is exactly what
> PSR-11 is about (this is the delegate dependency lookup pattern!) So
> even if you managed to have a unique JSON format agreed upon, you
> would still need PSR-11's ContainerInterface as a prerequisite.
I do not follow this logic.
Uncompiled containers have an upper bound of usefulness, because
otherwise the time spent on each request configuring the container with
services goes up with each service registered. That's why compiled
containers exist, to eliminate that overhead. They add a fair bit of
complexity for that, sure; that's the trade-off between the two (yes,
entirely legitimate) ways of configuring a container.
> Regarding the features:
> Your JSON file is very minimalistic in the features it provides. I
> understand this is "by design", but this design is very opiniated. You
> assume that the only way on object should be created is by calling its
> constructor and passing parameters. But there are plenty of other ways
> to create an object.
It was an off-the-cuff minimalist example to demonstrate the feasibility
of a config-file approach. It was not intended as a be-all-end-all
final implementation and I never represented it as such. Adding a
factory key or whatever is totally fine.
Although remember, as I understand it we're talking about a tool to make
it easy for packages to expose themselves to frameworks and
applications. In practice, I suspect the vast majority won't need most
complex features and functionality. If a specific framework needs it
for some reason (lazy loading is a very good example), well, fine, do
whatever your framework does for lazy loading via an alter, compiler
pass, extra 2 lines of setup code, or what have you. We don't need to
solve the 99% case, only the 80% case.
> With your JSON file, you are forcing anyone to use the *lowest common
> denominator* of DI. Using an interface, we can have instead the
> greatest common divisor.
> Using one of the interfaces Bernhard or I are proposing, it is fairly
> easy to implement your JSON format. But rather than forcing anyone
> into the same format (which is a bit of a dictatorship approach),
Dictatorship? That's hyperbolic. Any standard, by definition, implies
"yes, you DO have to do it this way to get these benefits". By that
logic, everything FIG does is dictatorial, which is nonsense.
> the competition stays open between frameworks. Your JSON format
> becomes one possible format amongst many possible implementations. We
> don't close the game, we open it to competition and innovation. So I
> definitely believe the interface is the way to go here.
I'm not sure we're even having the same discussion at this point... The
goal would be to provide one(1) way for libraries to expose their
services to a framework, to eliminate the bridge-module-per-library
problem. That way, only one adapter is needed per container
(cardinality c) rather than one per container and library (cardinality
c* l). In that case, "lots of competition" is counter-productive;
that's the problem we have today, that we're trying to solve!