123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- <?php
- /*
- * This file is part of the PHP CS utility.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- namespace Symfony\CS\Fixer\Contrib;
- use Symfony\CS\AbstractFixer;
- use Symfony\CS\Tokenizer\Token;
- use Symfony\CS\Tokenizer\Tokens;
- /**
- * @author Matteo Beccati <matteo@beccati.com>
- */
- class Php4ConstructorFixer extends AbstractFixer
- {
- /**
- * {@inheritdoc}
- */
- public function fix(\SplFileInfo $file, Tokens $tokens)
- {
- $classes = array_keys($tokens->findGivenKind(T_CLASS));
- $numClasses = count($classes);
- for ($i = 0; $i < $numClasses; ++$i) {
- $index = $classes[$i];
- // is it inside a namespace?
- $nspIndex = $tokens->getPrevTokenOfKind($index, array(array(T_NAMESPACE, 'namespace')));
- if (null !== $nspIndex) {
- $nspIndex = $tokens->getNextMeaningfulToken($nspIndex);
- // make sure it's not the global namespace, as PHP4 constructors are allowed in there
- if (!$tokens[$nspIndex]->equals('{')) {
- // unless it's the global namespace, the index currently points to the name
- $nspIndex = $tokens->getNextMeaningfulToken($nspIndex);
- if ($tokens[$nspIndex]->equals(';')) {
- // the class is inside a (non-block) namespace, no PHP4-code should be in there
- break;
- }
- // the index points to the { of a block-namespace
- $nspEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nspIndex);
- if ($index < $nspEnd) {
- // the class is inside a block namespace, skip other classes that might be in it
- for ($j = $i + 1; $j < $numClasses; ++$j) {
- if ($classes[$j] < $nspEnd) {
- ++$i;
- }
- }
- // and continue checking the classes that might follow
- continue;
- }
- }
- }
- $classNameIndex = $tokens->getNextMeaningfulToken($index);
- $className = $tokens[$classNameIndex]->getContent();
- $classStart = $tokens->getNextTokenOfKind($classNameIndex, array('{'));
- $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart);
- $this->fixConstructor($tokens, $className, $classStart, $classEnd);
- $this->fixParent($tokens, $classStart, $classEnd);
- }
- }
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'php4_constructor';
- }
- /**
- * {@inheritdoc}
- */
- public function getDescription()
- {
- return 'Convert PHP4-style constructors to __construct. Warning! This could change code behavior.';
- }
- /**
- * Fix constructor within a class, if possible.
- *
- * @param Tokens $tokens the Tokens instance
- * @param string $className the class name
- * @param int $classStart the class start index
- * @param int $classEnd the class end index
- */
- private function fixConstructor(Tokens $tokens, $className, $classStart, $classEnd)
- {
- $php4 = $this->findFunction($tokens, $className, $classStart, $classEnd);
- if (null === $php4) {
- // no PHP4-constructor!
- return;
- }
- if (!empty($php4['modifiers'][T_ABSTRACT]) || !empty($php4['modifiers'][T_STATIC])) {
- // PHP4 constructor can't be abstract or static
- return;
- }
- $php5 = $this->findFunction($tokens, '__construct', $classStart, $classEnd);
- if (null === $php5) {
- // no PHP5-constructor, we can rename the old one to __construct
- $tokens[$php4['nameIndex']]->setContent('__construct');
- return;
- }
- // does the PHP4-constructor only call $this->__construct($args, ...)?
- list($seq, $case) = $this->getWrapperMethodSequence($tokens, '__construct', $php4['startIndex'], $php4['bodyIndex']);
- if (null !== $tokens->findSequence($seq, $php4['bodyIndex'] - 1, $php4['endIndex'], $case)) {
- // good, delete it!
- for ($i = $php4['startIndex']; $i <= $php4['endIndex']; ++$i) {
- $tokens[$i]->clear();
- }
- return;
- }
- // does __construct only call the PHP4-constructor (with the same args)?
- list($seq, $case) = $this->getWrapperMethodSequence($tokens, $className, $php4['startIndex'], $php4['bodyIndex']);
- if (null !== $tokens->findSequence($seq, $php5['bodyIndex'] - 1, $php5['endIndex'], $case)) {
- // that was a weird choice, but we can safely delete it and...
- for ($i = $php5['startIndex']; $i <= $php5['endIndex']; ++$i) {
- $tokens[$i]->clear();
- }
- // rename the PHP4 one to __construct
- $tokens[$php4['nameIndex']]->setContent('__construct');
- }
- }
- /**
- * Fix calls to the parent constructor within a class.
- *
- * @param Tokens $tokens the Tokens instance
- * @param int $classStart the class start index
- * @param int $classEnd the class end index
- */
- private function fixParent(Tokens $tokens, $classStart, $classEnd)
- {
- // check calls to the parent constructor
- foreach ($tokens->findGivenKind(T_EXTENDS) as $index => $token) {
- $parentIndex = $tokens->getNextMeaningfulToken($index);
- $parentClass = $tokens[$parentIndex]->getContent();
- // using parent::ParentClassName()
- $parentSeq = $tokens->findSequence(array(
- array(T_STRING, 'parent'),
- array(T_DOUBLE_COLON),
- array(T_STRING, $parentClass),
- '(',
- ), $classStart, $classEnd, array(false, true, false, true));
- if (null !== $parentSeq) {
- // we only need indexes
- $parentSeq = array_keys($parentSeq);
- // replace method name with __construct
- $tokens[$parentSeq[2]]->setContent('__construct');
- }
- // using $this->ParentClassName()
- $parentSeq = $tokens->findSequence(array(
- array(T_VARIABLE, '$this'),
- array(T_OBJECT_OPERATOR),
- array(T_STRING, $parentClass),
- '(',
- ), $classStart, $classEnd, array(2 => false));
- if (null !== $parentSeq) {
- // we only need indexes
- $parentSeq = array_keys($parentSeq);
- // replace call with parent::__construct()
- $tokens[$parentSeq[0]] = new Token(array(
- T_STRING,
- 'parent',
- ));
- $tokens[$parentSeq[1]] = new Token(array(
- T_DOUBLE_COLON,
- '::',
- ));
- $tokens[$parentSeq[2]]->setContent('__construct');
- }
- }
- }
- /**
- * Generate the sequence of tokens necessary for the body of a wrapper method that simply
- * calls $this->{$method}( [args...] ) with the same arguments as its own signature.
- *
- * @param Tokens $tokens the Tokens instance
- * @param string $method the wrapped method name
- * @param int $startIndex function/method start index
- * @param int $bodyIndex function/method body index
- *
- * @return array an array containing the sequence and case sensitiveness [ 0 => $seq, 1 => $case ]
- */
- private function getWrapperMethodSequence(Tokens $tokens, $method, $startIndex, $bodyIndex)
- {
- // initialise sequence as { $this->{$method}(
- $seq = array(
- '{',
- array(T_VARIABLE, '$this'),
- array(T_OBJECT_OPERATOR),
- array(T_STRING, $method),
- '(',
- );
- $case = array(3 => false);
- // parse method parameters, if any
- $index = $startIndex;
- while (true) {
- // find the next variable name
- $index = $tokens->getNextTokenOfKind($index, array(array(T_VARIABLE)));
- if (null === $index || $index >= $bodyIndex) {
- // we've reached the body already
- break;
- }
- // append a comma if it's not the first variable
- if (count($seq) > 5) {
- $seq[] = ',';
- }
- // append variable name to the sequence
- $seq[] = array(T_VARIABLE, $tokens[$index]->getContent());
- }
- // almost done, close the sequence with ); }
- $seq[] = ')';
- $seq[] = ';';
- $seq[] = '}';
- return array($seq, $case);
- }
- /**
- * Find a function or method matching a given name within certain bounds.
- *
- * @param Tokens $tokens the Tokens instance
- * @param string $name the function/Method name
- * @param int $startIndex the search start index
- * @param int $endIndex the search end index
- *
- * @return array|null An associative array, if a match is found:
- *
- * - nameIndex (int): The index of the function/method name.
- * - startIndex (int): The index of the function/method start.
- * - endIndex (int): The index of the function/method end.
- * - bodyIndex (int): The index of the function/method body.
- * - modifiers (array): The modifiers as array keys and their index as
- * the values, e.g. array(T_PUBLIC => 10).
- */
- private function findFunction(Tokens $tokens, $name, $startIndex, $endIndex)
- {
- $function = $tokens->findSequence(array(
- array(T_FUNCTION),
- array(T_STRING, $name),
- '(',
- ), $startIndex, $endIndex, false);
- if (null === $function) {
- return;
- }
- // keep only the indexes
- $function = array_keys($function);
- // find previous block, saving method modifiers for later use
- $possibleModifiers = array(T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_ABSTRACT, T_FINAL);
- $modifiers = array();
- $prevBlock = $tokens->getPrevMeaningfulToken($function[0]);
- while (null !== $prevBlock && $tokens[$prevBlock]->isGivenKind($possibleModifiers)) {
- $modifiers[$tokens[$prevBlock]->getId()] = $prevBlock;
- $prevBlock = $tokens->getPrevMeaningfulToken($prevBlock);
- }
- if (isset($modifiers[T_ABSTRACT])) {
- // abstract methods have no body
- $bodyStart = null;
- $funcEnd = $tokens->getNextTokenOfKind($function[2], array(';'));
- } else {
- // find method body start and the end of the function definition
- $bodyStart = $tokens->getNextTokenOfKind($function[2], array('{'));
- $funcEnd = $bodyStart !== null ? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $bodyStart) : null;
- }
- return array(
- 'nameIndex' => $function[1],
- 'startIndex' => $prevBlock + 1,
- 'endIndex' => $funcEnd,
- 'bodyIndex' => $bodyStart,
- 'modifiers' => $modifiers,
- );
- }
- }
|