Completed
Push — master ( 37cd8c...de6a88 )
by Alexander
02:04
created

FunctionDataCollector::collectData()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 53
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 6.0016

Importance

Changes 0
Metric Value
dl 0
loc 53
ccs 27
cts 28
cp 0.9643
rs 8.7155
c 0
b 0
f 0
cc 6
eloc 28
nc 10
nop 2
crap 6.0016

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of the Code-Insight library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/console-helpers/code-insight
9
 */
10
11
namespace ConsoleHelpers\CodeInsight\KnowledgeBase\DataCollector;
12
13
14
use Go\ParserReflection\ReflectionFileNamespace;
15
16
class FunctionDataCollector extends AbstractDataCollector
17
{
18
19
	/**
20
	 * Collect data from a namespace.
21
	 *
22
	 * @param integer                 $file_id   File id.
23
	 * @param ReflectionFileNamespace $namespace Namespace.
24
	 *
25
	 * @return void
26
	 */
27 4
	public function collectData($file_id, ReflectionFileNamespace $namespace)
28
	{
29 4
		$sql = 'SELECT Name, Id
30
				FROM Functions
31
				WHERE FileId = :file_id';
32 4
		$old_functions = $this->db->fetchPairs($sql, array(
33 4
			'file_id' => $file_id,
34
		));
35
36 4
		$insert_sql = '	INSERT INTO Functions (FileId, Name, ParameterCount, RequiredParameterCount, IsVariadic, ReturnsReference, HasReturnType, ReturnType)
37
						VALUES (:file_id, :name, :parameter_count, :required_parameter_count, :is_variadic, :returns_reference, :has_return_type, :return_type)';
38 4
		$update_sql = '	UPDATE Functions
39
						SET	ParameterCount = :parameter_count,
40
							RequiredParameterCount = :required_parameter_count,
41
							IsVariadic = :is_variadic,
42
							ReturnsReference = :returns_reference,
43
							ReturnType = :return_type,
44
							HasReturnType = :has_return_type
45
						WHERE FileId = :file_id AND Name = :name';
46
47 4
		$new_functions = array();
48
49 4
		foreach ( $namespace->getFunctions() as $function ) {
50 4
			$function_name = $function->getName();
51 4
			$new_functions[] = $function_name;
52
53 4
			$has_return_type = $function->hasReturnType();
54 4
			$return_type = $has_return_type ? (string)$function->getReturnType() : null;
55
56 4
			$this->db->perform(
57 4
				isset($old_functions[$function_name]) ? $update_sql : $insert_sql,
58
				array(
59 4
					'file_id' => $file_id,
60 4
					'name' => $function_name,
61 4
					'parameter_count' => $function->getNumberOfParameters(),
62 4
					'required_parameter_count' => $function->getNumberOfRequiredParameters(),
63 4
					'is_variadic' => (int)$function->isVariadic(),
64 4
					'returns_reference' => (int)$function->returnsReference(),
65 4
					'has_return_type' => (int)$has_return_type,
66 4
					'return_type' => $return_type,
67
				)
68
			);
69
70 4
			$function_id = isset($old_functions[$function_name]) ? $old_functions[$function_name] : $this->db->lastInsertId();
71 4
			$this->processFunctionParameters($function_id, $function);
72
		}
73
74 4
		$delete_functions = array_diff(array_keys($old_functions), $new_functions);
75
76 4
		if ( $delete_functions ) {
77
			$this->deleteFunctions($file_id, $delete_functions);
78
		}
79 4
	}
80
81
	/**
82
	 * Deletes functions.
83
	 *
84
	 * @param integer $file_id   File ID.
85
	 * @param array   $functions Methods.
86
	 *
87
	 * @return void
88
	 */
89 1
	protected function deleteFunctions($file_id, array $functions)
90
	{
91 1
		if ( $functions ) {
92
			// Delete only given functions.
93
			$sql = 'SELECT Id
94
					FROM Functions
95
					WHERE FileId = :file_id AND Name IN (:names)';
96
			$function_ids = $this->db->fetchCol($sql, array(
97
				'file_id' => $file_id,
98
				'names' => $functions,
99
			));
100
		}
101
		else {
102
			// Delete all functions in a file.
103 1
			$sql = 'SELECT Id
104
					FROM Functions
105
					WHERE FileId = :file_id';
106 1
			$function_ids = $this->db->fetchCol($sql, array(
107 1
				'file_id' => $file_id,
108
			));
109
		}
110
111
		// @codeCoverageIgnoreStart
112
		if ( !$function_ids ) {
113
			return;
114
		}
115
		// @codeCoverageIgnoreEnd
116
117 1
		$sql = 'DELETE FROM Functions WHERE Id IN (:function_ids)';
118 1
		$this->db->perform($sql, array('function_ids' => $function_ids));
119
120 1
		$sql = 'DELETE FROM FunctionParameters WHERE FunctionId IN (:function_ids)';
121 1
		$this->db->perform($sql, array('function_ids' => $function_ids));
122 1
	}
123
124
	/**
125
	 * Processes function parameters.
126
	 *
127
	 * @param integer             $function_id Function ID.
128
	 * @param \ReflectionFunction $function    Function.
129
	 *
130
	 * @return void
131
	 */
132 4
	protected function processFunctionParameters($function_id, \ReflectionFunction $function)
133
	{
134 4
		$sql = 'SELECT Name
135
				FROM FunctionParameters
136
				WHERE FunctionId = :function_id';
137 4
		$old_parameters = $this->db->fetchCol($sql, array(
138 4
			'function_id' => $function_id,
139
		));
140
141 4
		$insert_sql = '	INSERT INTO FunctionParameters (FunctionId, Name, Position, TypeClass, HasType, TypeName, AllowsNull, IsArray, IsCallable, IsOptional, IsVariadic, CanBePassedByValue, IsPassedByReference, HasDefaultValue, DefaultValue, DefaultConstant)
142
						VALUES (:function_id, :name, :position, :type_class, :has_type, :type_name, :allows_null, :is_array, :is_callable, :is_optional, :is_variadic, :can_be_passed_by_value, :is_passed_by_reference, :has_default_value, :default_value, :default_constant)';
143 4
		$update_sql = '	UPDATE FunctionParameters
144
						SET	Position = :position,
145
							TypeClass = :type_class,
146
							HasType = :has_type,
147
							TypeName = :type_name,
148
							AllowsNull = :allows_null,
149
							IsArray = :is_array,
150
							IsCallable = :is_callable,
151
							IsOptional = :is_optional,
152
							IsVariadic = :is_variadic,
153
							CanBePassedByValue = :can_be_passed_by_value,
154
							IsPassedByReference = :is_passed_by_reference,
155
							HasDefaultValue = :has_default_value,
156
							DefaultValue = :default_value,
157
							DefaultConstant = :default_constant
158
						WHERE FunctionId = :function_id AND Name = :name';
159
160 4
		$new_parameters = array();
161
162 4
		foreach ( $function->getParameters() as $position => $parameter ) {
163 4
			$parameter_name = $parameter->getName();
164 4
			$new_parameters[] = $parameter_name;
165
166 4
			$type_class = $parameter->getClass();
167 4
			$type_class = $type_class ? $type_class->getName() : null;
168
169 4
			$has_type = $parameter->hasType();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method hasType() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
170 4
			$type_name = $has_type ? (string)$parameter->getType() : null;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method getType() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
171
172 4
			$has_default_value = $parameter->isDefaultValueAvailable();
173 4
			$default_value_is_constant = $has_default_value ? $parameter->isDefaultValueConstant() : false;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method isDefaultValueConstant() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
174
175 4
			$this->db->perform(
176 4
				in_array($parameter_name, $old_parameters) ? $update_sql : $insert_sql,
177
				array(
178 4
					'function_id' => $function_id,
179 4
					'name' => $parameter_name,
180 4
					'position' => $position,
181 4
					'type_class' => $type_class,
182 4
					'has_type' => (int)$has_type,
183 4
					'type_name' => $type_name,
184 4
					'allows_null' => (int)$parameter->allowsNull(),
185 4
					'is_array' => (int)$parameter->isArray(),
186 4
					'is_callable' => (int)$parameter->isCallable(),
187 4
					'is_optional' => (int)$parameter->isOptional(),
188 4
					'is_variadic' => (int)$parameter->isVariadic(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method isVariadic() does only exist in the following sub-classes of ReflectionParameter: Go\ParserReflection\ReflectionParameter. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
189 4
					'can_be_passed_by_value' => (int)$parameter->canBePassedByValue(),
190 4
					'is_passed_by_reference' => (int)$parameter->isPassedByReference(),
191 4
					'has_default_value' => (int)$has_default_value,
192 4
					'default_value' => $has_default_value ? json_encode($parameter->getDefaultValue()) : null,
193 4
					'default_constant' => $default_value_is_constant ? $parameter->getDefaultValueConstantName() : null,
194
				)
195
			);
196
		}
197
198 4
		$delete_parameters = array_diff($old_parameters, $new_parameters);
199
200 4
		if ( $delete_parameters ) {
201 1
			$sql = 'DELETE FROM FunctionParameters
202
					WHERE FunctionId = :function_id AND Name IN (:names)';
203 1
			$this->db->perform($sql, array(
204 1
				'function_id' => $function_id,
205 1
				'names' => $delete_parameters,
206
			));
207
		}
208 4
	}
209
210
	/**
211
	 * Delete previously collected data for a files.
212
	 *
213
	 * @param array $file_ids File IDs.
214
	 *
215
	 * @return void
216
	 */
217 1
	public function deleteData(array $file_ids)
218
	{
219 1
		foreach ( $file_ids as $file_id ) {
220 1
			$this->deleteFunctions($file_id, array());
221
		}
222 1
	}
223
224
	/**
225
	 * Returns statistics about the code.
226
	 *
227
	 * @return array
228
	 */
229 1
	public function getStatistics()
230
	{
231 1
		$sql = 'SELECT COUNT(*)
232
				FROM Functions';
233 1
		$function_count = $this->db->fetchValue($sql);
234
235
		return array(
236 1
			'Functions' => $function_count,
237
		);
238
	}
239
240
}
241