rfc:namespace_scoped_declares

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
rfc:namespace_scoped_declares [2016/09/20 20:33] – created nikicrfc:namespace_scoped_declares [2022/01/25 18:22] (current) – Move to inactive ilutov
Line 3: Line 3:
   * Date: 2016-09-20   * Date: 2016-09-20
   * Author: Nikita Popov <nikic@php.net>   * Author: Nikita Popov <nikic@php.net>
-  * Proposed PHP version: PHP 7.2 +  * Proposed PHP version: PHP 7.3 
-  * Status: Draft+  * Status: Inactive 
 +  * PR: https://github.com/php/php-src/pull/2972 
 +  * ML thread: http://externals.io/thread/326
  
 ===== Introduction ===== ===== Introduction =====
Line 15: Line 17:
 // bootstrap.php // bootstrap.php
 namespace_declare('Vendor\Lib', ['strict_types' => 1]); namespace_declare('Vendor\Lib', ['strict_types' => 1]);
-namespace_declare('Vendor\Lib\Sub\Name\Space', ['strict_types' => 0]); 
 </code> </code>
  
-The first call would make ''strict_types=1'' the default in the namespace ''Vendor\Lib'' and all sub-namespaces (i.e. all namespaces starting with ''Vendor\Lib\''). The second call would create an exception for this and make ''strict_types=0'' the default in ''Vendor\Lib\Sub\Name\Space'' (and sub-namespaces). A specific file in this namespace could then again override this default using a specific ''declare(strict_types=1);'' directive.+This would make ''strict_types=1'' the default in the namespace ''Vendor\Lib'' and all sub-namespaces (i.e. all namespaces starting with ''Vendor\Lib\''). A specific file in this namespace could of course override this default using a specific ''declare(strict_types=0);'' directive.
  
 ===== Motivation and Vision ===== ===== Motivation and Vision =====
Line 24: Line 25:
 The motivation behind this proposal is broader than just saving a single ''declare()'' in each file of a project. This kind of namespace-scoped declare may provide the means for introducing certain changes that make the language "stricter" without breaking backwards compatibility or library-interoperability (as ini directives do). The motivation behind this proposal is broader than just saving a single ''declare()'' in each file of a project. This kind of namespace-scoped declare may provide the means for introducing certain changes that make the language "stricter" without breaking backwards compatibility or library-interoperability (as ini directives do).
  
-For example, in modern PHP code it is nearly always a bug to dynamically create a property on an object that has not previously been declared. However disallowing this globally would be completely unthinkable, because a lot of software relies on it, and some use-cases legitimately benefit from the ability to do this. Adding an ini-setting for this is not possible either, because this would prevent using libraries that assume different values for this setting (which is the same reason why strict_types is not an ini setting).+For example, in modern PHP code it is nearly always a bug to dynamically create a property on an object that has not previously been declared. However disallowing this globally would be unthinkable, because a lot of software relies on it, and some use-cases legitimately benefit from the ability to do this. Adding an ini-setting to control this behavior is not possible either, because this would prevent using libraries that assume different values for this setting (which is the same reason for why strict_types is not an ini setting).
  
-Using a declare() directive for this purpose would solve the inter-operability issues and would additionally provide an "escape-hatch" for the cases the functionality is legitimately needed. However if the number of such directives increases, specifying them for every single file in a project would quickly become unwieldy. Additionally, it is likely that a library will want to use the same directives for all files. Changing the value of a directive would then require performing an update on all files. Namespace-scoped declares solve this by specifying such options in a single bootstrap file:+Using a declare() directive for this purpose would solve the inter-operability issues and would additionally provide an "escape-hatch" for the cases where the functionality is legitimately needed. However if the number of such directives increases, specifying them for every single file in a project would quickly become unwieldy. Additionally, it is likely that a library will want to use the same directives for all files. Changing the value of a directive would then require performing an update on all files. Namespace-scoped declares solve this by specifying such options in a single bootstrap file:
  
 <code php> <code php>
Line 37: Line 38:
 </code> </code>
  
-For libraries using Composer this bootstrap file would be specified as an autoloader of type "file". However, if this kind of approach should prove popular, it might be possible to include this information directly in the ''composer.json'' and let Composer manage it:+For libraries using Composer this bootstrap file would be specified as a "files" autoloader
 + 
 +<code json> 
 +
 +    "autoload": { 
 +        "psr-4": { "Vendor\\Lib\\": "src/" }, 
 +        "files": ["src/bootstrap.php"
 +    } 
 +
 + 
 +</code> 
 + 
 +However, if this kind of approach should prove popular, it might be possible to include this information directly in the ''composer.json'' and let Composer manage it:
  
 <code json> <code json>
Line 55: Line 68:
  
 <code php> <code php>
-function namespace_declare(string $namespace, array $directives) : void;+function namespace_declare(string $namespace, array $declares) : void;
 </code> </code>
  
-The given ''$directives'' will be used as default values for all code inside the namespace ''$namespace'' or a sub-namespace. A sub-namespace is any namespace that starts with ''$namespace . "\\"''(Note that namespaces are case-insensitive, so ''namespace_declare'' also applies case-insensitively.)+The given ''$declares'' will be used as default values for all code inside the namespace ''$namespace'' or a sub-namespace. A sub-namespace is any namespace that starts with ''$namespace . "\\"''Namespaces in PHP are case-insensitive, so ''namespace_declare'' also applies case-insensitively.
  
-If the provided ''$namespace'' is the empty string, an ''Error'' is thrown. It is explicitly not possible to specify a global default.+If the provided ''$namespace'' is the empty string, an ''Error'' is thrown. It is explicitly not possible to specify a global default. If ''$namespace'' starts or ends with a backslash, an ''Error'' is thrown.
  
 If ''namespace_declare'' was already called for a certain ''$namespace'', another call with the same namespace will result in an ''Error''. If ''namespace_declare'' was already called for a certain ''$namespace'', another call with the same namespace will result in an ''Error''.
  
-The declare directive defaults specified by ''namespace_declare()'' take effect for all files compiled (i.e. included) **after** the execution of the function call. Files included prior to the function call are not affect. The file that contains the call is also not affectedbecause the function will only be executed after the file has already been compiled.+If code using ''$namespace'' or a sub-namespace has already been loaded, an ''Error'' is thrown. This avoids situations where code in a namespace is loaded, subsequently ''namespace_declare()'' is used and more code from the namespace is loaded. Without this checkcode in the same namespace would end up using inconsistent declare directives. 
 + 
 +''namespace_declare'' has no impact on aliases created using ''class_alias'' and entities declared by internal code.
  
 ==== Nesting behavior ==== ==== Nesting behavior ====
Line 71: Line 86:
  
 <code php> <code php>
-namespace_declare('A', ['strict_types' => 1, 'no_dynamic_properties' => 1]); +namespace_declare('A', ['strict_types' => 1, 'dynamic_object_properties' => 0]); 
-namespace_declare('A\B', ['no_dynamic_properties' => 0]);+namespace_declare('A\B', ['dynamic_object_properties' => 1]);
 </code> </code>
  
Line 78: Line 93:
  
 <code> <code>
-no_dynamic_properties=// Because this was specified on A\B +dynamic_object_properties=// Because this was specified on A\B 
-strict_types=1          // Because this was not specified on A\B, but specified on A +strict_types=1              // Because this was not specified on A\B, but specified on A 
-ticks=0                 // Because this was not specified on A\B or A, so the global default is used+ticks=0                     // Because this was not specified on A\B or A, so the global default is used
 ... ...
 </code> </code>
Line 87: Line 102:
  
 <code php> <code php>
-namespace_declare('A\B', ['no_dynamic_properties' => 0]); +namespace_declare('A\B', ['dynamic_object_properties' => 1]); 
-namespace_declare('A', ['strict_types' => 1, 'no_dynamic_properties' => 1]);+namespace_declare('A', ['strict_types' => 1, 'dynamic_object_properties' => 0]);
 </code> </code>
  
-==== Open Question: Handling of unknown directives ====+==== Interaction with files using mixed namespaces ====
  
-The ''declare()'' statement will throw a compile-time warning if an unknown directive is used. This has the advantage that typos can be easily spotted. On the other hand this is bad for forward-compatibility, because specifying a directive that is only known in newer PHP versions would generate a warning in older versions. If the directive only exists to make certain checks stricter (such as  the ''dynamic_object_properties'' example) it would be preferably it would simply be (silently) ignored on older versions. +If multiple namespaces are mixed in a single file, all used namespaces are required to have consistent namespace-scoped declares. For example, the following code is illegal:
- +
-If we go for throwing a warning here, it might be beneficial to add a ''supports_declare()'' function, which allows you to determine whether a certain declare directive is supported, without resorting to a PHP version check (which would likely be unreliable with regard to alternative PHP implementations). +
- +
-==== Open Question: namespace_declare after namespace already used ==== +
- +
-As specified above, it is possible to first load code in a certain namespace and subsequently call ''namespace_declare()''. In this case the already compiled code will not be affected by the ''namespace_declare()'', which is bound to cause confusion. +
- +
-To avoid this, ''namespace_declare()'' could throw an ''Error'' if it called after code in the given namespace (or a sub-namespace) has already been loaded. However, this would require us to internally track which namespaces have had code loaded, something which we currently don't do. This tracking would also require opcache support. +
- +
-==== Open Question: Files with mixed namespaces ==== +
- +
-PHP allows mixing multiple namespaces in a single file. This is problematic for the feature being proposedbecause the different namespaces could have different namespace-scoped declares:+
  
 <code php> <code php>
Line 113: Line 116:
  
 // some_file.php // some_file.php
-namespace A; // Is strict_types=1+namespace A; // strict_types=1
 ... ...
  
-namespace B; // Is strict_types=0+namespace B; // strict_types=0
 ... ...
 </code> </code>
  
-Not only is this very confusingbecause this means that language behavior can silently change in the middle of a file, but it also poses a significant implementational problemFor example the current implementation of ''strict_types'' only permits a consistent ''strict_types'' value for a whole file. Allowing this kind of code would require changing the ''strict_types'' implementation to support this and, more importantly, commit to support this for all future declare directives we introduce, which may be non-trivial.+This restriction is introduced for two reasons: Firstlylack of such a restriction would imply that language behavior can silently change in the middle of a single file, which would be very confusing. Secondly, there are are significant implementational complexities associated with allowing this type of code due to limitations of specific declare directivesIn particular the current implementation of ''strict_types'' only permits a consistent ''strict_types'' value for a whole file. Lifting the declare-consistency requirement would require changing the ''strict_types'' implementation to support this and, more importantly, commitment to support this for all future declare directives we introduce, which may be non-trivial.
  
-Instead it may be preferable to only allow mixing multiple namespaces in the same file if they have consistent declare directive defaults. This still allows mixing namespaces, but enforces that declare directives will not magically change throughout the file.+==== Supported declare directives ====
  
-TODO: What about the encoding directive+Currently PHP supports three declare directives, ''ticks'', ''strict_types'' and ''encoding''.
  
-===== ChallengesDisadvantagesAlternatives =====+''namespace_declare()'' does not support ''encoding'' and will generate an ''Error'' if it is specified. The reason is that ''encoding'' has very special semanticsand requires the directive to be resolved already at parse-timeso that the lexer behavior may be influenced. At the point where the first namespace is encountered, it may already be too late to act on this directive.
  
-==== Technical challenges ====+For ''ticks'' the passed value must be a non-negative integer, otherwise an ''Error'' is thrown.
  
-There are a number of technical challenges involved in the implementation of this feature:+For ''strict_types'' the passed value must be integer ''0'' or ''1'', otherwise an ''Error'' is thrown.
  
-  * If the open question "namespace_declare after namespace already usedis resolved in favor of forbidding such usesinformation about namespaces that have already been loaded needs to be stored by the runtime. This requires opcache integration. (I consider this resolution likely.) +==== Open Question: Handling of unknown directives ==== 
-  * If the open question "Files with mixed namespaces" is resolved in favor of allowing such uses, the strict_types implementation will have to be significantly changed to allow changing strict_types in the middle of file(consider this resolution unlikely.) + 
-  * In any case, namespace-scoped declares will have to be taken into account by opcache. Namely, if a file is compiled with a certain set of namespace-scoped declares, it cannot necessarily be reused if it is compiled with a different set of declaresThis could be solved by storing a checksum based on the namespace-scoped declares at the time of compilation together with the cached file and compare it when loading it. I believe this can be done efficiently.+The ''declare()'' statement will throw a compile-time warning if an unknown directive is used. This has the advantage that typos can be easily spotted. On the other hand this is bad for forward-compatibility, because specifying a directive that is only known in newer PHP versions would generate a warning in older versions. If the directive only exists to make certain checks stricter (such as  the ''dynamic_object_properties'' example) it would be preferably if it would simply be (silently) ignored on older versions. 
 + 
 +If we go for throwing a warning hereit might be beneficial to add a ''supports_declare()'' function, which allows you to determine whether a certain declare directive is supported, without resorting to a PHP version check (which would likely be unreliable with regard to alternative PHP implementations). 
 + 
 +==== Open Question: Introspection functionality ==== 
 + 
 +It might be useful to also provide a mechanism to retrieve the active namespace-scoped declares at runtime, e.gusing a ''get_namespace_declares()'' function. One could either make this return the declarations for all namespaces exactly as givenor return the computed declarations for specific namespace. I'm not sure what the specific use-case for this kind of introspection functionality would be, though
 + 
 +==== Implementation considerations ==== 
 + 
 +Declare directives have to be known at compile-timebecause they may directly influence the emitted opcodes. Files will always be compiled for the current declare configuration and opcache will cache them for that specific configuration. Opcache additionally stores which namespaces and namespace-scoped declares are used and will verify that the (relevant) namespace-scoped declares did not change when the file is loaded againIf the (relevant) namespace-scoped declares changed, the file will be invalidated and compiled again for the new configuration. 
 + 
 +===== Disadvantages =====
  
-==== Disadvantages ====+==== Reduced explicitness ====
  
 It could be argued that the introducing of namespace-scoped declares will make it less discoverable which declare values are active inside a given file, because they are no longer required to be specified at the top of the file. It could be argued that the introducing of namespace-scoped declares will make it less discoverable which declare values are active inside a given file, because they are no longer required to be specified at the top of the file.
Line 144: Line 159:
 Additionally it should be noted that the same problem (to an even worse degree) exists for the ini system, where ini options can be specified from many different sources (including multiple ini files, htaccess files and inline in PHP code). In practice this does not appear to be a major problem. Additionally it should be noted that the same problem (to an even worse degree) exists for the ini system, where ini options can be specified from many different sources (including multiple ini files, htaccess files and inline in PHP code). In practice this does not appear to be a major problem.
  
-==== Alternatives ====+Finally, while namespace-scoped declares may be less explicit, they fit better into the mental model we use as programmers. Libraries generally do not haphazardly switch between different strict_types files and instead use a consistent mode for the entire project. Namespace-scoped declares codify this and prevent mistakes like forgetting to add a declare directive when creating a new file. 
 + 
 +==== Proliferation of declare directives ==== 
 + 
 +While this RFC proposes specific functionality (namespace-scoped declares), the Motivation section outlined a likely future application of this mechanism, which is the introduction of additional declare directives that modify language behavior to be "stricter" in some sense, without breaking compatibility or interoperability. 
 + 
 +A danger I see in this approach is a proliferation of many declare directives controlling small bits of the language. In this RFC I used a ''dynamic_object_properties'' directive as an example. Recently a discussion has come up for a ''strict_comparison'' directive, which makes operators such as ''<'' type-strict. On top of this we could add a ''strict_conditionals'' directive to make conditions only accept booleans. Maybe ''strict_arg_counts'' to forbid passing too many arguments to a userland function. You get the picture. Increasingly minor issues would end up covered by their own declare directive, so that you'd have to specify dozens of different directives to enable everything. 
 + 
 +This problem might motivate one to instead introduce The One True Strict Mode, similar to ''"use strict"'' in JavaScript. A single ''strict=1'' directive that enables a certain, carefully chosen set of "strict" functionality. If there were just a single directive you have to specify at the top of each file, we could probably also live without the functionality proposed by this RFC. 
 + 
 +Of course, such a general strict mode would also have its own set of issues. For once, it is something that we can only do once. Extending the functionality of the strict mode after it has been introduce would constitute a backwards-compatibility break. Additionally, a general strict mode requires a wide consensus on what functionality it should include. I know people who would welcome stricter language semantics in some areas but are also vehemently opposed to including ''strict_types=1'' in the ''strict=1'' mode. If the strict mode can't include strict types, what *can* it include? I suspect that this approach will lead to the strict mode only including a small set of the most uncontroversial changes, decreasing its usefulness. 
 + 
 +Nonetheless, I think that proliferation of declare directives is a real danger and we need to ensure that new directives are not added frivolously. 
 + 
 +==== Discussion: Potential for abuse ==== 
 + 
 +One issue that came up repeatedly during the first discussion, is that people perceived a potential for abuse in this feature. The concern is that it would be possible to call ''namespace_declare()'' on a namespace one doesn't own, and consequently break code using that namespace. 
 + 
 +I honestly do not understand this concern. PHP is already oversaturated with ways in which you could break external library code if one wants to (such as hijacking autoloading). If breaking library code is your goal, you don't need this feature to achieve that. However, the question remains why anyone would want to this, as in the end you only sabotage yourself. 
 + 
 +===== Alternatives =====
  
 The main alternative to this proposal I see is the introduction of a "proper module system", which would allow per-module specification of declares, similar to package attributes in Java. However this is just a very vague concept in my mind and would certainly require major language changes. The main alternative to this proposal I see is the introduction of a "proper module system", which would allow per-module specification of declares, similar to package attributes in Java. However this is just a very vague concept in my mind and would certainly require major language changes.
 +
 +While not an alternative to this feature, the general strict mode mentioned in the previous section might obsolete the need for namespace-scoped declares.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
  
-None.+The global function name ''namespace_declare'' will no longer be usable.
  
-===== Proposed Voting Choices =====+===== Vote =====
  
 As this is a language change, a 2/3 majority is required. As this is a language change, a 2/3 majority is required.
Line 158: Line 195:
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
-There is no implementation yet.+PR: https://github.com/php/php-src/pull/2972 
 + 
 + 
 +===== TODOs ===== 
 + 
 +  * Resolve open question "Handling of unknown directives" 
 +  * Resolve open question "Introspection functionality"
rfc/namespace_scoped_declares.1474403638.txt.gz · Last modified: 2017/09/22 13:28 (external edit)