1 | <?php |
||
2 | /** |
||
3 | * @category Library |
||
4 | * @license MIT http://opensource.org/licenses/MIT |
||
5 | * @link https://github.com/emlynwest/changelog |
||
6 | */ |
||
7 | |||
8 | namespace ChangeLog; |
||
9 | |||
10 | use ArrayIterator; |
||
11 | use Countable; |
||
12 | use IteratorAggregate; |
||
13 | use Naneau\SemVer\Parser; |
||
14 | use Naneau\SemVer\Sort; |
||
15 | use Naneau\SemVer\Version; |
||
0 ignored issues
–
show
|
|||
16 | use Traversable; |
||
17 | |||
18 | /** |
||
19 | * Represents a full change log. |
||
20 | */ |
||
21 | class Log implements IteratorAggregate, Countable |
||
22 | { |
||
23 | const VERSION_MAJOR = 'major'; |
||
24 | const VERSION_MINOR = 'minor'; |
||
25 | const VERSION_PATCH = 'patch'; |
||
26 | |||
27 | /** |
||
28 | * @var string |
||
29 | */ |
||
30 | protected $description = ''; |
||
31 | |||
32 | /** |
||
33 | * @var Release[] |
||
34 | */ |
||
35 | protected $releases = []; |
||
36 | |||
37 | /** |
||
38 | * @var string |
||
39 | */ |
||
40 | protected $title = ''; |
||
41 | |||
42 | /** |
||
43 | * Gets all the releases for the log. |
||
44 | * |
||
45 | * @return Release[] |
||
46 | */ |
||
47 | 3 | public function getReleases() |
|
48 | { |
||
49 | 3 | return $this->releases; |
|
50 | } |
||
51 | |||
52 | /** |
||
53 | * Gets the named release. |
||
54 | * |
||
55 | * @param string $name |
||
56 | * |
||
57 | * @return Release|null |
||
58 | */ |
||
59 | 9 | public function getRelease($name) |
|
60 | { |
||
61 | 9 | $key = strtolower($name); |
|
62 | 9 | if ( ! $this->hasRelease($key)) |
|
63 | { |
||
64 | 1 | return null; |
|
65 | } |
||
66 | |||
67 | 9 | return $this->releases[$key]; |
|
68 | } |
||
69 | |||
70 | /** |
||
71 | * Adds a release to the Log. |
||
72 | * Can be used to replace existing releases too. |
||
73 | * |
||
74 | * @param Release $release |
||
75 | */ |
||
76 | 23 | public function addRelease(Release $release) |
|
77 | { |
||
78 | 23 | $name = strtolower($release->getName()); |
|
79 | 23 | $this->releases[$name] = $release; |
|
80 | 23 | $this->sortReleases(); |
|
81 | } |
||
82 | |||
83 | /** |
||
84 | * Removes a release from the Log. |
||
85 | * |
||
86 | * @param string $name |
||
87 | */ |
||
88 | 4 | public function removeRelease($name) |
|
89 | { |
||
90 | 4 | $key = strtolower($name); |
|
91 | 4 | unset($this->releases[$key]); |
|
92 | } |
||
93 | |||
94 | /** |
||
95 | * Checks if the Log has the named Release. |
||
96 | * |
||
97 | * @param string $name |
||
98 | * |
||
99 | * @return bool |
||
100 | */ |
||
101 | 13 | public function hasRelease($name) |
|
102 | { |
||
103 | 13 | $key = strtolower($name); |
|
104 | 13 | return isset($this->releases[$key]); |
|
105 | } |
||
106 | |||
107 | /** |
||
108 | * @return string |
||
109 | */ |
||
110 | 14 | public function getDescription() |
|
111 | { |
||
112 | 14 | return $this->description; |
|
113 | } |
||
114 | |||
115 | /** |
||
116 | * @param string $description |
||
117 | */ |
||
118 | 17 | public function setDescription($description) |
|
119 | { |
||
120 | 17 | $this->description = $description; |
|
121 | } |
||
122 | |||
123 | /** |
||
124 | * @return string |
||
125 | */ |
||
126 | 14 | public function getTitle() |
|
127 | { |
||
128 | 14 | return $this->title; |
|
129 | } |
||
130 | |||
131 | /** |
||
132 | * @param string $title |
||
133 | */ |
||
134 | 17 | public function setTitle($title) |
|
135 | { |
||
136 | 17 | $this->title = $title; |
|
137 | } |
||
138 | |||
139 | /** |
||
140 | * {@inheritdoc} |
||
141 | */ |
||
142 | 18 | public function getIterator() |
|
143 | { |
||
144 | 18 | return new ArrayIterator($this->releases); |
|
145 | } |
||
146 | |||
147 | /** |
||
148 | * {@inheritdoc} |
||
149 | */ |
||
150 | 1 | public function count() |
|
151 | { |
||
152 | 1 | return count($this->releases); |
|
153 | } |
||
154 | |||
155 | /** |
||
156 | * Sorts the releases inside this log in accordance with semantic versioning, latest release first. |
||
157 | */ |
||
158 | 23 | public function sortReleases() |
|
159 | { |
||
160 | // If there is an unreleased release pull that out and sort the rest |
||
161 | 23 | $unreleased = null; |
|
162 | 23 | if (isset($this->releases['unreleased'])) |
|
163 | { |
||
164 | 10 | $unreleased = $this->releases['unreleased']; |
|
165 | 10 | unset($this->releases['unreleased']); |
|
166 | } |
||
167 | |||
168 | 23 | $order = Sort::sort(array_keys($this->releases)); |
|
169 | 23 | $order = array_reverse($order); |
|
170 | |||
171 | 23 | $newOrder = []; |
|
172 | /** @var Version $version */ |
||
173 | 23 | foreach ($order as $version) |
|
174 | { |
||
175 | 23 | $index = $version->__toString(); |
|
176 | 23 | $newOrder[$index] = $this->releases[$index]; |
|
177 | } |
||
178 | |||
179 | 23 | if ($unreleased !== null) |
|
180 | { |
||
181 | 10 | $newOrder = ['unreleased' => $unreleased] + $newOrder; |
|
182 | } |
||
183 | |||
184 | 23 | $this->releases = $newOrder; |
|
185 | } |
||
186 | |||
187 | /** |
||
188 | * Merges another Log's releases with this log. |
||
189 | * |
||
190 | * @param Log $log |
||
191 | */ |
||
192 | 2 | public function mergeLog(Log $log) |
|
193 | { |
||
194 | /** @var Release $release */ |
||
195 | 2 | foreach ($log as $release) |
|
196 | { |
||
197 | 2 | $name = $release->getName(); |
|
198 | 2 | if ($this->hasRelease($name)) |
|
199 | { |
||
200 | // if it does exist then merge the changes |
||
201 | 2 | $this->mergeRelease($log, $name); |
|
202 | } |
||
203 | else |
||
204 | { |
||
205 | // If the release does not exist add it |
||
206 | 2 | $this->addRelease($release); |
|
207 | } |
||
208 | } |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * Combines all changes of the name of the given release from the given log into this log. |
||
213 | * |
||
214 | * @param Log $log |
||
215 | * @param string $name |
||
216 | */ |
||
217 | 2 | protected function mergeRelease(Log $log, $name) |
|
218 | { |
||
219 | 2 | $myRelease = $this->getRelease($name); |
|
220 | 2 | $theirRelease = $log->getRelease($name); |
|
221 | |||
222 | 2 | $changes = $this->mergeChangesArrays( |
|
223 | 2 | $theirRelease->getAllChanges(), |
|
224 | 2 | $myRelease->getAllChanges() |
|
225 | 2 | ); |
|
226 | 2 | $myRelease->setAllChanges($changes); |
|
227 | } |
||
228 | |||
229 | /** |
||
230 | * Merges two sets of changes. |
||
231 | * |
||
232 | * @param array $left |
||
233 | * @param array $right |
||
234 | * |
||
235 | * @return array |
||
236 | */ |
||
237 | 2 | protected function mergeChangesArrays($left, $right) |
|
238 | { |
||
239 | 2 | $return = $left; |
|
240 | |||
241 | 2 | foreach ($right as $type => $changes) |
|
242 | { |
||
243 | 2 | if (isset($left[$type])) |
|
244 | { |
||
245 | 2 | $return[$type] = array_merge($right[$type], $left[$type]); |
|
246 | } |
||
247 | else |
||
248 | { |
||
249 | 1 | $return[$type] = $changes; |
|
250 | } |
||
251 | } |
||
252 | |||
253 | 2 | return $return; |
|
254 | } |
||
255 | |||
256 | /** |
||
257 | * @return Release |
||
258 | */ |
||
259 | 2 | public function getLatestRelease() |
|
260 | { |
||
261 | 2 | $releases = $this->releases; |
|
262 | |||
263 | 2 | $release = array_shift($releases); |
|
264 | |||
265 | 2 | if (count($this->releases) > 1 && strtolower($release->getName()) === 'unreleased') |
|
266 | { |
||
267 | 1 | $release = array_shift($releases); |
|
268 | } |
||
269 | |||
270 | 2 | return $release; |
|
271 | } |
||
272 | |||
273 | 5 | public function getNextVersion($type) |
|
274 | { |
||
275 | 5 | if (! in_array($type, [static::VERSION_MAJOR, static::VERSION_MINOR, static::VERSION_PATCH])) |
|
276 | { |
||
277 | 5 | return $type; |
|
278 | } |
||
279 | |||
280 | 1 | $latestRelease = $this->getLatestRelease(); |
|
281 | |||
282 | 1 | $version = $latestRelease->getName() === 'unreleased' ? '0.0.0' : $latestRelease->getName() ; |
|
283 | |||
284 | 1 | $semver = Parser::parse($version); |
|
285 | 1 | $patch = $semver->getPatch(); |
|
286 | 1 | $minor = $semver->getMinor(); |
|
287 | 1 | $major = $semver->getMajor(); |
|
288 | |||
289 | switch ($type) |
||
290 | { |
||
291 | case Log::VERSION_PATCH: |
||
292 | 1 | $patch++; |
|
293 | 1 | break; |
|
294 | case Log::VERSION_MINOR: |
||
295 | 1 | $minor++; |
|
296 | 1 | break; |
|
297 | case Log::VERSION_MAJOR: |
||
298 | 1 | $major++; |
|
299 | 1 | break; |
|
300 | } |
||
301 | |||
302 | 1 | return "$major.$minor.$patch"; |
|
303 | } |
||
304 | |||
305 | } |
||
306 |
Let?s assume that you have a directory layout like this:
and let?s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: