Re: [PHP-DEV] [RFC] Namespace-scoped declares, again

  106572
August 13, 2019 20:26 rowan.collins@gmail.com (Rowan Collins)
On 13/08/2019 18:45, Mark Randall wrote:
> I thought about this as my first consideration, however it effectively > requires that the PHP code within the package class is fully parsed > and executed in order for it to retrieve the data. > > Consider pre-loading where the code is compiled, but not run, it would > not be possible to use namespace level defines to add compile-level > optimizations or checks (at least without some AST hackery).
Ah, that makes sense. Does that necessarily mean we need a dummy class, though? The autoloading logic in the engine knows that it called the autoload callback expecting a package definition, so can count as success that the package is now defined. In other words, the file the autoloader included would look like this (again, sticking to the notion that "package" is separate from "namespace"): # # File: /lib/company/project1.php #
  106576
August 13, 2019 22:28 markyr@gmail.com (Mark Randall)
On 13/08/2019 21:26, Rowan Collins wrote:
> Ah, that makes sense. Does that necessarily mean we need a dummy class, > though? The autoloading logic in the engine knows that it called the > autoload callback expecting a package definition, so can count as > success that the package is now defined. > > In other words, the file the autoloader included would look like this > (again, sticking to the notion that "package" is separate from > "namespace"):
Personally with file-level opt ins I'm not 100% sold on the benefits of packages over namespace level information. I guess I'm not certain on what problem they're trying to solve that's different from just another object at namespace level. As for the dummy class, the only real benefit is that existing autoloading procedures can be used to locate it and be included with minimal overhead. PHP can call the autoloader internally but it would require userland loaders to be upgraded with it, not exactly a huge stretch, especially with most things being composer-based, but a dummy class would make it work out the box, including optimization steps like dumping the class list. Mark Randall
  106578
August 14, 2019 06:45 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Rowan,

wt., 13 sie 2019 o 22:26 Rowan Collins collins@gmail.com> napisał(a):

> On 13/08/2019 18:45, Mark Randall wrote: > > I thought about this as my first consideration, however it effectively > > requires that the PHP code within the package class is fully parsed > > and executed in order for it to retrieve the data. > > > > Consider pre-loading where the code is compiled, but not run, it would > > not be possible to use namespace level defines to add compile-level > > optimizations or checks (at least without some AST hackery). > > > Ah, that makes sense. Does that necessarily mean we need a dummy class, > though? The autoloading logic in the engine knows that it called the > autoload callback expecting a package definition, so can count as > success that the package is now defined. > > In other words, the file the autoloader included would look like this > (again, sticking to the notion that "package" is separate from > "namespace"): > > > # > # File: /lib/company/project1.php > # > > > packagedef company/project1 { > strict_types=1; > strict_operators=1; > upgrade_errors_to_exceptions=E_ALL; > } >
Was thinking a while about proper syntax and would like to mention what I said before. It doesn't have to be a new syntax if we agree to put package definiction in sort of separate configuration file. In the end all what we need is package name and a bunch of declare directives. Given that it could be easily done using separate conf file like: # package.ini [package] name = MyVendor\MyLibrary strict_types = 1 encoding = UTF-8 # package.json { "name": "MyVendor\\MyLibrary", "declare": { "strict_types": 1, "encoding": "UTF-8" } } # package.toml [[package]] name = "MyVendor\\MyLibrary" strict_types = 1 encoding = "UTF-8" # package.yaml name: MyVendor\MyLibrary strict_types: 1 encoding: UTF-8 That way it would prevent to mix normal PHP code with package definition, cause every possible PHP syntax we would agree, opens the door to add something more to that file, for eg.: # package.php MyLibrary:FooBar\Baz; Then it would be possible to extract it and load package definition before class autoload would attempt to load source file and meet: https://wiki.php.net/rfc/namespace_scoped_declares#motivation_and_vision [2] https://www.php.net/manual/en/function.register-tick-function.php -- regards / pozdrawiam, -- Michał Brzuchalski about.me/brzuchal brzuchalski.com
  106582
August 14, 2019 09:01 rowan.collins@gmail.com (Rowan Collins)
On 14/08/2019 07:45, Michał Brzuchalski wrote:
> It doesn't have to be a new syntax if we agree to put package definiction > in sort of separate configuration file.
With the exception of INI files, all your examples are new syntax, as far as the internal mechanisms in PHP are concerned. They have the advantage of not inventing our own, but the disadvantage of needing our parser to handle all the ways people might write them (particularly true for YAML).
> That way it would prevent to mix normal PHP code with package definition, > cause every possible PHP syntax > we would agree, opens the door to add something more to that file, for eg.: > > # package.php > declare package MyVendor\MyLibrary { > strict_types = 1, > encoding = "UTF-8" > } > function foo() {}
I don't see this as a problem. Right now, PHP doesn't care how many files you put your code in. As far as I know, you could concatenate the entirety of Laravel into one PHP file, and applications would not be able to tell the difference. Similarly, you could put the whole thing in a database and use eval() to execute it without touching any files. In the same way, we can have a package definition not as a configuration file, but as a piece of PHP code that needs to have been executed. In practice, people wouldn't write what you have there for the same reason they don't write this: # foo.php 1,        'strict_operators' => 1     ]; } That way, we can probably reuse the whole parser infrastructure, and it looks entirely natural to a PHP programmer. [1]: https://blog.jetbrains.com/phpstorm/2019/02/new-phpstorm-meta-php-features/ -- Rowan Collins [IMSoP]
  106586
August 14, 2019 09:27 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
śr., 14 sie 2019 o 11:01 Rowan Collins collins@gmail.com> napisał(a):

> I don't see this as a problem. Right now, PHP doesn't care how many > files you put your code in. As far as I know, you could concatenate the > entirety of Laravel into one PHP file, and applications would not be > able to tell the difference. Similarly, you could put the whole thing in > a database and use eval() to execute it without touching any files. > > That is true but not if there are any declare statements. If so it could
IMO work only like that: > I think what attracts me to this idea is precisely that it doesn't > require much extra machinery. We could even use the trick that PHPStorm > uses for metadata stubs [1], and make the package definition look like > valid executable PHP, but never actually execute it: > > package Foo { > const declare = [ > 'strict_types' => 1, > 'strict_operators' => 1 > ]; > } >
How could that know when need look for package definition? Example: # src/Foo.php
  106587
August 14, 2019 09:49 rowan.collins@gmail.com (Rowan Collins)
On 14/08/2019 10:27, Michał Brzuchalski wrote:
> But that's conflicting with you above idea for package definition like > that: > package MyVendorMyLibrary { > declare(strict_types = 1); > }
That's not what I'm suggesting; perhaps my example could have been clearer. I envisage two new keywords: - To put some code in a package, you would write "package Foo;" at the top of a file, or "package Foo { }" around a block, in the same way you can do now for namespaces and declare() statements. - To define a package before it is used, you would write "packagedef Foo { }" around some block of pseudo-PHP that defined the options for that package. That might involve using declare() directly, or might just be an array that defines the options.
> You suggest that it would trigger autoload to load "MyVendor\MyPackage" > but current autoload machinery is able to load only classes, > not even functions or consts! cause it gets the only class name now. > It would need to be changed anyway.
As I said in another reply, this is only the same change that needed to be made to support "trait" alongside "class" and "interface", or would be needed to support "enum" or "struct". The userland part of the autoloader already doesn't know which of those it's looking for, so the only constraint is that the names can't collide, so you couldn't name a package the same thing as a class. There are other problems with autoloading functions and constants, but the relevant point is that you can define function foo() {} and class foo {} in the same namespace; as long as you couldn't also have a separate package foo{}, it could share the same autoloader as classes, interfaces, and traits. Regards, -- Rowan Collins [IMSoP]
  106589
August 14, 2019 10:07 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
śr., 14 sie 2019 o 11:49 Rowan Collins collins@gmail.com> napisał(a):

> > You suggest that it would trigger autoload to load "MyVendor\MyPackage" > > but current autoload machinery is able to load only classes, > > not even functions or consts! cause it gets the only class name now. > > It would need to be changed anyway. > > > As I said in another reply, this is only the same change that needed to > be made to support "trait" alongside "class" and "interface", or would > be needed to support "enum" or "struct". The userland part of the > autoloader already doesn't know which of those it's looking for, so the > only constraint is that the names can't collide, so you couldn't name a > package the same thing as a class. > > Exactly so how would it know from string name either it should load class
from src/Foo.php or src/__nsmeta.php if there is no information? -- regards / pozdrawiam, -- Michał Brzuchalski about.me/brzuchal brzuchalski.com
  106591
August 14, 2019 10:11 rowan.collins@gmail.com (Rowan Collins)
On 14/08/2019 11:07, Michał Brzuchalski wrote:
> Exactly so how would it know from string name either it should load class > from src/Foo.php or src/__nsmeta.php if there is no information?
It wouldn't. It would include src/Foo.php, and that would have the definition of something with the name "Foo" - either a class, an interface, a trait, or a package. If it wasn't what the engine was expecting, it would be an error, just as it is right now if you write "implements ClassName", or "new TraitName();" Regards, -- Rowan Collins [IMSoP]
  106592
August 14, 2019 10:17 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
śr., 14 sie 2019 o 12:11 Rowan Collins collins@gmail.com> napisał(a):

> On 14/08/2019 11:07, Michał Brzuchalski wrote: > > Exactly so how would it know from string name either it should load class > > from src/Foo.php or src/__nsmeta.php if there is no information? > > > It wouldn't. It would include src/Foo.php, and that would have the > definition of something with the name "Foo" - either a class, an > interface, a trait, or a package. If it wasn't what the engine was > expecting, it would be an error, just as it is right now if you write > "implements ClassName", or "new TraitName();" >
Following that would introduce unneeded additional directory hierarchy level in a usual library which uses PSR-4 which is the most used one, right? /composer.json /src/Foo.php /src/Foo/ <- all package classes should go here? -- regards / pozdrawiam, -- Michał Brzuchalski about.me/brzuchal brzuchalski.com
  106594
August 14, 2019 10:26 rowan.collins@gmail.com (Rowan Collins)
On 14/08/2019 11:17, Michał Brzuchalski wrote:
> Following that would introduce unneeded additional directory hierarchy > level in a usual library > which uses PSR-4 which is the most used one, right? > > /composer.json > /src/Foo.php > /src/Foo/ <- all package classes should go here?
That would be one place to put it, yes. But it would be entirely up to how people wanted to define their autoloader, that's the beauty of it. There's no reason something couldn't generate an autoloader that essentially said this: function autoload($name) {    if ( $name == self::PACKAGE_NAME ) {       require self::SRC_DIR . '/__packagedef.php';    } elseif ( str_begins_with($name, self::BASE_NAMESPACE) ) {       require self::SRC_DIR . str_replace('\\', '/', $name) . '.php';    } } Please don't pick holes in that implementation; my point is, if this was how packages were implemented, people would decide how they wanted to use it, and PSR-4 would probably be superseded by something which accounted for packages existing. Regards, -- Rowan Collins [IMSoP]
  106595
August 14, 2019 10:26 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
śr., 14 sie 2019 o 12:17 Michał Brzuchalski <michal@brzuchalski.com>
napisał(a):

> > > śr., 14 sie 2019 o 12:11 Rowan Collins collins@gmail.com> > napisał(a): > >> On 14/08/2019 11:07, Michał Brzuchalski wrote: >> > Exactly so how would it know from string name either it should load >> class >> > from src/Foo.php or src/__nsmeta.php if there is no information? >> >> >> It wouldn't. It would include src/Foo.php, and that would have the >> definition of something with the name "Foo" - either a class, an >> interface, a trait, or a package. If it wasn't what the engine was >> expecting, it would be an error, just as it is right now if you write >> "implements ClassName", or "new TraitName();" >> > > Following that would introduce unneeded additional directory hierarchy > level in a usual library > which uses PSR-4 which is the most used one, right? > > /composer.json > /src/Foo.php > /src/Foo/ <- all package classes should go here? >
Going even further by the example of Doctrine libraries: # doctrine/collections - Composer Package [1] uses PSR-4 autoload with "Doctrine\\Common\\Collections\\" prefix to adopt package concept it would have to change the prefix to "Doctrine\\Common\\" to find Collections.php and all the rest of source code in Collections directory # doctrine/common - Composer Package [2] uses "Doctrine\\Common\\" prefix should change into "Doctrine\\" to be able to load Common.php and the rest of source code in Common directory # ocramius/package-versions - Composer Package [3] uses "PackageVersions\\" prefix and then what? [1] https://github.com/doctrine/collections/blob/master/composer.json [2] https://github.com/doctrine/common/blob/master/composer.json [3] https://github.com/Ocramius/PackageVersions/blob/master/composer.json -- regards / pozdrawiam, -- Michał Brzuchalski about.me/brzuchal brzuchalski.com