Description
Recently, the Deprecate dynamic properties RFC has been approved for PHP 8.2 and as I expected it will cause havoc, I've started doing some test runs against various repos, including PHPCS.
The test run against PHPCS only went to proof my suspicion as this deprecation (or rather the fatal error this will become in PHP 9.0) breaks currently supported functionality in PHPCS.
Context
The deprecation of dynamic properties basically means that any property which is not explicitly declared in a class, but is being get/set will now yield a "Creation of dynamic property ClassName::$propertyName is deprecated" deprecation notice, which will become a fatal error in PHP 9.0.
There are three exceptions to this:
- Dynamic properties are still supported when the class implements the magic
__get()
and__set()
methods. - Dynamic properties are still supported when a class extends
stdClass
(which implements the magic__get()
and__set()
methods). - Dynamic properties are still supported when a class is given the
#[AllowDynamicProperties]
attribute, though this attribute is expected to only be supported for a limited time (until ~PHP 9.0) as the intention is to eventually remove support for dynamic properties completely from PHP (with the exception of the above two situations).
What problem does this cause in PHPCS ?
In PHPCS, the value of public
properties can be adjusted from within a ruleset and from within (test) files using the phpcs:set
annotation.
While this feature is mostly used for adjusting the properties for individual sniffs, there are a number of (external) standards which use the same property in a multitude of sniffs.
As things are, PHPCS currently explicitly supports setting the value for a (public
) property for all sniffs in a category/standard in one go, like so:
<rule ref="PSR1">
<properties>
<property name="setforallsniffs" value="true" />
</properties>
</rule>
This is also documented and safeguarded as such via the RuleInclusionTest
.
The deprecation/removal of support for dynamic properties breaks this functionality.
What are our options ?
- Add the
#[AllowDynamicProperties]
attribute to every sniff.
Impact: HIGH for sniff maintainers, no impact for users.
Breaking change: No
Stable: No. The attribute would also need to be added to every single sniff out in the wild in external standards, so this will break as soon as an external standard does not have the attribute. It will also break (again) when support for the attribute is removed from PHP. - Add the magic
__get()
and__set()
methods to theSniff
interface.
Impact: HIGH for sniff maintainers, no impact for users.
Breaking change: Yes as every single sniff class, both internal and external, would need to implement these methods.
This change could only be made for PHPCS 4.0, but will allow for standards to be cross-version compatible with PHPCS 3.x and 4.x without extra effort.
Stable: Yes, for those standards which choose to support PHPCS 4.x and make this change. - Add a new
AbstractSniff
base class which includes the magic__get()
and__set()
methods.
Impact: HIGH for sniff maintainers, no impact for users.
Breaking change: Yes as the class declaration for every single sniff, both internal and external would need to be updated fromSniffName implements Sniff
toSniffName extends Sniff
. This change could only be made for PHPCS 4.0 and will make it a lot more complicated for standards to be cross-version compatible with PHPCS 3.x and 4.x (for those who desire this).
Stable: Yes, for those standards which choose to support PHPCS 4.x and make this change. - Make all sniffs extend
stdClass
.
Impact: HIGH for sniff maintainers, no impact for users.
Breaking change: No The class declaration for every single (abstract) sniff, both internal and external would need to be updated fromSniffName implements Sniff
toSniffName extends stdClass implements Sniff
, but this is not enforced by PHPCS, so standard maintainers can implement this whenever they are getting ready to support PHP 8.2. This change would also be cross-version compatible with both PHPCS 3.x as well as 4.x (for those who desire this).
Stable: Yes, for those standards which choose to make this change. - Remove support for setting properties for categories/standards in one go.
Impact: none for sniff maintainers, MEDIUM impact for users.
Any custom ruleset/standard which currently uses this feature will need to be adjusted and for those standards where this feature is commonly used, this will mean fiddly ruleset adjustments - the property would need to be explicitly set for every sniff using it, new sniffs added to a standard would not automatically receive the property anymore etc
While this feature may not be used in a huge amount of standards, for those standards - and their users - where it is used, it will cause a lot of churn in continuous ruleset adjustments now and in the future.
Breaking change: Yes A feature which has always been supported would now be removed.
Stable: No, the need to continuously update rulesets whenever new sniffs using common properties come out, make this unstable for end-users. - Only set properties received from a ruleset when the property exists on a sniff.
... and throw an informative error when a ruleset attempts to set a property on a sniff which doesn't have that property.
The error message should only be thrown when the property is being set on an individual sniff, not when the property is (attempted to be) set for a standard/category of sniffs.
Impact: LOW/MEDIUM for sniff maintainers, LOW impact for users.
Breaking change: Yes, sort of.
While probably rare, there may be a few standards out there relying on "magic dynamic" properties which may or may not be available to the sniff depending on the ruleset under which a sniff is run. Those standards would break with this change, but that break can be mitigated by the standard maintainers by either adding the magic__get()
/__set()
/__isset()
methods or making the property/properties explicit on each sniff.
Stable: Yes, sort of and the informative error message for typos in properties would be in line with the "goal" of the PHP RFC.
What will not work
- Adding the
#[AllowDynamicProperties]
attribute to theSniff
interface, for it to be inherited by implementing classes, is not an option as it will result in aFatal error: Cannot apply #[AllowDynamicProperties] to interface
.
Proposal
All things considered, I'd like to propose implementing option 6 for the following reasons:
- The change would not be bound to PHPCS 4.x, which would put pressure on the roadmap for PHPCS 4.x as it would then be closely tied to the PHP 8.2 release schedule,
- The change will yield improved error messages for users with typos in property names in custom ruleset, which is in line with the PHP RFC and would help end-users of PHPCS.
- While it does have the potential to break (a limited set of) external standards, I suspect this will be exceedingly rare and that far more standards will benefit from the continued support for setting properties for a category/standard in one go.
Opinions ?