Complex classes like Twig_Tests_TemplateTest 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_Tests_TemplateTest, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 11 | class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase |
||
| 12 | { |
||
| 13 | /** |
||
| 14 | * @dataProvider getAttributeExceptions |
||
| 15 | */ |
||
| 16 | public function testGetAttributeExceptions($template, $message, $useExt) |
||
| 17 | { |
||
| 18 | $name = 'index_'.($useExt ? 1 : 0); |
||
| 19 | $templates = array( |
||
| 20 | $name => $template.$useExt, // appending $useExt makes the template content unique |
||
| 21 | ); |
||
| 22 | |||
| 23 | $env = new Twig_Environment(new Twig_Loader_Array($templates), array('strict_variables' => true)); |
||
| 24 | if (!$useExt) { |
||
| 25 | $env->addNodeVisitor(new CExtDisablingNodeVisitor()); |
||
| 26 | } |
||
| 27 | $template = $env->loadTemplate($name); |
||
| 28 | |||
| 29 | $context = array( |
||
| 30 | 'string' => 'foo', |
||
| 31 | 'empty_array' => array(), |
||
| 32 | 'array' => array('foo' => 'foo'), |
||
| 33 | 'array_access' => new Twig_TemplateArrayAccessObject(), |
||
| 34 | 'magic_exception' => new Twig_TemplateMagicPropertyObjectWithException(), |
||
| 35 | 'object' => new stdClass(), |
||
| 36 | ); |
||
| 37 | |||
| 38 | try { |
||
| 39 | $template->render($context); |
||
| 40 | $this->fail('Accessing an invalid attribute should throw an exception.'); |
||
| 41 | } catch (Twig_Error_Runtime $e) { |
||
| 42 | $this->assertSame(sprintf($message, $name), $e->getMessage()); |
||
| 43 | } |
||
| 44 | } |
||
| 45 | |||
| 46 | public function getAttributeExceptions() |
||
| 47 | { |
||
| 48 | $tests = array( |
||
| 49 | array('{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1', false), |
||
| 50 | array('{{ empty_array["a"] }}', 'Key "a" does not exist as the array is empty in "%s" at line 1', false), |
||
| 51 | array('{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1', false), |
||
| 52 | array('{{ array_access["a"] }}', 'Key "a" in object with ArrayAccess of class "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false), |
||
| 53 | array('{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1', false), |
||
| 54 | array('{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1', false), |
||
| 55 | array('{{ empty_array.a }}', 'Key "a" does not exist as the array is empty in "%s" at line 1', false), |
||
| 56 | array('{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1', false), |
||
| 57 | array('{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1', false), |
||
| 58 | array('{{ array_access.a }}', 'Method "a" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false), |
||
| 59 | array('{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ _self.foo(array_access) }}', 'Method "missing_method" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false), |
||
| 60 | array('{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.', false), |
||
| 61 | array('{{ object["a"] }}', 'Impossible to access a key "a" on an object of class "stdClass" that does not implement ArrayAccess interface in "%s" at line 1', false), |
||
| 62 | ); |
||
| 63 | |||
| 64 | if (function_exists('twig_template_get_attributes')) { |
||
| 65 | foreach (array_slice($tests, 0) as $test) { |
||
| 66 | $test[2] = true; |
||
| 67 | $tests[] = $test; |
||
| 68 | } |
||
| 69 | } |
||
| 70 | |||
| 71 | return $tests; |
||
| 72 | } |
||
| 73 | |||
| 74 | /** |
||
| 75 | * @dataProvider getGetAttributeWithSandbox |
||
| 76 | */ |
||
| 77 | public function testGetAttributeWithSandbox($object, $item, $allowed, $useExt) |
||
| 78 | { |
||
| 79 | $twig = new Twig_Environment(); |
||
| 80 | $policy = new Twig_Sandbox_SecurityPolicy(array(), array(), array(/*method*/), array(/*prop*/), array()); |
||
| 81 | $twig->addExtension(new Twig_Extension_Sandbox($policy, !$allowed)); |
||
| 82 | $template = new Twig_TemplateTest($twig, $useExt); |
||
| 83 | |||
| 84 | try { |
||
| 85 | $template->getAttribute($object, $item, array(), 'any'); |
||
| 86 | |||
| 87 | if (!$allowed) { |
||
| 88 | $this->fail(); |
||
| 89 | } |
||
| 90 | } catch (Twig_Sandbox_SecurityError $e) { |
||
| 91 | if ($allowed) { |
||
| 92 | $this->fail(); |
||
| 93 | } |
||
| 94 | |||
| 95 | $this->assertContains('is not allowed', $e->getMessage()); |
||
| 96 | } |
||
| 97 | } |
||
| 98 | |||
| 99 | public function getGetAttributeWithSandbox() |
||
| 100 | { |
||
| 101 | $tests = array( |
||
| 102 | array(new Twig_TemplatePropertyObject(), 'defined', false, false), |
||
| 103 | array(new Twig_TemplatePropertyObject(), 'defined', true, false), |
||
| 104 | array(new Twig_TemplateMethodObject(), 'defined', false, false), |
||
| 105 | array(new Twig_TemplateMethodObject(), 'defined', true, false), |
||
| 106 | ); |
||
| 107 | |||
| 108 | if (function_exists('twig_template_get_attributes')) { |
||
| 109 | foreach (array_slice($tests, 0) as $test) { |
||
| 110 | $test[3] = true; |
||
| 111 | $tests[] = $test; |
||
| 112 | } |
||
| 113 | } |
||
| 114 | |||
| 115 | return $tests; |
||
| 116 | } |
||
| 117 | |||
| 118 | /** |
||
| 119 | * @dataProvider getGetAttributeWithTemplateAsObject |
||
| 120 | */ |
||
| 121 | public function testGetAttributeWithTemplateAsObject($useExt) |
||
| 122 | { |
||
| 123 | $template = new Twig_TemplateTest(new Twig_Environment(), $useExt); |
||
| 124 | $template1 = new Twig_TemplateTest(new Twig_Environment(), false); |
||
| 125 | |||
| 126 | $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'string')); |
||
| 127 | $this->assertEquals('some_string', $template->getAttribute($template1, 'string')); |
||
| 128 | |||
| 129 | $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'true')); |
||
| 130 | $this->assertEquals('1', $template->getAttribute($template1, 'true')); |
||
| 131 | |||
| 132 | $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'zero')); |
||
| 133 | $this->assertEquals('0', $template->getAttribute($template1, 'zero')); |
||
| 134 | |||
| 135 | $this->assertNotInstanceof('Twig_Markup', $template->getAttribute($template1, 'empty')); |
||
| 136 | $this->assertSame('', $template->getAttribute($template1, 'empty')); |
||
| 137 | } |
||
| 138 | |||
| 139 | public function getGetAttributeWithTemplateAsObject() |
||
| 140 | { |
||
| 141 | $bools = array( |
||
| 142 | array(false), |
||
| 143 | ); |
||
| 144 | |||
| 145 | if (function_exists('twig_template_get_attributes')) { |
||
| 146 | $bools[] = array(true); |
||
| 147 | } |
||
| 148 | |||
| 149 | return $bools; |
||
| 150 | } |
||
| 151 | |||
| 152 | /** |
||
| 153 | * @dataProvider getTestsDependingOnExtensionAvailability |
||
| 154 | */ |
||
| 155 | public function testGetAttributeOnArrayWithConfusableKey($useExt = false) |
||
| 156 | { |
||
| 157 | $template = new Twig_TemplateTest( |
||
| 158 | new Twig_Environment(), |
||
| 159 | $useExt |
||
| 160 | ); |
||
| 161 | |||
| 162 | $array = array('Zero', 'One', -1 => 'MinusOne', '' => 'EmptyString', '1.5' => 'FloatButString', '01' => 'IntegerButStringWithLeadingZeros'); |
||
| 163 | |||
| 164 | $this->assertSame('Zero', $array[false]); |
||
| 165 | $this->assertSame('One', $array[true]); |
||
| 166 | $this->assertSame('One', $array[1.5]); |
||
| 167 | $this->assertSame('One', $array['1']); |
||
| 168 | $this->assertSame('MinusOne', $array[-1.5]); |
||
| 169 | $this->assertSame('FloatButString', $array['1.5']); |
||
| 170 | $this->assertSame('IntegerButStringWithLeadingZeros', $array['01']); |
||
| 171 | $this->assertSame('EmptyString', $array[null]); |
||
| 172 | |||
| 173 | $this->assertSame('Zero', $template->getAttribute($array, false), 'false is treated as 0 when accessing an array (equals PHP behavior)'); |
||
| 174 | $this->assertSame('One', $template->getAttribute($array, true), 'true is treated as 1 when accessing an array (equals PHP behavior)'); |
||
| 175 | $this->assertSame('One', $template->getAttribute($array, 1.5), 'float is casted to int when accessing an array (equals PHP behavior)'); |
||
| 176 | $this->assertSame('One', $template->getAttribute($array, '1'), '"1" is treated as integer 1 when accessing an array (equals PHP behavior)'); |
||
| 177 | $this->assertSame('MinusOne', $template->getAttribute($array, -1.5), 'negative float is casted to int when accessing an array (equals PHP behavior)'); |
||
| 178 | $this->assertSame('FloatButString', $template->getAttribute($array, '1.5'), '"1.5" is treated as-is when accessing an array (equals PHP behavior)'); |
||
| 179 | $this->assertSame('IntegerButStringWithLeadingZeros', $template->getAttribute($array, '01'), '"01" is treated as-is when accessing an array (equals PHP behavior)'); |
||
| 180 | $this->assertSame('EmptyString', $template->getAttribute($array, null), 'null is treated as "" when accessing an array (equals PHP behavior)'); |
||
| 181 | } |
||
| 182 | |||
| 183 | public function getTestsDependingOnExtensionAvailability() |
||
| 184 | { |
||
| 185 | if (function_exists('twig_template_get_attributes')) { |
||
| 186 | return array(array(false), array(true)); |
||
| 187 | } |
||
| 188 | |||
| 189 | return array(array(false)); |
||
| 190 | } |
||
| 191 | |||
| 192 | /** |
||
| 193 | * @dataProvider getGetAttributeTests |
||
| 194 | */ |
||
| 195 | public function testGetAttribute($defined, $value, $object, $item, $arguments, $type, $useExt = false) |
||
| 196 | { |
||
| 197 | $template = new Twig_TemplateTest(new Twig_Environment(), $useExt); |
||
| 198 | |||
| 199 | $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type)); |
||
| 200 | } |
||
| 201 | |||
| 202 | /** |
||
| 203 | * @dataProvider getGetAttributeTests |
||
| 204 | */ |
||
| 205 | public function testGetAttributeStrict($defined, $value, $object, $item, $arguments, $type, $useExt = false, $exceptionMessage = null) |
||
| 223 | |||
| 224 | /** |
||
| 225 | * @dataProvider getGetAttributeTests |
||
| 226 | */ |
||
| 227 | public function testGetAttributeDefined($defined, $value, $object, $item, $arguments, $type, $useExt = false) |
||
| 228 | { |
||
| 229 | $template = new Twig_TemplateTest(new Twig_Environment(), $useExt); |
||
| 230 | |||
| 231 | $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true)); |
||
| 232 | } |
||
| 233 | |||
| 234 | /** |
||
| 235 | * @dataProvider getGetAttributeTests |
||
| 236 | */ |
||
| 237 | public function testGetAttributeDefinedStrict($defined, $value, $object, $item, $arguments, $type, $useExt = false) |
||
| 243 | |||
| 244 | /** |
||
| 245 | * @dataProvider getTestsDependingOnExtensionAvailability |
||
| 246 | */ |
||
| 247 | public function testGetAttributeCallExceptions($useExt = false) |
||
| 255 | |||
| 256 | public function getGetAttributeTests() |
||
| 389 | } |
||
| 390 | |||
| 667 |
Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a
@returnannotation as described here.