8000 Merge branch '4.4' · symfony/symfony@99098f8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 99098f8

Browse files
Merge branch '4.4'
* 4.4: [DI] add `LazyString` for lazy computation of string values injected into services [Dotenv] allow LF in single-quoted strings Replace STDIN by php://stdin [Yaml] Throw exception for tagged invalid inline elements [Mailer] Fix Mandrill Transport API payload with named addresses [HttpClient] improve StreamWrapper
2 parents e28bc78 + 594e7ae commit 99098f8

File tree

20 files changed

+352
-75
lines changed

20 files changed

+352
-75
lines changed

src/Symfony/Bridge/Twig/Command/LintCommand.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
8181
$showDeprecations = $input->getOption('show-deprecations');
8282

8383
if (['-'] === $filenames) {
84-
return $this->display($input, $output, $io, [$this->validate($this->getStdin(), uniqid('sf_', true))]);
84+
return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]);
8585
}
8686

8787
if (!$filenames) {
@@ -125,16 +125,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
125125
return $this->display($input, $output, $io, $filesInfo);
126126
}
127127

128-
private function getStdin(): string
129-
{
130-
$template = '';
131-
while (!feof(STDIN)) {
132-
$template .= fread(STDIN, 1024);
133-
}
134-
135-
return $template;
136-
}
137-
138128
private function getFilesInfo(array $filenames): array
139129
{
140130
$filesInfo = [];

src/Symfony/Component/Console/Helper/QuestionHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ private function doAsk(OutputInterface $output, Question $question)
115115
{
116116
$this->writePrompt($output, $question);
117117

118-
$inputStream = $this->inputStream ?: STDIN;
118+
$inputStream = $this->inputStream ?: fopen('php://stdin', 'r');
119119
$autocomplete = $question->getAutocompleterCallback();
120120

121121
if (null === $autocomplete || !Terminal::hasSttyAvailable()) {

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ CHANGELOG
2525
* made singly-implemented interfaces detection be scoped by file
2626
* added ability to define a static priority method for tagged service
2727
* added support for improved syntax to define method calls in Yaml
28+
* added `LazyString` for lazy computation of string values injected into services
2829

2930
4.3.0
3031
-----
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection;
13+
14+
/**
15+
* A string whose value is computed lazily by a callback.
16+
*
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
*/
19+
class LazyString
20+
{
21+
private $value;
22+
23+
/**
24+
* @param callable $callback A callable or a [Closure, method] lazy-callable
25+
*
26+
* @return static
27+
*/
28+
public static function fromCallable($callback, ...$arguments): self
29+
{
30+
if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) {
31+
throw new \TypeError(sprintf('Argument 1 passed to %s() must be a callable or a [Closure, method] lazy-callable, %s given.', __METHOD__, \gettype($callback)));
32+
}
33+
34+
$lazyString = new static();
35+
$lazyString->value = static function () use (&$callback, &$arguments, &$value): string {
36+
if (null !== $arguments) {
37+
if (!\is_callable($callback)) {
38+
$callback[0] = $callback[0]();
39+
$callback[1] = $callback[1] ?? '__invoke';
40+
}
41+
$value = $callback(...$arguments);
42+
$callback = self::getPrettyName($callback);
43+
$arguments = null;
44+
}
45+
46+
return $value ?? '';
47+
};
48+
49+
return $lazyString;
50+
}
51+
52+
public function __toString()
53+
{
54+
if (\is_string($this->value)) {
55+
return $this->value;
56+
}
57+
58+
try {
59+
return $this->value = ($this->value)();
60+
} catch (\Throwable $e) {
61+
if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) {
62+
$type = explode(', ', $e->getMessage());
63+
$type = substr(array_pop($type), 0, -\strlen(' returned'));
64+
$r = new \ReflectionFunction($this->value);
65+
$callback = $r->getStaticVariables()['callback'];
66+
67+
$e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type));
68+
}
69+
70+
if (\PHP_VERSION_ID < 70400) {
71+
// leverage the ErrorHandler component with graceful fallback when it's not available
72+
return trigger_error($e, E_USER_ERROR);
73+
}
74+
75+
throw $e;
76+
}
77+
}
78+
79+
private function __construct()
80+
{
81+
}
82+
83+
private static function getPrettyName(callable $callback): string
84+
{
85+
if (\is_string($callback)) {
86+
return $callback;
87+
}
88+
89+
if (\is_array($callback)) {
90+
$class = \is_object($callback[0]) ? \get_class($callback[0]) : $callback[0];
91+
$method = $callback[1];
92+
} elseif ($callback instanceof \Closure) {
93+
$r = new \ReflectionFunction($callback);
94+
95+
if (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) {
96+
return $r->name;
97+
}
98+
99+
$class = $class->name;
100+
$method = $r->name;
101+
} else {
102+
$class = \get_class($callback);
103+
$method = '__invoke';
104+
}
105+
106+
if (isset($class[15]) && "\0" === $class[15] && 0 === strpos($class, "class@anonymous\x00")) {
107+
$class = get_parent_class($class).'@anonymous';
108+
}
109+
110+
return $class.'::'.$method;
111+
}
112+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\LazyString;
16+
use Symfony\Component\ErrorHandler\ErrorHandler;
17+
18+
class LazyStringTest extends TestCase
19+
{
20+
public function testLazyString()
21+
{
22+
$count = 0;
23+
$s = LazyString::fromCallable(function () use (&$count) {
24+
return ++$count;
25+
});
26+
27+
$this->assertSame(0, $count);
28+
$this->assertSame('1', (string) $s);
29+
$this->assertSame(1, $count);
30+
}
31+
32+
public function testLazyCallable()
33+
{
34+
$count = 0;
35+
$s = LazyString::fromCallable([function () use (&$count) {
36+
return new class($count) {
37+
private $count;
38+
39+
public function __construct(int &$count)
40+
{
41+
$this->count = &$count;
42+
}
43+
44+
public function __invoke()
45+
{
46+
return ++$this->count;
47+
}
48+
};
49+
}]);
50+
51+
$this->assertSame(0, $count);
52+
$this->assertSame('1', (string) $s);
53+
$this->assertSame(1, $count);
54+
$this->assertSame('1', (string) $s); // ensure the value is memoized
55+
$this->assertSame(1, $count);
56+
}
57+
58+
/**
59+
* @runInSeparateProcess
60+
*/
61+
public function testReturnTypeError()
62+
{
63+
ErrorHandler::register();
64+
65+
$s = LazyString::fromCallable(function () { return []; });
66+
67+
$this->expectException(\TypeError::class);
68+
$this->expectExceptionMessage('Return value of '.__NAMESPACE__.'\{closure}() passed to '.LazyString::class.'::fromCallable() must be of the type string, array returned.');
69+
70+
(string) $s;
71+
}
72+
}

src/Symfony/Component/DependencyInjection/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"require-dev": {
2424
"symfony/yaml": "^4.4|^5.0",
2525
"symfony/config": "^5.0",
26+
"symfony/error-handler": "^4.4|^5.0",
2627
"symfony/expression-language": "^4.4|^5.0"
2728
},
2829
"suggest": {

src/Symfony/Component/Dotenv/Dotenv.php

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -258,25 +258,18 @@ private function lexValue(): string
258258

259259
do {
260260
if ("'" === $this->data[$this->cursor]) {
261-
$value = '';
262-
++$this->cursor;
261+
$len = 0;
263262

264-
while ("\n" !== $this->data[$this->cursor]) {
265-
if ("'" === $this->data[$this->cursor]) {
266-
break;
267-
}
268-
$value .= $this->data[$this->cursor];
269-
++$this->cursor;
263+
do {
264+
if ($this->cursor + ++$len === $this->end) {
265+
$this->cursor += $len;
270266

271-
if ($this->cursor === $this->end) {
272267
throw $this->createFormatException('Missing quote to end the value');
273268
}
274-
}
275-
if ("\n" === $this->data[$this->cursor]) {
276-
throw $this->createFormatException('Missing quote to end the value');
277-
}
278-
++$this->cursor;
279-
$v .= $value;
269+
} while ("'" !== $this->data[$this->cursor + $len]);
270+
271+
$v .= substr($this->data, 1 + $this->cursor, $len - 1);
272+
$this->cursor += 1 + $len;
280273
} elseif ('"' === $this->data[$this->cursor]) {
281274
$value = '';
282275
++$this->cursor;

src/Symfony/Component/Dotenv/Tests/DotenvTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function getEnvDataWithFormatErrors()
4040
['FOO', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO...\n ^ line 1 offset 3"],
4141
['FOO="foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo...\n ^ line 1 offset 8"],
4242
['FOO=\'foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo...\n ^ line 1 offset 8"],
43-
['FOO=\'foo'."\n", "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo\\n...\n ^ line 1 offset 8"],
43+
['FOO=\'foo'."\n", "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo\\n...\n ^ line 1 offset 9"],
4444
['export FOO', "Unable to unset an environment variable in \".env\" at line 1.\n...export FOO...\n ^ line 1 offset 10"],
4545
['FOO=${FOO', "Unclosed braces on variable expansion in \".env\" at line 1.\n...FOO=\${FOO...\n ^ line 1 offset 9"],
4646
['FOO= BAR', "Whitespace are not supported before the value in \".env\" at line 1.\n...FOO= BAR...\n ^ line 1 offset 4"],
@@ -115,6 +115,7 @@ public function getEnvData()
115115
['FOO="bar\rfoo"', ['FOO' => "bar\rfoo"]],
116116
['FOO=\'bar\nfoo\'', ['FOO' => 'bar\nfoo']],
117117
['FOO=\'bar\rfoo\'', ['FOO' => 'bar\rfoo']],
118+
["FOO='bar\nfoo'", ['FOO' => "bar\nfoo"]],
118119
['FOO=" FOO "', ['FOO' => ' FOO ']],
119120
['FOO=" "', ['FOO' => ' ']],
120121
['PATH="c:\\\\"', ['PATH' => 'c:\\']],

src/Symfony/Component/HttpClient/Response/CurlResponse.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ public function __destruct()
227227
*/
228228
private function close(): void
229229
{
230+
if (self::$performing) {
231+
$this->multi->handlesActivity[$this->id][] = null;
232+
$this->multi->handlesActivity[$this->id][] = new TransportException('Response has been canceled.');
233+
234+
return;
235+
}
236+
230237
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
231238
curl_multi_remove_handle($this->multi->handle, $this->handle);
232239
curl_setopt_array($this->handle, [
@@ -264,6 +271,12 @@ private static function schedule(self $response, array &$runningResponses): void
264271
private static function perform(CurlClientState $multi, array &$responses = null): void
265272
{
266273
if (self::$performing) {
274+
if ($responses) {
275+
$response = current($responses);
276+
$multi->handlesActivity[(int) $response->handle][] = null;
277+
$multi->handlesActivity[(int) $response->handle][] = new TransportException(sprintf('Userland callback cannot use the client nor the response while processing "%s".', curl_getinfo($response->handle, CURLINFO_EFFECTIVE_URL)));
278+
}
279+
267280
return;
268281
}
269282

@@ -370,8 +383,15 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
370383

371384
curl_setopt($ch, CURLOPT_PRIVATE, 'content');
372385

373-
if (!$content && $options['buffer'] instanceof \Closure && $options['buffer']($headers)) {
374-
$content = fopen('php://temp', 'w+');
386+
try {
387+
if (!$content && $options['buffer'] instanceof \Closure && $options['buffer']($headers)) {
388+
$content = fopen('php://temp', 'w+');
389+
}
390+
} catch (\Throwable $e) {
391+
$multi->handlesActivity[$id][] = null;
392+
$multi->handlesActivity[$id][] = $e;
393+
394+
return 0;
375395
}
376396
} elseif (null !== $info['redirect_url'] && $logger) {
377397
$logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url']));

src/Symfony/Component/HttpClient/Response/MockResponse.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,7 @@ public static function fromRequest(string $method, string $url, array $options,
105105
$response->requestOptions = $options;
106106
$response->id = ++self::$idSequence;
107107

108-
if (($options['buffer'] ?? null) instanceof \Closure) {
109-
$response->content = $options['buffer']($mock->getHeaders(false)) ? fopen('php://temp', 'w+') : null;
110-
} else {
108+
if (!($options['buffer'] ?? null) instanceof \Closure) {
111109
$response->content = true === ($options['buffer'] ?? true) ? fopen('php://temp', 'w+') : null;
112110
}
113111
$response->initializer = static function (self $response) {
@@ -184,6 +182,10 @@ protected static function perform(ClientState $multi, array &$responses): void
184182
$response->headers = $chunk[1]->getHeaders(false);
185183
self::readResponse($response, $chunk[0], $chunk[1], $offset);
186184
$multi->handlesActivity[$id][] = new FirstChunk();
185+
186+
if (($response->requestOptions['buffer'] ?? null) instanceof \Closure) {
187+
$response->content = $response->requestOptions['buffer']($response->headers) ? fopen('php://temp', 'w+') : null;
188+
}
187189
} catch (\Throwable $e) {
188190
$multi->handlesActivity[$id][] = null;
189191
$multi->handlesActivity[$id][] = $e;

0 commit comments

Comments
 (0)
0