Browse code

Merge branch '1.x' into 2.x

* 1.x:
Optimize usage of Traversable/Iterator
Fix typo in phpdoc
fix C89 compat

Fabien Potencier authored on 27/12/2016 10:56:33
Showing 3 changed files
... ...
@@ -573,7 +573,7 @@ function twig_array_merge($arr1, $arr2)
573 573
 function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
574 574
 {
575 575
     if ($item instanceof Traversable) {
576
-        if ($item instanceof IteratorAggregate) {
576
+        while ($item instanceof IteratorAggregate) {
577 577
             $item = $item->getIterator();
578 578
         }
579 579
 
... ...
@@ -734,7 +734,27 @@ function _twig_default_filter($value, $default = '')
734 734
 function twig_get_array_keys_filter($array)
735 735
 {
736 736
     if ($array instanceof Traversable) {
737
-        return array_keys(iterator_to_array($array));
737
+        while ($array instanceof IteratorAggregate) {
738
+            $array = $array->getIterator();
739
+        }
740
+
741
+        if ($array instanceof Iterator) {
742
+            $keys = array();
743
+            $array->rewind();
744
+            while ($array->valid()) {
745
+                $keys[] = $array->key();
746
+                $array->next();
747
+            }
748
+
749
+            return $keys;
750
+        }
751
+
752
+        $keys = array();
753
+        foreach ($array as $key => $item) {
754
+            $keys[] = $key;
755
+        }
756
+
757
+        return $keys;
738 758
     }
739 759
 
740 760
     if (!is_array($array)) {
... ...
@@ -812,7 +832,21 @@ function twig_in_filter($value, $compare)
812 832
     } elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) {
813 833
         return '' === $value || false !== strpos($compare, (string) $value);
814 834
     } elseif ($compare instanceof Traversable) {
815
-        return in_array($value, iterator_to_array($compare, false), is_object($value) || is_resource($value));
835
+        if (is_object($value) || is_resource($value)) {
836
+            foreach ($compare as $item) {
837
+                if ($item === $value) {
838
+                    return true;
839
+                }
840
+            }
841
+        } else {
842
+            foreach ($compare as $item) {
843
+                if ($item == $value) {
844
+                    return true;
845
+                }
846
+            }
847
+        }
848
+
849
+        return false;
816 850
     }
817 851
 
818 852
     return false;
... ...
@@ -19,7 +19,7 @@ class Twig_FactoryRuntimeLoader implements Twig_RuntimeLoaderInterface
19 19
     private $map;
20 20
 
21 21
     /**
22
-     * @param array $map An array where keys are class names and keys factory callables
22
+     * @param array $map An array where keys are class names and values factory callables
23 23
      */
24 24
     public function __construct($map = array())
25 25
     {
... ...
@@ -107,14 +107,24 @@ class Twig_Tests_Extension_CoreTest extends PHPUnit_Framework_TestCase
107 107
         $this->assertEquals($output, 'éÄ');
108 108
     }
109 109
 
110
-    public function testCustomEscaper()
110
+    /**
111
+     * @dataProvider provideCustomEscaperCases
112
+     */
113
+    public function testCustomEscaper($expected, $string, $strategy)
111 114
     {
112 115
         $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock());
113 116
         $twig->getExtension('Twig_Extension_Core')->setEscaper('foo', 'foo_escaper_for_test');
114 117
 
115
-        $this->assertEquals('fooUTF-8', twig_escape_filter($twig, 'foo', 'foo'));
116
-        $this->assertEquals('UTF-8', twig_escape_filter($twig, null, 'foo'));
117
-        $this->assertEquals('42UTF-8', twig_escape_filter($twig, 42, 'foo'));
118
+        $this->assertSame($expected, twig_escape_filter($twig, $string, $strategy));
119
+    }
120
+
121
+    public function provideCustomEscaperCases()
122
+    {
123
+        return array(
124
+            array('fooUTF-8', 'foo', 'foo'),
125
+            array('UTF-8', null, 'foo'),
126
+            array('42UTF-8', 42, 'foo'),
127
+        );
118 128
     }
119 129
 
120 130
     /**
... ...
@@ -125,22 +135,129 @@ class Twig_Tests_Extension_CoreTest extends PHPUnit_Framework_TestCase
125 135
         twig_escape_filter(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()), 'foo', 'bar');
126 136
     }
127 137
 
128
-    public function testTwigFirst()
138
+    /**
139
+     * @dataProvider provideTwigFirstCases
140
+     */
141
+    public function testTwigFirst($expected, $input)
129 142
     {
130 143
         $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock());
131
-        $this->assertEquals('a', twig_first($twig, 'abc'));
132
-        $this->assertEquals(1, twig_first($twig, array(1, 2, 3)));
133
-        $this->assertSame('', twig_first($twig, null));
134
-        $this->assertSame('', twig_first($twig, ''));
144
+        $this->assertSame($expected, twig_first($twig, $input));
135 145
     }
136 146
 
137
-    public function testTwigLast()
147
+    public function provideTwigFirstCases()
148
+    {
149
+        $i = array(1 => 'a', 2 => 'b', 3 => 'c');
150
+
151
+        return array(
152
+            array('a', 'abc'),
153
+            array(1, array(1, 2, 3)),
154
+            array('', null),
155
+            array('', ''),
156
+            array('a', new CoreTestIterator($i, array_keys($i), true, 3)),
157
+        );
158
+    }
159
+
160
+    /**
161
+     * @dataProvider provideTwigLastCases
162
+     */
163
+    public function testTwigLast($expected, $input)
138 164
     {
139 165
         $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock());
140
-        $this->assertEquals('c', twig_last($twig, 'abc'));
141
-        $this->assertEquals(3, twig_last($twig, array(1, 2, 3)));
142
-        $this->assertSame('', twig_last($twig, null));
143
-        $this->assertSame('', twig_last($twig, ''));
166
+        $this->assertSame($expected, twig_last($twig, $input));
167
+    }
168
+
169
+    public function provideTwigLastCases()
170
+    {
171
+        $i = array(1 => 'a', 2 => 'b', 3 => 'c');
172
+
173
+        return array(
174
+            array('c', 'abc'),
175
+            array(3, array(1, 2, 3)),
176
+            array('', null),
177
+            array('', ''),
178
+            array('c', new CoreTestIterator($i, array_keys($i), true)),
179
+        );
180
+    }
181
+
182
+    /**
183
+     * @dataProvider provideArrayKeyCases
184
+     */
185
+    public function testArrayKeysFilter(array $expected, $input)
186
+    {
187
+        $this->assertSame($expected, twig_get_array_keys_filter($input));
188
+    }
189
+
190
+    public function provideArrayKeyCases()
191
+    {
192
+        $array = array('a' => 'a1', 'b' => 'b1', 'c' => 'c1');
193
+        $keys = array_keys($array);
194
+
195
+        return array(
196
+            array($keys, $array),
197
+            array($keys, new CoreTestIterator($array, $keys)),
198
+            array($keys, new CoreTestIteratorAggregate($array, $keys)),
199
+            array($keys, new CoreTestIteratorAggregateAggregate($array, $keys)),
200
+            array(array(), null),
201
+            array(array('a'), new SimpleXMLElement('<xml><a></a></xml>')),
202
+        );
203
+    }
204
+
205
+    /**
206
+     * @dataProvider provideInFilterCases
207
+     */
208
+    public function testInFilter($expected, $value, $compare)
209
+    {
210
+        $this->assertSame($expected, twig_in_filter($value, $compare));
211
+    }
212
+
213
+    public function provideInFilterCases()
214
+    {
215
+        $array = array(1, 2, 'a' => 3, 5, 6, 7);
216
+        $keys = array_keys($array);
217
+
218
+        return array(
219
+            array(true, 1, $array),
220
+            array(true, '3', $array),
221
+            array(true, '3', 'abc3def'),
222
+            array(true, 1, new CoreTestIterator($array, $keys, true, 1)),
223
+            array(true, '3', new CoreTestIterator($array, $keys, true, 3)),
224
+            array(true, '3', new CoreTestIteratorAggregateAggregate($array, $keys, true, 3)),
225
+            array(false, 4, $array),
226
+            array(false, 4, new CoreTestIterator($array, $keys, true)),
227
+            array(false, 4, new CoreTestIteratorAggregateAggregate($array, $keys, true)),
228
+            array(false, 1, 1),
229
+            array(true, 'b', new SimpleXMLElement('<xml><a>b</a></xml>')),
230
+        );
231
+    }
232
+
233
+    /**
234
+     * @dataProvider provideSliceFilterCases
235
+     */
236
+    public function testSliceFilter($expected, $input, $start, $length = null, $preserveKeys = false)
237
+    {
238
+        $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock());
239
+        $this->assertSame($expected, twig_slice($twig, $input, $start, $length, $preserveKeys));
240
+    }
241
+
242
+    public function provideSliceFilterCases()
243
+    {
244
+        $i = array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4);
245
+        $keys = array_keys($i);
246
+
247
+        return array(
248
+            array(array('a' => 1), $i, 0, 1, true),
249
+            array(array('a' => 1), $i, 0, 1, false),
250
+            array(array('b' => 2, 'c' => 3), $i, 1, 2),
251
+            array(array(1), array(1, 2, 3, 4), 0, 1),
252
+            array(array(2, 3), array(1, 2, 3, 4), 1, 2),
253
+            array(array(2, 3), new CoreTestIterator($i, $keys, true), 1, 2),
254
+            array(array('c' => 3, 'd' => 4), new CoreTestIteratorAggregate($i, $keys, true), 2, null, true),
255
+            array($i, new CoreTestIterator($i, $keys, true), 0, count($keys) + 10, true),
256
+            array(array(), new CoreTestIterator($i, $keys, true), count($keys) + 10),
257
+            array('de', 'abcdef', 3, 2),
258
+            array(array(), new SimpleXMLElement('<items><item>1</item><item>2</item></items>'), 3),
259
+            array(array(), new ArrayIterator(array(1, 2)), 3)
260
+        );
144 261
     }
145 262
 }
146 263
 
... ...
@@ -148,3 +265,83 @@ function foo_escaper_for_test(Twig_Environment $env, $string, $charset)
148 265
 {
149 266
     return $string.$charset;
150 267
 }
268
+
269
+final class CoreTestIteratorAggregate implements IteratorAggregate
270
+{
271
+    private $iterator;
272
+
273
+    public function __construct(array $array, array $keys, $allowAccess = false, $maxPosition = false)
274
+    {
275
+        $this->iterator = new CoreTestIterator($array, $keys, $allowAccess, $maxPosition);
276
+    }
277
+
278
+    public function getIterator()
279
+    {
280
+        return $this->iterator;
281
+    }
282
+}
283
+
284
+final class CoreTestIteratorAggregateAggregate implements IteratorAggregate
285
+{
286
+    private $iterator;
287
+
288
+    public function __construct(array $array, array $keys, $allowValueAccess = false, $maxPosition = false)
289
+    {
290
+        $this->iterator = new CoreTestIteratorAggregate($array, $keys, $allowValueAccess, $maxPosition);
291
+    }
292
+
293
+    public function getIterator()
294
+    {
295
+        return $this->iterator;
296
+    }
297
+}
298
+
299
+final class CoreTestIterator implements Iterator
300
+{
301
+    private $position;
302
+    private $array;
303
+    private $arrayKeys;
304
+    private $allowValueAccess;
305
+    private $maxPosition;
306
+
307
+    public function __construct(array $values, array $keys, $allowValueAccess = false, $maxPosition = false)
308
+    {
309
+        $this->array = $values;
310
+        $this->arrayKeys = $keys;
311
+        $this->position = 0;
312
+        $this->allowValueAccess = $allowValueAccess;
313
+        $this->maxPosition = false === $maxPosition ? count($values) + 1 : $maxPosition;
314
+    }
315
+
316
+    public function rewind()
317
+    {
318
+        $this->position = 0;
319
+    }
320
+
321
+    public function current()
322
+    {
323
+        if ($this->allowValueAccess) {
324
+            return $this->array[$this->key()];
325
+        }
326
+
327
+        throw new \LogicException('Code should only use the keys, not the values provided by iterator.');
328
+    }
329
+
330
+    public function key()
331
+    {
332
+        return $this->arrayKeys[$this->position];
333
+    }
334
+
335
+    public function next()
336
+    {
337
+        ++$this->position;
338
+        if ($this->position === $this->maxPosition) {
339
+             throw new \LogicException(sprintf('Code should not iterate beyond %d.', $this->maxPosition));
340
+        }
341
+    }
342
+
343
+    public function valid()
344
+    {
345
+        return isset($this->arrayKeys[$this->position]);
346
+    }
347
+}