Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F5982920
PassedParameters.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
24 KB
Referenced Files
None
Subscribers
None
PassedParameters.php
View Options
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2020 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/
namespace
PHPCSUtils\Utils
;
use
PHP_CodeSniffer\Exceptions\RuntimeException
;
use
PHP_CodeSniffer\Files\File
;
use
PHP_CodeSniffer\Util\Tokens
;
use
PHPCSUtils\Internal\Cache
;
use
PHPCSUtils\Tokens\Collections
;
use
PHPCSUtils\Utils\Arrays
;
use
PHPCSUtils\Utils\GetTokensAsString
;
/**
* Utility functions to retrieve information about parameters passed to function calls,
* class instantiations, array declarations, isset and unset constructs.
*
* @since 1.0.0
*/
final
class
PassedParameters
{
/**
* Tokens which are considered stop point, either because they are the end
* of the parameter (comma) or because we need to skip over them.
*
* @since 1.0.0
*
* @var array<int|string, int|string>
*/
private
static
$callParsingStopPoints
=
[
\T_COMMA
=>
\T_COMMA
,
\T_OPEN_SHORT_ARRAY
=>
\T_OPEN_SHORT_ARRAY
,
\T_OPEN_SQUARE_BRACKET
=>
\T_OPEN_SQUARE_BRACKET
,
\T_OPEN_PARENTHESIS
=>
\T_OPEN_PARENTHESIS
,
\T_DOC_COMMENT_OPEN_TAG
=>
\T_DOC_COMMENT_OPEN_TAG
,
\T_ATTRIBUTE
=>
\T_ATTRIBUTE
,
];
/**
* Checks if any parameters have been passed.
*
* - If passed a `T_STRING`, `T_NAME_FULLY_QUALIFIED`, `T_NAME_RELATIVE`, `T_NAME_QUALIFIED`,
* or `T_VARIABLE` stack pointer, it will treat it as a function call.
* If a token which is *not* a function call is passed, the behaviour is undetermined.
* - If passed a `T_ANON_CLASS` stack pointer, it will accept it as a class instantiation.
* - If passed a `T_SELF`, `T_STATIC` or `T_PARENT` stack pointer, it will accept it as a
* class instantiation function call when used like `new self()` (with or without parentheses).
* When these hierarchiecal keywords are not preceded by the `new` keyword, parentheses
* will be required for the token to be accepted.
* - If passed a `T_ARRAY` or `T_OPEN_SHORT_ARRAY` stack pointer, it will detect
* whether the array has values or is empty.
* For purposes of backward-compatibility with older PHPCS versions, `T_OPEN_SQUARE_BRACKET`
* tokens will also be accepted and will be checked whether they are in reality
* a short array opener.
* - If passed a `T_ISSET` or `T_UNSET` stack pointer, it will detect whether those
* language constructs have "parameters".
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position of function call name,
* language construct or array open token.
* @param true|null $isShortArray Optional. Short-circuit the short array check for
* `T_OPEN_SHORT_ARRAY` tokens if it isn't necessary.
* Efficiency tweak for when this has already been established,
* Use with EXTREME care.
*
* @return bool
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
* accepted types or doesn't exist.
*/
public
static
function
hasParameters
(
File
$phpcsFile
,
$stackPtr
,
$isShortArray
=
null
)
{
$tokens
=
$phpcsFile
->
getTokens
();
if
(
isset
(
$tokens
[
$stackPtr
])
===
false
||
isset
(
Collections
::
parameterPassingTokens
()[
$tokens
[
$stackPtr
][
'code'
]])
===
false
)
{
throw
new
RuntimeException
(
'The hasParameters() method expects a function call, array, isset or unset token to be passed.'
);
}
// Only accept self/static/parent if preceded by `new` or followed by an open parenthesis.
$next
=
$phpcsFile
->
findNext
(
Tokens
::
$emptyTokens
,
(
$stackPtr
+
1
),
null
,
true
);
if
(
isset
(
Collections
::
ooHierarchyKeywords
()[
$tokens
[
$stackPtr
][
'code'
]])
===
true
)
{
$prev
=
$phpcsFile
->
findPrevious
(
Tokens
::
$emptyTokens
,
(
$stackPtr
-
1
),
null
,
true
);
if
(
$tokens
[
$prev
][
'code'
]
!==
\T_NEW
&&
(
$next
!==
false
&&
$tokens
[
$next
][
'code'
]
!==
\T_OPEN_PARENTHESIS
)
)
{
throw
new
RuntimeException
(
'The hasParameters() method expects a function call, array, isset or unset token to be passed.'
);
}
}
if
(
isset
(
Collections
::
shortArrayListOpenTokensBC
()[
$tokens
[
$stackPtr
][
'code'
]])
===
true
&&
$isShortArray
!==
true
&&
Arrays
::
isShortArray
(
$phpcsFile
,
$stackPtr
)
===
false
)
{
throw
new
RuntimeException
(
'The hasParameters() method expects a function call, array, isset or unset token to be passed.'
);
}
if
(
$next
===
false
)
{
return
false
;
}
// Deal with short array syntax.
if
(
isset
(
Collections
::
shortArrayListOpenTokensBC
()[
$tokens
[
$stackPtr
][
'code'
]])
===
true
)
{
if
(
$next
===
$tokens
[
$stackPtr
][
'bracket_closer'
])
{
// No parameters.
return
false
;
}
return
true
;
}
// Deal with function calls, long arrays, isset and unset.
// Next non-empty token should be the open parenthesis.
if
(
$tokens
[
$next
][
'code'
]
!==
\T_OPEN_PARENTHESIS
)
{
return
false
;
}
if
(
isset
(
$tokens
[
$next
][
'parenthesis_closer'
])
===
false
)
{
return
false
;
}
$ignore
=
Tokens
::
$emptyTokens
;
$ignore
[
\T_ELLIPSIS
]
=
\T_ELLIPSIS
;
// Prevent PHP 8.1 first class callables from being seen as function calls.
$closeParenthesis
=
$tokens
[
$next
][
'parenthesis_closer'
];
$nextNextNonEmpty
=
$phpcsFile
->
findNext
(
$ignore
,
(
$next
+
1
),
(
$closeParenthesis
+
1
),
true
);
if
(
$nextNextNonEmpty
===
$closeParenthesis
)
{
// No parameters.
return
false
;
}
return
true
;
}
/**
* Get information on all parameters passed.
*
* See {@see PassedParameters::hasParameters()} for information on the supported constructs.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position of function call name,
* language construct or array open token.
* @param int $limit Optional. Limit the parameter retrieval to the first #
* parameters/array entries.
* Use with care on function calls, as this can break
* support for named parameters!
* @param true|null $isShortArray Optional. Short-circuit the short array check for
* `T_OPEN_SHORT_ARRAY` tokens if it isn't necessary.
* Efficiency tweak for when this has already been established,
* Use with EXTREME care.
*
* @return array<int|string, array<string, int|string>>
* A multi-dimentional array with information on each parameter/array item.
* The information gathered about each parameter/array item is in the following format:
* ```php
* 1 => array(
* 'start' => int, // The stack pointer to the first token in the parameter/array item.
* 'end' => int, // The stack pointer to the last token in the parameter/array item.
* 'raw' => string, // A string with the contents of all tokens between `start` and `end`.
* 'clean' => string, // Same as `raw`, but all comment tokens have been stripped out.
* )
* ```
* If a named parameter is encountered in a function call, the top-level index will not be
* the parameter _position_, but the _parameter name_ and the array will include two extra keys:
* ```php
* 'parameter_name' => array(
* 'name' => string, // The parameter name (without the colon).
* 'name_token' => int, // The stack pointer to the parameter name token.
* ...
* )
* ```
* The `'start'`, `'end'`, `'raw'` and `'clean'` indexes will always contain just and only
* information on the parameter value.
* _Note: The array starts at index 1 for positional parameters._
* _The key for named parameters will be the parameter name._
* If no parameters/array items are found, an empty array will be returned.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
* accepted types or doesn't exist.
*/
public
static
function
getParameters
(
File
$phpcsFile
,
$stackPtr
,
$limit
=
0
,
$isShortArray
=
null
)
{
if
(
self
::
hasParameters
(
$phpcsFile
,
$stackPtr
,
$isShortArray
)
===
false
)
{
return
[];
}
$effectiveLimit
=
(
\is_int
(
$limit
)
&&
$limit
>
0
)
?
$limit
:
0
;
if
(
Cache
::
isCached
(
$phpcsFile
,
__METHOD__
,
"$stackPtr-$effectiveLimit"
)
===
true
)
{
return
Cache
::
get
(
$phpcsFile
,
__METHOD__
,
"$stackPtr-$effectiveLimit"
);
}
if
(
$effectiveLimit
!==
0
&&
Cache
::
isCached
(
$phpcsFile
,
__METHOD__
,
"$stackPtr-0"
)
===
true
)
{
return
\array_slice
(
Cache
::
get
(
$phpcsFile
,
__METHOD__
,
"$stackPtr-0"
),
0
,
$effectiveLimit
,
true
);
}
// Ok, we know we have a valid token with parameters and valid open & close brackets/parenthesis.
$tokens
=
$phpcsFile
->
getTokens
();
// Mark the beginning and end tokens.
if
(
isset
(
Collections
::
shortArrayListOpenTokensBC
()[
$tokens
[
$stackPtr
][
'code'
]])
===
true
)
{
$opener
=
$stackPtr
;
$closer
=
$tokens
[
$stackPtr
][
'bracket_closer'
];
}
else
{
$opener
=
$phpcsFile
->
findNext
(
Tokens
::
$emptyTokens
,
(
$stackPtr
+
1
),
null
,
true
);
$closer
=
$tokens
[
$opener
][
'parenthesis_closer'
];
}
$mayHaveNames
=
(
isset
(
Collections
::
functionCallTokens
()[
$tokens
[
$stackPtr
][
'code'
]])
===
true
);
$parameters
=
[];
$nextComma
=
$opener
;
$paramStart
=
(
$opener
+
1
);
$cnt
=
1
;
$stopPoints
=
self
::
$callParsingStopPoints
+
Tokens
::
$scopeOpeners
;
$stopPoints
[]
=
$tokens
[
$closer
][
'code'
];
while
((
$nextComma
=
$phpcsFile
->
findNext
(
$stopPoints
,
(
$nextComma
+
1
),
(
$closer
+
1
)))
!==
false
)
{
// Ignore anything within square brackets.
if
(
isset
(
$tokens
[
$nextComma
][
'bracket_opener'
],
$tokens
[
$nextComma
][
'bracket_closer'
])
&&
$nextComma
===
$tokens
[
$nextComma
][
'bracket_opener'
]
)
{
$nextComma
=
$tokens
[
$nextComma
][
'bracket_closer'
];
continue
;
}
// Skip past nested arrays, function calls and arbitrary groupings.
if
(
$tokens
[
$nextComma
][
'code'
]
===
\T_OPEN_PARENTHESIS
&&
isset
(
$tokens
[
$nextComma
][
'parenthesis_closer'
])
)
{
$nextComma
=
$tokens
[
$nextComma
][
'parenthesis_closer'
];
continue
;
}
// Skip past closures, anonymous classes and anything else scope related.
if
(
isset
(
$tokens
[
$nextComma
][
'scope_condition'
],
$tokens
[
$nextComma
][
'scope_closer'
])
&&
$tokens
[
$nextComma
][
'scope_condition'
]
===
$nextComma
)
{
$nextComma
=
$tokens
[
$nextComma
][
'scope_closer'
];
continue
;
}
// Skip over potentially large docblocks.
if
(
$tokens
[
$nextComma
][
'code'
]
===
\T_DOC_COMMENT_OPEN_TAG
&&
isset
(
$tokens
[
$nextComma
][
'comment_closer'
])
)
{
$nextComma
=
$tokens
[
$nextComma
][
'comment_closer'
];
continue
;
}
// Skip over attributes.
if
(
$tokens
[
$nextComma
][
'code'
]
===
\T_ATTRIBUTE
&&
isset
(
$tokens
[
$nextComma
][
'attribute_closer'
])
)
{
$nextComma
=
$tokens
[
$nextComma
][
'attribute_closer'
];
continue
;
}
if
(
$tokens
[
$nextComma
][
'code'
]
!==
\T_COMMA
&&
$tokens
[
$nextComma
][
'code'
]
!==
$tokens
[
$closer
][
'code'
]
)
{
// Just in case.
continue
;
// @codeCoverageIgnore
}
// Ok, we've reached the end of the parameter.
$paramEnd
=
(
$nextComma
-
1
);
$key
=
$cnt
;
if
(
$mayHaveNames
===
true
)
{
$firstNonEmpty
=
$phpcsFile
->
findNext
(
Tokens
::
$emptyTokens
,
$paramStart
,
(
$paramEnd
+
1
),
true
);
if
(
$firstNonEmpty
!==
$paramEnd
)
{
$secondNonEmpty
=
$phpcsFile
->
findNext
(
Tokens
::
$emptyTokens
,
(
$firstNonEmpty
+
1
),
(
$paramEnd
+
1
),
true
);
if
(
$tokens
[
$secondNonEmpty
][
'code'
]
===
\T_COLON
&&
$tokens
[
$firstNonEmpty
][
'code'
]
===
\T_PARAM_NAME
)
{
if
(
isset
(
$parameters
[
$tokens
[
$firstNonEmpty
][
'content'
]])
===
false
)
{
// Set the key to be the name, but only if we've not seen this name before.
$key
=
$tokens
[
$firstNonEmpty
][
'content'
];
}
$parameters
[
$key
][
'name'
]
=
$tokens
[
$firstNonEmpty
][
'content'
];
$parameters
[
$key
][
'name_token'
]
=
$firstNonEmpty
;
$paramStart
=
(
$secondNonEmpty
+
1
);
}
}
}
$parameters
[
$key
][
'start'
]
=
$paramStart
;
$parameters
[
$key
][
'end'
]
=
$paramEnd
;
$parameters
[
$key
][
'raw'
]
=
\trim
(
GetTokensAsString
::
normal
(
$phpcsFile
,
$paramStart
,
$paramEnd
));
$parameters
[
$key
][
'clean'
]
=
\trim
(
GetTokensAsString
::
noComments
(
$phpcsFile
,
$paramStart
,
$paramEnd
));
// Check if there are more tokens before the closing parenthesis.
// Prevents function calls with trailing comma's from setting an extra parameter:
// `functionCall( $param1, $param2, );`.
$hasNextParam
=
$phpcsFile
->
findNext
(
Tokens
::
$emptyTokens
,
(
$nextComma
+
1
),
$closer
,
true
);
if
(
$hasNextParam
===
false
)
{
// Reached the end, so for the purpose of caching, this should be saved as if no limit was set.
$effectiveLimit
=
0
;
break
;
}
// Stop if there is a valid limit and the limit has been reached.
if
(
$effectiveLimit
!==
0
&&
$cnt
===
$effectiveLimit
)
{
break
;
}
// Prepare for the next parameter.
$paramStart
=
(
$nextComma
+
1
);
++
$cnt
;
}
if
(
$effectiveLimit
!==
0
&&
$cnt
===
$effectiveLimit
)
{
Cache
::
set
(
$phpcsFile
,
__METHOD__
,
"$stackPtr-$effectiveLimit"
,
$parameters
);
}
else
{
// Limit is 0 or total items is less than effective limit.
Cache
::
set
(
$phpcsFile
,
__METHOD__
,
"$stackPtr-0"
,
$parameters
);
}
return
$parameters
;
}
/**
* Get information on a specific parameter passed.
*
* See {@see PassedParameters::hasParameters()} for information on the supported constructs.
*
* @see PassedParameters::getParameterFromStack() For when the parameter stack of a function call is
* already retrieved.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position of function call name,
* language construct or array open token.
* @param int $paramOffset The 1-based index position of the parameter to retrieve.
* @param string|array<string> $paramNames Optional. Either the name of the target parameter
* to retrieve as a string or an array of names for the
* same target parameter.
* Only relevant for function calls.
* An arrays of names is supported to allow for functions
* for which the parameter names have undergone name
* changes over time.
* When specified, the name will take precedence over the
* offset.
* For PHP 8 support, it is STRONGLY recommended to
* always pass both the offset as well as the parameter
* name when examining function calls.
*
* @return array<string, int|string>|false Array with information on the parameter/array item at the specified
* offset, or with the specified name.
* Or `FALSE` if the specified parameter/array item is not found.
* See {@see PassedParameters::getParameters()} for the format of the
* returned (single-dimensional) array.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
* accepted types or doesn't exist.
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If a function call parameter is requested and
* the `$paramName` parameter is not passed.
*/
public
static
function
getParameter
(
File
$phpcsFile
,
$stackPtr
,
$paramOffset
,
$paramNames
=
[])
{
$tokens
=
$phpcsFile
->
getTokens
();
if
(
empty
(
$paramNames
)
===
true
)
{
$parameters
=
self
::
getParameters
(
$phpcsFile
,
$stackPtr
,
$paramOffset
);
}
else
{
$parameters
=
self
::
getParameters
(
$phpcsFile
,
$stackPtr
);
}
/*
* Non-function calls.
*/
if
(
isset
(
Collections
::
functionCallTokens
()[
$tokens
[
$stackPtr
][
'code'
]])
===
false
)
{
if
(
isset
(
$parameters
[
$paramOffset
])
===
true
)
{
return
$parameters
[
$paramOffset
];
}
return
false
;
}
/*
* Function calls.
*/
return
self
::
getParameterFromStack
(
$parameters
,
$paramOffset
,
$paramNames
);
}
/**
* Count the number of parameters which have been passed.
*
* See {@see PassedParameters::hasParameters()} for information on the supported constructs.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The position of function call name,
* language construct or array open token.
*
* @return int
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
* accepted types or doesn't exist.
*/
public
static
function
getParameterCount
(
File
$phpcsFile
,
$stackPtr
)
{
if
(
self
::
hasParameters
(
$phpcsFile
,
$stackPtr
)
===
false
)
{
return
0
;
}
return
\count
(
self
::
getParameters
(
$phpcsFile
,
$stackPtr
));
}
/**
* Get information on a specific function call parameter passed.
*
* This is an efficiency method to correctly handle positional versus named parameters
* for function calls when multiple parameters need to be examined.
*
* See {@see PassedParameters::hasParameters()} for information on the supported constructs.
*
* @since 1.0.0
*
* @param array<int|string, array<string, int|string>> $parameters The output of a previous call to
* {@see PassedParameters::getParameters()}.
* @param int $paramOffset The 1-based index position of the parameter
* to retrieve.
* @param string|array<string> $paramNames Either the name of the target parameter to retrieve
* as a string or an array of names for the same target
* parameter.
* An array of names is supported to allow for functions
* for which the parameter names have undergone name
* changes over time.
* The name will take precedence over the offset.
*
* @return array<string, int|string>|false Array with information on the parameter at the specified offset,
* or with the specified name.
* Or `FALSE` if the specified parameter is not found.
* See {@see PassedParameters::getParameters()} for the format of the
* returned (single-dimensional) array.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the `$paramNames` parameter is not passed
* and the requested parameter was not passed
* as a positional parameter in the function call
* being examined.
*/
public
static
function
getParameterFromStack
(
array
$parameters
,
$paramOffset
,
$paramNames
)
{
if
(
empty
(
$parameters
)
===
true
)
{
return
false
;
}
// First check for a named parameter.
if
(
empty
(
$paramNames
)
===
false
)
{
$paramNames
=
(
array
)
$paramNames
;
foreach
(
$paramNames
as
$name
)
{
// Note: parameter names are case-sensitive!.
if
(
isset
(
$parameters
[
$name
])
===
true
)
{
return
$parameters
[
$name
];
}
}
}
// Next check for positional parameters.
if
(
isset
(
$parameters
[
$paramOffset
])
===
true
&&
isset
(
$parameters
[
$paramOffset
][
'name'
])
===
false
)
{
return
$parameters
[
$paramOffset
];
}
if
(
empty
(
$paramNames
)
===
true
)
{
throw
new
RuntimeException
(
'To allow for support for PHP 8 named parameters, the $paramNames parameter must be passed.'
);
}
return
false
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Oct 3, 22:37 (1 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
18/ff/258f099ab943ca0ef0b19a683734
Default Alt Text
PassedParameters.php (24 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment