Complex classes like Twig_NodeVisitor_Optimizer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Twig_NodeVisitor_Optimizer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
22 | class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface |
||
23 | { |
||
24 | const OPTIMIZE_ALL = -1; |
||
25 | const OPTIMIZE_NONE = 0; |
||
26 | const OPTIMIZE_FOR = 2; |
||
27 | const OPTIMIZE_RAW_FILTER = 4; |
||
28 | const OPTIMIZE_VAR_ACCESS = 8; |
||
29 | |||
30 | protected $loops = array(); |
||
31 | protected $loopsTargets = array(); |
||
32 | protected $optimizers; |
||
33 | protected $prependedNodes = array(); |
||
34 | protected $inABody = false; |
||
35 | |||
36 | /** |
||
37 | * Constructor. |
||
38 | * |
||
39 | * @param int $optimizers The optimizer mode |
||
40 | */ |
||
41 | public function __construct($optimizers = -1) |
||
42 | { |
||
43 | if (!is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) { |
||
44 | throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); |
||
45 | } |
||
46 | |||
47 | $this->optimizers = $optimizers; |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * {@inheritdoc} |
||
52 | */ |
||
53 | public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) |
||
54 | { |
||
55 | if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { |
||
56 | $this->enterOptimizeFor($node, $env); |
||
57 | } |
||
58 | |||
59 | if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { |
||
60 | if ($this->inABody) { |
||
61 | if (!$node instanceof Twig_Node_Expression) { |
||
62 | if (get_class($node) !== 'Twig_Node') { |
||
63 | array_unshift($this->prependedNodes, array()); |
||
64 | } |
||
65 | } else { |
||
66 | $node = $this->optimizeVariables($node, $env); |
||
67 | } |
||
68 | } elseif ($node instanceof Twig_Node_Body) { |
||
69 | $this->inABody = true; |
||
70 | } |
||
71 | } |
||
72 | |||
73 | return $node; |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * {@inheritdoc} |
||
78 | */ |
||
79 | public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) |
||
80 | { |
||
81 | $expression = $node instanceof Twig_Node_Expression; |
||
82 | |||
83 | if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { |
||
84 | $this->leaveOptimizeFor($node, $env); |
||
85 | } |
||
86 | |||
87 | if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { |
||
88 | $node = $this->optimizeRawFilter($node, $env); |
||
89 | } |
||
90 | |||
91 | $node = $this->optimizePrintNode($node, $env); |
||
92 | |||
93 | if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { |
||
94 | if ($node instanceof Twig_Node_Body) { |
||
95 | $this->inABody = false; |
||
96 | } elseif ($this->inABody) { |
||
97 | if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) { |
||
98 | $nodes = array(); |
||
99 | foreach (array_unique($prependedNodes) as $name) { |
||
100 | $nodes[] = new Twig_Node_SetTemp($name, $node->getLine()); |
||
101 | } |
||
102 | |||
103 | $nodes[] = $node; |
||
104 | $node = new Twig_Node($nodes); |
||
105 | } |
||
106 | } |
||
107 | } |
||
108 | |||
109 | return $node; |
||
110 | } |
||
111 | |||
112 | protected function optimizeVariables(Twig_NodeInterface $node, Twig_Environment $env) |
||
|
|||
113 | { |
||
114 | if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) { |
||
115 | $this->prependedNodes[0][] = $node->getAttribute('name'); |
||
116 | |||
117 | return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine()); |
||
118 | } |
||
119 | |||
120 | return $node; |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * Optimizes print nodes. |
||
125 | * |
||
126 | * It replaces: |
||
127 | * |
||
128 | * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" |
||
129 | * |
||
130 | * @param Twig_NodeInterface $node A Node |
||
131 | * @param Twig_Environment $env The current Twig environment |
||
132 | */ |
||
133 | protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment $env) |
||
150 | |||
151 | /** |
||
152 | * Removes "raw" filters. |
||
153 | * |
||
154 | * @param Twig_NodeInterface $node A Node |
||
155 | * @param Twig_Environment $env The current Twig environment |
||
156 | */ |
||
157 | protected function optimizeRawFilter(Twig_NodeInterface $node, Twig_Environment $env) |
||
158 | { |
||
159 | if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) { |
||
160 | return $node->getNode('node'); |
||
161 | } |
||
162 | |||
163 | return $node; |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Optimizes "for" tag by removing the "loop" variable creation whenever possible. |
||
168 | * |
||
169 | * @param Twig_NodeInterface $node A Node |
||
170 | * @param Twig_Environment $env The current Twig environment |
||
171 | */ |
||
172 | protected function enterOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env) |
||
173 | { |
||
174 | if ($node instanceof Twig_Node_For) { |
||
175 | // disable the loop variable by default |
||
176 | $node->setAttribute('with_loop', false); |
||
177 | array_unshift($this->loops, $node); |
||
178 | array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name')); |
||
179 | array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name')); |
||
180 | } elseif (!$this->loops) { |
||
181 | // we are outside a loop |
||
182 | return; |
||
183 | } |
||
184 | |||
185 | // when do we need to add the loop variable back? |
||
186 | |||
187 | // the loop variable is referenced for the current loop |
||
188 | elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) { |
||
189 | $node->setAttribute('always_defined', true); |
||
190 | $this->addLoopToCurrent(); |
||
191 | } |
||
192 | |||
193 | // optimize access to loop targets |
||
194 | elseif ($node instanceof Twig_Node_Expression_Name && in_array($node->getAttribute('name'), $this->loopsTargets)) { |
||
195 | $node->setAttribute('always_defined', true); |
||
196 | } |
||
197 | |||
198 | // block reference |
||
199 | elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) { |
||
200 | $this->addLoopToCurrent(); |
||
201 | } |
||
202 | |||
203 | // include without the only attribute |
||
204 | elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) { |
||
205 | $this->addLoopToAll(); |
||
206 | } |
||
207 | |||
208 | // include function without the with_context=false parameter |
||
209 | elseif ($node instanceof Twig_Node_Expression_Function |
||
210 | && 'include' === $node->getAttribute('name') |
||
211 | && (!$node->getNode('arguments')->hasNode('with_context') |
||
212 | || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') |
||
213 | ) |
||
214 | ) { |
||
215 | $this->addLoopToAll(); |
||
216 | } |
||
217 | |||
218 | // the loop variable is referenced via an attribute |
||
219 | elseif ($node instanceof Twig_Node_Expression_GetAttr |
||
220 | && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant |
||
221 | || 'parent' === $node->getNode('attribute')->getAttribute('value') |
||
222 | ) |
||
223 | && (true === $this->loops[0]->getAttribute('with_loop') |
||
224 | || ($node->getNode('node') instanceof Twig_Node_Expression_Name |
||
225 | && 'loop' === $node->getNode('node')->getAttribute('name') |
||
226 | ) |
||
227 | ) |
||
228 | ) { |
||
229 | $this->addLoopToAll(); |
||
230 | } |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * Optimizes "for" tag by removing the "loop" variable creation whenever possible. |
||
235 | * |
||
236 | * @param Twig_NodeInterface $node A Node |
||
237 | * @param Twig_Environment $env The current Twig environment |
||
238 | */ |
||
239 | protected function leaveOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env) |
||
240 | { |
||
241 | if ($node instanceof Twig_Node_For) { |
||
242 | array_shift($this->loops); |
||
243 | array_shift($this->loopsTargets); |
||
244 | array_shift($this->loopsTargets); |
||
245 | } |
||
246 | } |
||
247 | |||
248 | protected function addLoopToCurrent() |
||
252 | |||
253 | protected function addLoopToAll() |
||
254 | { |
||
255 | foreach ($this->loops as $loop) { |
||
256 | $loop->setAttribute('with_loop', true); |
||
257 | } |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * {@inheritdoc} |
||
262 | */ |
||
263 | public function getPriority() |
||
267 | } |
||
268 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.