Skip to content

Commit 2ca5517

Browse files
committed
Merge branch 'v5.5.x'
2 parents 7ab4c80 + bc26369 commit 2ca5517

File tree

9 files changed

+331
-8
lines changed

9 files changed

+331
-8
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"symfony/yaml": "~3.4|~4.3|^5|^6",
4545
"symfony/process": "~3.4|~4.3|^5|^6",
4646
"phpdocumentor/reflection-docblock": "~5.3",
47+
"phpdocumentor/type-resolver": "1.6.*",
4748
"wdes/php-i18n-l10n": "^4.0",
4849
"code-lts/cli-tools": "^1.4.0",
4950
"erusev/parsedown": "^1.7"

scripts/make-release.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,13 @@ echo "PHP version required: ${PHP_VERSION_REQUIRED}"
158158

159159
echo "Lock composer php version"
160160
COMPOSER_FILE=$(cat composer.json)
161+
# Example: 5db49ae740e4d1fd8eb79a9de52c9aefc7906f1f
162+
GIT_COMMIT_HASH="$(git rev-parse --verify HEAD)"
163+
COMPOSER_AUTOLOADER_VERSION="$(echo "$VERSION" | tr '.' '_' | tr '-' '_')"
161164

162165
${COMPOSER_BIN} config platform.php "$PHP_VERSION_REQUIRED"
166+
echo "Setting composer autoload suffix to: ${COMPOSER_AUTOLOADER_VERSION}__${GIT_COMMIT_HASH}"
167+
${COMPOSER_BIN} config autoloader-suffix "${COMPOSER_AUTOLOADER_VERSION}__${GIT_COMMIT_HASH}"
163168

164169
backupVendorFolder
165170

scripts/phar-generator-script.php

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
// To be able to fetch the version in this script afterwards
88
require_once __DIR__ . '/../src/Doctum.php';
9+
// Load phar utils (https://github.com/Seldaek/phar-utils)
10+
require_once __DIR__ . '/phar-utils-timestamps.php';
911

1012
use Phar;
1113
use RecursiveDirectoryIterator;
@@ -40,6 +42,14 @@
4042
exit(1);
4143
}
4244

45+
exec('git show -s --format=%ci HEAD', $commandOutput);
46+
47+
$gitCommitDate = $commandOutput[0] ?? '';
48+
if (empty($gitCommitDate)) {
49+
echo 'Unable to fetch the git commit';
50+
exit(1);
51+
}
52+
4353
$pharAlias = 'doctum-' . $version . '.phar';
4454

4555
$phar = new Phar(
@@ -50,14 +60,12 @@
5060

5161
$shebang = '#!/usr/bin/env php';
5262

53-
$date = date('c');
54-
5563
// See: https://github.com/zendtech/ZendOptimizerPlus/issues/115#issuecomment-25612769
5664
// See: https://stackoverflow.com/a/13896692/5155484
5765
$stub = <<<STUB
5866
<?php
5967
/**
60-
* Doctum phar, generated by Doctum the $date
68+
* Doctum phar, generated by Doctum on $gitCommitDate
6169
* @see https://github.com/code-lts/doctum#readme
6270
* @version $version
6371
* @license MIT
@@ -258,14 +266,14 @@ public static function getExcludedFolders(): array
258266
'vcs.browser' => 'https://github.com/code-lts/doctum',
259267
'vcs.ref' => $gitCommit,
260268
'version' => $version,
261-
'build-date' => $date,
269+
'build-date' => $gitCommitDate,
262270
'license' => 'MIT',
263271
'vendor' => 'Doctum',
264272
'name' => 'Doctum',
265273
]
266274
);
267275

268-
$files = array_map(
276+
$files = array_map(
269277
static function (string $fileRelativePath) {
270278
return [
271279
'name' => $fileRelativePath,
@@ -274,13 +282,31 @@ static function (string $fileRelativePath) {
274282
},
275283
PharFilterIterator::getAcceptedFiles()
276284
);
285+
$oldPharCount = $phar->count();
286+
unset($phar);
287+
288+
$pharUtils = new \Seld\PharUtils\Timestamps($buildRoot . '/doctum.phar');
289+
$pharUtils->updateTimestamps($gitCommitDate);
290+
$pharUtils->save($buildRoot . '/doctum.phar', Phar::SHA256);
291+
292+
$phar = new Phar(
293+
$buildRoot . '/doctum.phar',
294+
FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME,
295+
$pharAlias
296+
);
297+
298+
if ($oldPharCount !== $phar->count()) {
299+
echo 'File count mismatch, phar is corrupted ?';
300+
exit(1);
301+
}
277302

278303
$manifest = [
279304
'files' => $files,
280305
'excludedFiles' => PharFilterIterator::getExcludedFiles(),
281306
'excludedFolders' => PharFilterIterator::getExcludedFolders(),
282307
'phar' => [
283308
'sha256' => $phar->getSignature()['hash'],
309+
'fileSha256' => hash_file('sha256', $buildRoot . '/doctum.phar'),
284310
'numberOfFiles' => $phar->count(),
285311
],
286312
];

scripts/phar-utils-timestamps.php

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
/*
6+
* This file is part of PHAR Utils.
7+
*
8+
* (c) Jordi Boggiano <[email protected]>
9+
*
10+
* @source https://github.com/Seldaek/phar-utils/commit/8dbab3e508113310354194c83719ca343b4ad9f4
11+
*
12+
* For the full copyright and license information, please view the LICENSE
13+
* file that was distributed with this source code.
14+
*/
15+
16+
namespace Seld\PharUtils;
17+
18+
class Timestamps
19+
{
20+
/**
21+
* @var string
22+
*/
23+
private $contents;
24+
25+
/**
26+
* @param string $file path to the phar file to use
27+
*/
28+
public function __construct(string $file)
29+
{
30+
$this->contents = file_get_contents($file);
31+
}
32+
33+
/**
34+
* Updates each file's unix timestamps in the PHAR
35+
*
36+
* The PHAR signature can then be produced in a reproducible manner.
37+
*
38+
* @param int|\DateTimeInterface|string $timestamp Date string or DateTime or unix timestamp to use
39+
*/
40+
public function updateTimestamps($timestamp = null)
41+
{
42+
if ($timestamp instanceof \DateTime || $timestamp instanceof \DateTimeInterface) {
43+
$timestamp = $timestamp->getTimestamp();
44+
} elseif (is_string($timestamp)) {
45+
$timestamp = strtotime($timestamp);
46+
} elseif (!is_int($timestamp)) {
47+
$timestamp = strtotime('1984-12-24T00:00:00Z');
48+
}
49+
50+
// detect manifest offset / end of stub
51+
if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
52+
throw new \RuntimeException('Could not detect the stub\'s end in the phar');
53+
}
54+
55+
// set starting position and skip past manifest length
56+
$pos = $match[0][1] + strlen($match[0][0]);
57+
$stubEnd = $pos + $this->readUint($pos, 4);
58+
$pos += 4;
59+
60+
$numFiles = $this->readUint($pos, 4);
61+
$pos += 4;
62+
63+
// skip API version (YOLO)
64+
$pos += 2;
65+
66+
// skip PHAR flags
67+
$pos += 4;
68+
69+
$aliasLength = $this->readUint($pos, 4);
70+
$pos += 4 + $aliasLength;
71+
72+
$metadataLength = $this->readUint($pos, 4);
73+
$pos += 4 + $metadataLength;
74+
75+
while ($pos < $stubEnd) {
76+
$filenameLength = $this->readUint($pos, 4);
77+
$pos += 4 + $filenameLength;
78+
79+
// skip filesize
80+
$pos += 4;
81+
82+
// update timestamp to a fixed value
83+
$timeStampBytes = pack('L', $timestamp);
84+
$this->contents[$pos + 0] = $timeStampBytes[0];
85+
$this->contents[$pos + 1] = $timeStampBytes[1];
86+
$this->contents[$pos + 2] = $timeStampBytes[2];
87+
$this->contents[$pos + 3] = $timeStampBytes[3];
88+
89+
// skip timestamp, compressed file size, crc32 checksum and file flags
90+
$pos += 4 * 4;
91+
92+
$metadataLength = $this->readUint($pos, 4);
93+
$pos += 4 + $metadataLength;
94+
95+
$numFiles--;
96+
}
97+
98+
if ($numFiles !== 0) {
99+
throw new \LogicException('All files were not processed, something must have gone wrong');
100+
}
101+
}
102+
103+
/**
104+
* Saves the updated phar file, optionally with an updated signature.
105+
*
106+
* @param string $path
107+
* @param int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512
108+
* @return bool
109+
*/
110+
public function save(string $path, int $signatureAlgo)
111+
{
112+
$pos = $this->determineSignatureBegin();
113+
114+
$algos = [
115+
\Phar::MD5 => 'md5',
116+
\Phar::SHA1 => 'sha1',
117+
\Phar::SHA256 => 'sha256',
118+
\Phar::SHA512 => 'sha512',
119+
];
120+
121+
if (!isset($algos[$signatureAlgo])) {
122+
throw new \UnexpectedValueException('Invalid hash algorithm given: ' . $signatureAlgo . ' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512');
123+
}
124+
$algo = $algos[$signatureAlgo];
125+
126+
// re-sign phar
127+
// signature
128+
$signature = hash($algo, substr($this->contents, 0, $pos), true)
129+
// sig type
130+
. pack('L', $signatureAlgo)
131+
// ohai Greg & Marcus
132+
. 'GBMB';
133+
134+
$this->contents = substr($this->contents, 0, $pos) . $signature;
135+
136+
return file_put_contents($path, $this->contents);
137+
}
138+
139+
private function readUint($pos, $bytes)
140+
{
141+
$res = unpack('V', substr($this->contents, $pos, $bytes));
142+
143+
return $res[1];
144+
}
145+
146+
/**
147+
* Determine the beginning of the signature.
148+
*
149+
* @return int
150+
*/
151+
private function determineSignatureBegin()
152+
{
153+
// detect signature position
154+
if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
155+
throw new \RuntimeException('Could not detect the stub\'s end in the phar');
156+
}
157+
158+
// set starting position and skip past manifest length
159+
$pos = $match[0][1] + strlen($match[0][0]);
160+
$manifestEnd = $pos + 4 + $this->readUint($pos, 4);
161+
162+
$pos += 4;
163+
$numFiles = $this->readUint($pos, 4);
164+
165+
$pos += 4;
166+
167+
// skip API version (YOLO)
168+
$pos += 2;
169+
170+
// skip PHAR flags
171+
$pos += 4;
172+
173+
$aliasLength = $this->readUint($pos, 4);
174+
$pos += 4 + $aliasLength;
175+
176+
$metadataLength = $this->readUint($pos, 4);
177+
$pos += 4 + $metadataLength;
178+
179+
$compressedSizes = 0;
180+
while (($numFiles > 0) && ($pos < $manifestEnd - 24)) {
181+
$filenameLength = $this->readUint($pos, 4);
182+
$pos += 4 + $filenameLength;
183+
184+
// skip filesize and timestamp
185+
$pos += 2 * 4;
186+
187+
$compressedSizes += $this->readUint($pos, 4);
188+
// skip compressed file size, crc32 checksum and file flags
189+
$pos += 3 * 4;
190+
191+
$metadataLength = $this->readUint($pos, 4);
192+
$pos += 4 + $metadataLength;
193+
194+
$numFiles--;
195+
}
196+
197+
if ($numFiles !== 0) {
198+
throw new \LogicException('All files were not processed, something must have gone wrong');
199+
}
200+
201+
return $manifestEnd + $compressedSizes;
202+
}
203+
204+
}

scripts/update-release.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/sh
1+
#!/bin/bash
22

33
##
44
# @license http://unlicense.org/UNLICENSE The UNLICENSE

src/Parser/NodeVisitor.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
use Doctum\Parser\Node\DocBlockNode;
4242
use PhpParser\Node\Stmt\PropertyProperty;
4343
use PhpParser\Node\UnionType;
44+
use PhpParser\Node\IntersectionType;
4445

4546
class NodeVisitor extends NodeVisitorAbstract
4647
{
@@ -187,12 +188,12 @@ protected function addFunction(FunctionNode $node, ?string $namespace = null)
187188
}
188189

189190
/**
190-
* @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|null $type Type declaration
191+
* @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|IntersectionType|null $type Type declaration
191192
*/
192193
protected function typeToString($type): ?string
193194
{
194195
$typeString = null;
195-
if ($type !== null && ! ($type instanceof NullableType || $type instanceof UnionType)) {
196+
if ($type !== null && ! ($type instanceof NullableType || $type instanceof UnionType || $type instanceof IntersectionType)) {
196197
$typeString = $type->__toString();
197198
} elseif ($type instanceof NullableType) {
198199
$typeString = $type->type->__toString();
@@ -202,6 +203,12 @@ protected function typeToString($type): ?string
202203
$typeString[] = $type->__toString();
203204
}
204205
$typeString = implode('|', $typeString);
206+
} elseif ($type instanceof IntersectionType) {
207+
$typeString = [];
208+
foreach ($type->types as $type) {
209+
$typeString[] = $type->__toString();
210+
}
211+
$typeString = implode('&', $typeString);
205212
}
206213

207214
if ($typeString === null) {

0 commit comments

Comments
 (0)