Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
/*
621 lars 3
 * This file is part of sebastian/recursion-context.
148 lars 4
 *
5
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace SebastianBergmann\RecursionContext;
11
 
12
use const PHP_INT_MAX;
13
use const PHP_INT_MIN;
621 lars 14
use function array_key_exists;
148 lars 15
use function array_pop;
16
use function array_slice;
17
use function count;
18
use function is_array;
19
use function is_object;
20
use function random_int;
21
use function spl_object_hash;
22
use SplObjectStorage;
23
 
24
/**
25
 * A context containing previously processed arrays and objects
26
 * when recursively processing a value.
27
 */
28
final class Context
29
{
30
    /**
31
     * @var array[]
32
     */
33
    private $arrays;
34
 
35
    /**
36
     * @var SplObjectStorage
37
     */
38
    private $objects;
39
 
40
    /**
41
     * Initialises the context.
42
     */
43
    public function __construct()
44
    {
45
        $this->arrays  = [];
46
        $this->objects = new SplObjectStorage;
47
    }
48
 
49
    /**
50
     * @codeCoverageIgnore
51
     */
52
    public function __destruct()
53
    {
54
        foreach ($this->arrays as &$array) {
55
            if (is_array($array)) {
56
                array_pop($array);
57
                array_pop($array);
58
            }
59
        }
60
    }
61
 
62
    /**
63
     * Adds a value to the context.
64
     *
65
     * @param array|object $value the value to add
66
     *
67
     * @throws InvalidArgumentException Thrown if $value is not an array or object
68
     *
69
     * @return bool|int|string the ID of the stored value, either as a string or integer
70
     *
71
     * @psalm-template T
72
     * @psalm-param T $value
73
     * @param-out T $value
74
     */
75
    public function add(&$value)
76
    {
77
        if (is_array($value)) {
78
            return $this->addArray($value);
79
        }
80
 
81
        if (is_object($value)) {
82
            return $this->addObject($value);
83
        }
84
 
85
        throw new InvalidArgumentException(
86
            'Only arrays and objects are supported'
87
        );
88
    }
89
 
90
    /**
91
     * Checks if the given value exists within the context.
92
     *
93
     * @param array|object $value the value to check
94
     *
95
     * @throws InvalidArgumentException Thrown if $value is not an array or object
96
     *
97
     * @return false|int|string the string or integer ID of the stored value if it has already been seen, or false if the value is not stored
98
     *
99
     * @psalm-template T
100
     * @psalm-param T $value
101
     * @param-out T $value
102
     */
103
    public function contains(&$value)
104
    {
105
        if (is_array($value)) {
106
            return $this->containsArray($value);
107
        }
108
 
109
        if (is_object($value)) {
110
            return $this->containsObject($value);
111
        }
112
 
113
        throw new InvalidArgumentException(
114
            'Only arrays and objects are supported'
115
        );
116
    }
117
 
118
    /**
119
     * @return bool|int
120
     */
121
    private function addArray(array &$array)
122
    {
123
        $key = $this->containsArray($array);
124
 
125
        if ($key !== false) {
126
            return $key;
127
        }
128
 
129
        $key            = count($this->arrays);
130
        $this->arrays[] = &$array;
131
 
621 lars 132
        if (!array_key_exists(PHP_INT_MAX, $array) && !array_key_exists(PHP_INT_MAX - 1, $array)) {
148 lars 133
            $array[] = $key;
134
            $array[] = $this->objects;
135
        } else { /* cover the improbable case too */
621 lars 136
            /* Note that array_slice (used in containsArray) will return the
137
             * last two values added *not necessarily* the highest integer
138
             * keys in the array, so the order of these writes to $array
139
             * is important, but the actual keys used is not. */
148 lars 140
            do {
141
                $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
621 lars 142
            } while (array_key_exists($key, $array));
148 lars 143
 
144
            $array[$key] = $key;
145
 
146
            do {
147
                $key = random_int(PHP_INT_MIN, PHP_INT_MAX);
621 lars 148
            } while (array_key_exists($key, $array));
148 lars 149
 
150
            $array[$key] = $this->objects;
151
        }
152
 
153
        return $key;
154
    }
155
 
156
    /**
157
     * @param object $object
158
     */
159
    private function addObject($object): string
160
    {
161
        if (!$this->objects->contains($object)) {
162
            $this->objects->attach($object);
163
        }
164
 
165
        return spl_object_hash($object);
166
    }
167
 
168
    /**
169
     * @return false|int
170
     */
171
    private function containsArray(array &$array)
172
    {
173
        $end = array_slice($array, -2);
174
 
175
        return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false;
176
    }
177
 
178
    /**
179
     * @param object $value
180
     *
181
     * @return false|string
182
     */
183
    private function containsObject($value)
184
    {
185
        if ($this->objects->contains($value)) {
186
            return spl_object_hash($value);
187
        }
188
 
189
        return false;
190
    }
191
}