[RFC] Import of variables

php.internals

Manuel Canga

6 years ago
Hi Internals, I'd like to open up a discussion around the implementation of a new functionality: 'import of variables'. This functionality would allow to create a new 'use vars' keyword in order to can use( or cannot use ) global variables in local scope( of current file ). I think the best is a example: ```php <?php $a = 1; $b = 2; $c = 3; include __DIR__.'/without_import.php'; include __DIR__.'/all_import.php'; include __DIR__.'/none_import.php'; include __DIR__.'/some_vars.php'; include __DIR__.'/global_in_function.php'; ``` ## without_import.php ```php <?php echo $a; //1 echo $b; //2 $c = 'any value'; //replace value in global var ``` ## all import.php ```php <?php use vars all; echo $a; //1 echo $b; //2 $c = 'other value'; //replace value in global var ``` ## none_import.php ```php <?php use vars none; echo $a; //Warning: undefined var $a echo $b; //Warning: undefined var $b $c = 'other value'; //assign value to local var ``` ## some_vars.php ```php <?php use vars $a, $c, $d; //Warning: undefined var $d echo $a; //1 echo $b; //Warning: undefined var $b $c = 'a value'; //replace value in global var ``` ## global_in_function.php ```php <?php use vars $a, $c; function hello() { global $a, $b, $c; echo $a; //1 echo $b; //null. $c = 'end value'; //replace value in global var } hello(); ``` In a project with a lot of global vars( like WordPress ) this functionality avoids conflicts and easy replacements of values in main global vars. Regards
-- Manuel Canga

Rowan Collins

6 years ago
On 09/08/2020 10:01, Manuel Canga wrote:
> Hi Internals, > > I'd like to open up a discussion around the implementation of a new > functionality: 'import of variables'. > > This functionality would allow to create a new 'use vars' keyword in order to can use( or cannot use ) global variables in local scope( of current file ).
Right now, PHP has no notion of "file scope", and making that the _default_ would be a huge compatibility break. The "use vars none" variant that opts _out_ of the global scope might be possible, but at that point, you can just use the common JS trick of wrapping your code in a function and immediately running it: <?php (function() { global $a, $c, $d; echo $a; //1 echo $b; //Warning: undefined var $b $c = 'a value'; //replace value in global var })(); The last example, where the function uses "global" but only gets the globals if they're also "used" in the current file, seems like it would make more sense using a class, a static method, and some private static variables, since right now we don't think of a function as "belonging to" a file in the same way it would belong to a class. Regards,
-- Rowan Tommins (né Collins) [IMSoP]

tyson andre

6 years ago
Hi Internals, > This functionality would allow to create a new  'use vars' keyword in order to can use( or cannot use )  global variables in local scope( of current file ). To be clear: The variables in the top-level scope depend on what has require()d/include()d a file. The top-level scope starts off as being global, but if a file is required from within a function/method/closure (e.g. the autoloader closure), then the top-level scope in the require()'d file uses variables (e.g. $this) from whatever context called require(). It may be possible to use a declare syntax, e.g. declare(used_variables='all') for `'all'`, `null`, `['var1', 'var2']`, etc. - Otherwise, you face the issue of where `use vars` should be allowed, what happens if there's a statement before `use vars`, etc. I can see this as having some use cases, such as in configuration files or files used for bootstrapping. For example, ``` <?php declare(used_variables=null); $api_base = IS_PRODUCTION ? 'https://example.com/api/' : 'http://localhost/api'; do_stuff(); return [ // long config array 'url_new' => "$api_base/new", 'url_all' => "$api_base/all", ]; ``` This feature (ignoring the question of syntax) would ensure that people reading the file knew that $api_base was not modified by other files and that other files did not read local variables created within a configuration/bootstrapping file in unexpected ways, which is a fairly common issue in some web apps I've worked on. Opcache would also do a better job at optimizing code if it knew which variables in a top-level scope couldn't be modified. That being said, there's been opposition to extensions to the language that add functionality that can be implemented in other ways, as in Rowan's comment, but peoples opinions depend on the specifics of the proposal (e.g. `match` was added and was more performant than chained conditionals or switch). As Rowan said, there are ways to reimplement this: - Wrapping the config file or bootstrapping file in a closure, global function, or class method - `function safe_require_once(string $path, $vars = []) { extract($vars); require($path); }` from the caller, to limit what variables are passed in. IDEs/tooling would be worse at telling you if a file name had a typo, though. Regards, - Tyson

David Rodrigues

6 years ago
I suggests to make something like Node does: import "file.php" ($a, $b) so $a and $b must be extracted from file.php context. All other variables should be ignored. Or, to avoid create more complexity and possible new keywords to import syntax, maybe create a new function: importx(string $file, ?array $extract = null) and requirex(...) (importx and requirex is only pseudo) Em dom, 9 de ago de 2020 11:42, tyson andre <tysonandre775@hotmail.com> escreveu:

Josh Bruce

6 years ago
Have not tested - just catching up on emails. To verify: A property defined with a default value of the global does not already do this? Developer need: I rarely access global variables directly - usually going through a lib or framework. When I do I would probably wrap the access in a trait or method on a class. Cheers, Josh

Manuel Canga

6 years ago
Hi, Josh. Thanks ---- En dom, 09 ago 2020 21:36:30 +0200 Josh Bruce <josh@joshbruce.dev> escribió ----
> Have not tested - just catching up on emails. > > To verify: A property defined with a default value of the global does not already do this?
In functions/methods. In some files can "inherit" gobal vars directly or simply "inherit" local vars of other function.
> Developer need: I rarely access global variables directly - usually going through a lib or framework. When I do I would probably wrap the access in a trait or method on a class.
Some frameworks or CMS use view files in PHP like templates. https://platesphp.com/v3/templates/syntax/#syntax-example In WordPress case: "Developers are advised to consider this a Reserved Name List, and not to create local variables with the same names in Plugins or Themes. Under some circumstances, the global variable value will be replaced by the local variable value, causing errors in the WordPress Core that are difficult to diagnose. " https://codex.wordpress.org/Global_Variables This could be avoid easily with "use vars none" on the start of each file of theme/plugin. Regards

Manuel Canga

6 years ago
Hi, David. Thanks ---- En dom, 09 ago 2020 16:58:41 +0200 David Rodrigues <david.proweb@gmail.com> escribió ----
> I suggests to make something like Node does: import "file.php" ($a, $b) so $a and $b must be extracted from file.php context. All other variables should be ignored. > Or, to avoid create more complexity and possible new keywords to import syntax, maybe create a new function: > importx(string $file, ?array $extract = null) and requirex(...) (importx and requirex is only pseudo)
That is a good idea!. However, in some framworks/CMS you don't have control about include/require. :( Regards

Manuel Canga

6 years ago
Hi, Tyson, Thanks. ---- En dom, 09 ago 2020 16:41:44 +0200 tyson andre <tysonandre775@hotmail.com> escribió ----
> Hi Internals, > > To be clear: The variables in the top-level scope depend on what has require()d/include()d a file. > The top-level scope starts off as being global, but if a file is required from within a function/method/closure (e.g. the autoloader closure), then the top-level scope in the require()'d file uses variables (e.g. $this) from whatever context called require().
My idea was using 'use vars' not only with global vars but also with vars in context.
> It may be possible to use a declare syntax, e.g. declare(used_variables='all') for `'all'`, `null`, `['var1', 'var2']`, etc. > - Otherwise, you face the issue of where `use vars` should be allowed, what happens if there's a statement before `use vars`, etc.
Good point, I don't think about that.
> I can see this as having some use cases, such as in configuration files or files used for bootstrapping. > For example, > > ``` > <?php > declare(used_variables=null); > > $api_base = IS_PRODUCTION ? 'https://example.com/api/' : 'http://localhost/api'; > do_stuff(); > > return [ > // long config array > 'url_new' => "$api_base/new", > 'url_all' => "$api_base/all", > ]; > ``` > > This feature (ignoring the question of syntax) would ensure that people reading the file knew that $api_base was not modified by other files > and that other files did not read local variables created within a configuration/bootstrapping file in unexpected ways, > which is a fairly common issue in some web apps I've worked on. > Opcache would also do a better job at optimizing code if it knew which variables in a top-level scope couldn't be modified. > > That being said, there's been opposition to extensions to the language that add functionality that can be implemented in other ways, as in Rowan's comment, > but peoples opinions depend on the specifics of the proposal > (e.g. `match` was added and was more performant than chained conditionals or switch). > > As Rowan said, there are ways to reimplement this: > - Wrapping the config file or bootstrapping file in a closure, global function, or class method > - `function safe_require_once(string $path, $vars = []) { extract($vars); require($path); }` from the caller, to limit what variables are passed in. IDEs/tooling would be worse at telling you if a file name had a typo, though.
I think in views system( of some frameworks ) or CMS like WordPress. In WordPress, for example, I can do something like this: ## index.php ``` foreach($posts as $post ) { $post = 'foo'; } ``` index.php is loaded from CMS. $post is a global variable used in WordPress core. So that, I am crashing WordPress. However, with: ``` use vars none; foreach($posts as $post ) { $post = 'foo'; } ``` I don't crash my WordPress or any external plugin which can use global vars. My file now is a container( black box ) where my code is isolated. Regards

Marco Pivetta

6 years ago
Hey Manuel, On Sun, Aug 9, 2020, 11:02 Manuel Canga <php@manuelcanga.dev> wrote:
> Hi Internals, > > I'd like to open up a discussion around the implementation of a new > functionality: 'import of variables'. > > This functionality would allow to create a new 'use vars' keyword in > order to can use( or cannot use ) global variables in local scope( of > current file ). > > I think the best is a example: > > ```php > <?php > > $a = 1; > $b = 2; > $c = 3; > > include __DIR__.'/without_import.php'; > include __DIR__.'/all_import.php'; > include __DIR__.'/none_import.php'; > include __DIR__.'/some_vars.php'; > include __DIR__.'/global_in_function.php'; > ``` > > ## without_import.php > > ```php > <?php > > echo $a; //1 > echo $b; //2 > $c = 'any value'; //replace value in global var > ``` > > ## all import.php > > ```php > <?php > > use vars all; > > echo $a; //1 > echo $b; //2 > $c = 'other value'; //replace value in global var > ``` > > ## none_import.php > > ```php > <?php > > use vars none; > > echo $a; //Warning: undefined var $a > echo $b; //Warning: undefined var $b > $c = 'other value'; //assign value to local var > ``` > > > ## some_vars.php > > ```php > <?php > > use vars $a, $c, $d; //Warning: undefined var $d > > echo $a; //1 > echo $b; //Warning: undefined var $b > $c = 'a value'; //replace value in global var > ``` > > ## global_in_function.php > > ```php > <?php > > use vars $a, $c; > > > function hello() { > global $a, $b, $c; > > echo $a; //1 > echo $b; //null. > $c = 'end value'; //replace value in global var > } > > hello(); > ``` > > > In a project with a lot of global vars( like WordPress ) this > functionality avoids conflicts and easy replacements of values in main > global vars. >
This looks like what IIFE do already (you can use them today): ```php require 'something/with/side/effects.php'; (static function () use ($a, $b, & $c) : void { echo $a; //1 echo $b; //Warning: undefined var $b $c = 'a value'; //replace value in global var })(); ``` You can use this right now, and it has the advantage of reporting any missing symbols right at the time at which the closure is declared. I tend to use IIFE all the time in procedural code, since they prevent leaking state into globals 👍

Manuel Canga

6 years ago
Hi, Internals, Thanks, Marco and also to Rowans, Josh, David and Tyson I'll use IIFE in order to avoid global conflicts. Regards ---- En lun, 10 ago 2020 02:16:06 +0200 Marco Pivetta <ocramius@gmail.com> escribió ----