Repository navigation
Refactor suggestion / move utility functions from File to static classes #2189
Description
Since PHPCS 3.x now has a minimum PHP requirement of PHP 5.4, I was wondering whether it might be an idea to refactor a number of groups of methods in the File class to traits.
The reasoning behind this is two-fold:
- Only a limited number of sniffs use the methods I have in mind for this, so having them in traits rather than in the
Fileclass makes the re-useable code more modular and easier to maintain. - Over the years I have written a number of additional token walking utility methods which I think belong in PHPCS rather than in the WPCS/PHPCompatibility standards in which they now live.
Traits would allow for these methods to be added to PHPCS more easily without making theFileclass even larger than it already is.
Timing
The transition could be made in PHPCS 3.x.
In PHPCS 3.x, the File class would in that case use the new traits and for the transitioned methods just call the method in the trait under the hood.
The methods in the File class would be deprecated and could be removed in PHPCS 4.x.
This would allow external standards some time to implement the new way of doing things and make the BC break in PHPCS 4.x smaller as it can already be mitigated beforehand.
Proposal details
To be concrete, I'm currently thinking of the following Traits:
DeclarationName, containing:getName(), previously calledgetDeclarationName()
FunctionDeclaration, containing:getParameters(), previously calledgetMethodParameters()getProperties(), previously calledgetMethodProperties()- and using
DeclarationNamefor thegetName()method
PropertyDeclaration, containing:getMemberProperties()
ObjectDeclarationcontaining:getClassProperties()findExtendedClassName()findImplementedInterfaceName()findExtendedInterfaceNames()- open PR File: add newFile::findExtendedInterfaceNames()utility method #2128- and using
DeclarationNamefor thegetName()method
Genericcontaining:isReference()
ScopeConditions, containing:hasCondition()getCondition()
Additionally, I'd be happy to pull the following based on battle-tested code I've written for WPCS/PHPCompatibility:
FunctionCall, containing:hasParameters()getParameterCount()getParameters()getParameter()(to get the details for the parameter at a specific position)
These methods all work on both function calls as well as for splitting an array into the individual items and could be extended to coverisset(),unset()andlist()as well.
- And if there would be any interest in it -
FQClassNames, containing:getNameFromNewToken()getNameFromDoubleColonToken()getExtendedName()getFQName()isNamespaced()determineNamespace()getDeclaredNamespaceName()
For the Generic trait, I could also offer an additional isShortList() method.
I'm also still working on the logic for a TStringAnalyser which should reliably determine whether, for instance, a T_STRING token represents a function call, the use of a global constant or for instance a use statement alias.
Open questions
What I'd currently like to know, is:
- Does the principle of this sound like a good idea for PHPCS ?
- If so, does the implementation proposal for PHPCS 3.x/4.x sound like a good idea ?
- If so, where should the traits live ? I.e. , should they be placed in a dedicated
Traitsdirectory or, for instance, inUtil/Traitsor should the directory be called something completely different ? - If so, feedback on the proposed initial Traits as outline above (trait names, function groupings etc)
- And would there be interest in the additional traits/methods as outlined above ?
Activity
gsherwood commentedon Oct 18, 2018
Yeah, I think I like the idea. The File class has always been bloated but I've never put thought into refactoring it due to the large BC break it would cause.
My initial thought was v4 only, but your point about developers being able to migrate sniffs before v4 is a good one, so I think you are right. This feels like something that can go into v3 with the transitional methods and in File deprecated immediately and removed in v4.
I would normally throw them under a
src/Traitsfolder. I do prefer trait class to include the word "Trait" at the end of the file and trait name, but having Traits in the FQ name makes this redundant. So I don't know. Maybe they should live insrc/Filesorsrc/Sniffssomewhere?They look ok to me. I don't have a strong opinion on this yet, but I imagine I will once I see some code being used.
This one's a big job and something I'd probably want to solely focus on for a single PHPCS release as it is also going to be a big information dump for sniff developers. So maybe this is the change in v3.5 or v3.6.
jrfnl commentedon Oct 18, 2018
@gsherwood Thanks for your response and feedback. I'd happily work on this, but as you say you'd prefer to have this in a dedicated release, I'll hold off for now until 3.4.0 has come out. That would also allow for PR #2128 to be merged before this refactor.
jrfnl commentedon Nov 15, 2018
I've been thinking about this some more and while traits would be a good solution for some of the external standards I work on, which have a base sniff class from which all sniffs extend, for PHPCS itself, the implementation will be simpler and cleaner as classes with static properties and static methods.
This will prevent naming conflicts over imported traits when both an abstract sniff as well as a child sniff would use the same trait, or when one trait would use another.
With that mind, putting these static classes in the
Utilor in aHelperdirectory seems logical.For anyone wondering about the difference/choice for static classes:
Nearly all of these methods will need to be passed the
$phpcsFileobject and will need the tokens array.In some of the external standards, a base sniff class is used which makes those available via
$thisto save having to pass these around on every function call to utility functions, like so:However, as this is not standard in PHPCS itself, traits would not have access to
$phpcsFileor$tokensvia$this, which removes the reason for using traits instead of static classes.jrfnl commentedon Dec 17, 2018
Just FYI: I've started work on this as, as soon as I started work on the AbstractDocblockSniff - #2222 -, I realized some things would be easier if some of the new utility functions would already be available.
I hope to slowly but surely get a complete WIP branch ready by the time 3.4.0/3.4.1 is released, so I can start pulling the individual bits from it as soon as 3.5.0 opens for commits.
Once I've cleaned up what I've done so far, I'll put a WIP branch online somewhere so anyone interested can have a look at the direction this is going in.
jrfnl commentedon Jan 10, 2019
Little status update for those interested:
I'm working on this on and off, but making steady progress. I estimate that about 70% is done at this moment.
Notes:
Depending on what gets merged before this refactor gets merged, some additional adjustments may still be needed, but I'm keeping a close eye on that.
public staticand can be called from anywhere (unless otherwise indicated).Current structure as things are shaping up:
Util\ConditionUtilsgetCondition()(moved fromFile) - this method has been enhanced a lot to be more flexible and remove code duplication.hasCondition()(moved fromFile)getFirstCondition()getLastCondition()isOOProperty()isOOConstant()isOOMethod()validDirectScope()Util\FunctionDeclarationUtils$magicMethods(moved fromGeneric.NamingConventions.CamelCapsFunctionName+PEAR.NamingConventions.ValidFunctionName)$methodsDoubleUnderscore(moved fromGeneric.NamingConventions.CamelCapsFunctionName)$magicFunctions(moved fromGeneric.NamingConventions.CamelCapsFunctionName+PEAR.NamingConventions.ValidFunctionName)getMethodParameters()(moved fromFile)getMethodProperties()(moved fromFile)isMagicFunction()isMagicFunctionName()isMagicMethod()isMagicMethodName()isPHPDoubleUnderscoreMethod()isPHPDoubleUnderscoreMethodName()isSpecialMethod()isSpecialMethodName()Util\ObjectDeclarationUtilsgetClassProperties()(moved fromFile)findExtendedClassName()(moved fromFileand adjusted based on File: add newFile::findExtendedInterfaceNames()utility method #2128)findExtendedInterfaceNames()- see PR File: add newFile::findExtendedInterfaceNames()utility method #2128findImplementedInterfaceNames()(moved fromFileand adjusted based on File: add newFile::findExtendedInterfaceNames()utility method #2128)examineObjectDeclarationSignature()- see PR File: add newFile::findExtendedInterfaceNames()utility method #2128Util\PassedParametersUtilshasParameters()getParameters()getParameter()getParameterCount()Currently working on:
Util\VariableUtils$phpReservedVars(moved fromAbstractVariableSniff+ enhanced and made complete)getMemberProperties()(moved fromFile)isPHPReservedVarName()isSuperglobal()isSuperglobalName()isForeachAs()isAssignment()Util\NameUtilsgetDeclarationName()(moved fromFile)isCamelCaps()(moved fromUtil\Common)toCamelCaps()isPascalCase()toPascalCase()isUpperCaseUnderscores()toUpperCaseUnderscores()isSnakeCase()toSnakeCase()Util\TextStringUtilsREGEX_COMPLEX_VARSstripQuotes()stripInterpolatedVariables()getInterpolatedVariables()Util\...(name to be determined)isReference()(moved fromFile)isShortList()getUseType()getAsType()On the back-burner for later in combination with the
AbstractDocblockSniff(see #2222):Util\CommentUtils$allowedTypes(moved fromCommon)suggestType()(moved fromCommon)findCommentAboveFunction()findCommentAboveOOStructure()findCommentAboveOOProperty()findCommentAboveOOConstant()findCommentAbove()Feedback welcome.
gsherwood commentedon Jan 10, 2019
First thought... wow :)
Second is just about naming. I don't think every class under
Utilalso needs to end withUtils. Seems redundant to me in this case. Is there a specific reason (code looks better, it's a convention im not aware of, etc)?I'd also change
FunctionsDeclarationUtilstoFunctionDeclarationto matchObjectDeclaration(unless that was a typo).jrfnl commentedon Jan 10, 2019
😀
Not existing convention or anything, just felt like a way to differentiate these classes from the other/existing classes in the
Utildirectory which don't typically offer methods for use in sniffs (other than theCommonclass, but the utility methods in that class will be moved out to one of the new classes anyway).Alternatively, another directory for these type of classes would also be an option. Just what would that directory be called in that case ?
That was a typo ;-)
gsherwood commentedon Jan 10, 2019
In my head, these were under File/Util, but I think I just read your comments incorrectly.
Maybe Util/Sniffs ?? I hate naming things.
jrfnl commentedon Jan 10, 2019
To prevent further confusion, they are currently in the
src/Utildirectory.I hear you.
Util/Sniffsdoesn't feel right either though, though I haven't got a good alternative.I'll sleep on it and welcome more suggestions. Someone will have a good name in mind ;-)
[-]Refactor suggestion / move utility functions from File to traits[/-][+]Refactor suggestion / move utility functions from File to static classes[/+]jrfnl commentedon Jan 26, 2019
Ok, let's figure this name thing out as I will have to redo every single commit with the new names, so I can't really publish a WIP branch until we have a decision on this.
Some suggestions of where to place these classes (in alphabetic order, not indicating any preference):
src/Helperssrc/SniffHelperssrc/SniffUtilssrc/Utils/SniffHelpersThe previously suggested
src/Sniff/Utilsdoesn't really feel right as it would lump these classes together with the abstracts, while they are not abstracts and are useful for a lot more sniffs than just the abstracts.The previously suggested
src/File/Utilsdoesn't really feel right either as these classes don't provide aFileobject with tokens, like the classes inFile.My preference would be a toplevel directory as hiding these away in a deeper level makes them less easily discoverable and they are kind of useful ;-)
Watchers of this repo, please feel free to chime in with ideas or preferences as well.
jrfnl commentedon Jan 26, 2019
@gsherwood In practical terms, how would you like this to be pulled ?
PR #2295 is a prerequisite for this PR so would need to be merged first and I've worked on the presumption that PR #2353 will be merged beforehand as well.
The way I've set things up is with atomic commits with most of the time quite detailed commit messages telling "the story" (see screenshot).
Screenshot of the first part of the WIP branch commits
Some parts could be pulled simultaneously, but pulling things in one go or in blocks in the order I've set them up will probably be easiest as for implementation commits, the
usestatements in sniffs would conflict if not pulled in this order and for commits adding files (the helper classes and/or unit tests), thepackage.xmlfile would quickly start to conflict.GaryJones commentedon Jan 26, 2019
src/Helpersis too generic.Of those choices,
src/SniffUtilsseems the most descriptive and accurate. These are utility classes / methods / things to help write Sniffs efficiently.jrfnl commentedon Jan 28, 2019
For the naming convention methods, I'm still trying to find unambiguous specifications. I've created a little comparison table of my current understanding of them.
Input welcome! You can comment in the comparison table.
https://docs.google.com/spreadsheets/d/1WQo7G3BRIk0dT9eMxcPJw48DShrlenQRfvlD--rFAgA/edit?usp=sharing
jrfnl commentedon Feb 2, 2019
@gsherwood Just checking in: have you had a chance to think about the naming and how to pull this ?
6 remaining items
gsherwood commentedon Feb 28, 2019
Given true/false are valid return types, the long form probably remains as true/false, unless I've missed some complexity here.
gsherwood commentedon Feb 28, 2019
I think pulling in the other two commits at once makes sense. I'll target them for 3.5.0 as well.
As for naming, I really dislike multi-word directories, so my vote is for
src/Util/Sniffsorsrc/Util/Sniff- can't decide if I like the plural form or not. Every other directory besidesUtiluses plurals, so I'd probably lean that way.jrfnl commentedon Feb 28, 2019
@gsherwood Thanks for the input. That would hide these utility classes away in a deeper level, making them a little less easily discoverable, and - aside from the
Standardsdirectory - none of the others directories insrchave deeper levels.gsherwood commentedon Feb 28, 2019
I don't think they are hidden in there. I suspect people would look in Utils for utility functions and see another directory dedicated to sniff utility classes. It also allows for future util categories to have a place.
True, but I don't think those other dirs are related to each other. This is a new collection of utility classes that will be used just as often as Util/Tokens. I'd prefer to keep them together in a Util folder and namespace.
jrfnl commentedon Feb 28, 2019
Okidokie,
src/Util/Sniffsit is.Give me a few days to adjust all the individual commits. After that I will set up a draft PR.
jrfnl commentedon Feb 28, 2019
Oh and just to be sure: we were going to drop the
Utilsfrom the class names too, weren't we ?So, for just a sampling of the new classes:
src\Util\ConditionUtilssrc\Util\Sniffs\ConditionsPHP_CodeSniffer\Util\Sniffs\Conditionssrc\Util\FunctionDeclarationUtilssrc\Util\Sniffs\FunctionDeclarationsPHP_CodeSniffer\Util\Sniffs\FunctionDeclarationssrc\Util\ObjectDeclarationUtilssrc\Util\Sniffs\ObjectDeclarationsPHP_CodeSniffer\Util\Sniffs\ObjectDeclarationssrc\Util\NamespaceUtilssrc\Util\Sniffs\NamespacesPHP_CodeSniffer\Util\Sniffs\Namespacesgsherwood commentedon Feb 28, 2019
Yes please
jrfnl commentedon Mar 23, 2019
Ok, done all the renames and recommits. WIP is up as a draft PR #2456
mwgamble commentedon Mar 26, 2019
I don't think this is accurate. This snippet works just fine in PHP 5.5 and up:
https://3v4l.org/S5KTi
jrfnl commentedon Mar 26, 2019
@mwgamble Interesting, I'll do some testing with one of the external standards, but I seem to remember this was problematic when I was making some of those cross-version compatible with PHPCS 2 and 3.
Providing the tests are successful, I'll remove that phrase from the Upgrade Guide. Thanks for your input!
jrfnl commentedon Mar 26, 2019
@mwgamble Tested & found working. I've updated the Upgrade Guide to reflect this and expanded the code sample as well. Thanks!
jrfnl commentedon Apr 12, 2020
Closing as this refactor is not going into PHPCS anymore.
It has been published separately as PHPCSUtils. More details in this comment: #2456 (comment)