Browse Source

Merge branch '4.next' into 5.x

ADmad 3 years ago
parent
commit
3c38f41590

+ 37 - 12
src/Error/Renderer/ConsoleExceptionRenderer.php

@@ -67,23 +67,48 @@ class ConsoleExceptionRenderer implements ExceptionRendererInterface
      */
     public function render(): ResponseInterface|string
     {
+        $exceptions = [$this->error];
+        $previous = $this->error->getPrevious();
+        while ($previous !== null) {
+            $exceptions[] = $previous;
+            $previous = $previous->getPrevious();
+        }
         $out = [];
-        $out[] = sprintf(
-            '<error>[%s] %s</error> in %s on line %s',
-            get_class($this->error),
-            $this->error->getMessage(),
-            $this->error->getFile(),
-            $this->error->getLine()
-        );
+        foreach ($exceptions as $i => $error) {
+            $out = array_merge($out, $this->renderException($error, $i));
+        }
+
+        return join("\n", $out);
+    }
+
+    /**
+     * Render an individual exception
+     *
+     * @param \Throwable $exception The exception to render.
+     * @param int $index Exception index in the chain
+     * @return array
+     */
+    protected function renderException(Throwable $exception, int $index): array
+    {
+        $out = [
+            sprintf(
+                '<error>%s[%s] %s</error> in %s on line %s',
+                $index > 0 ? 'Caused by ' : '',
+                get_class($exception),
+                $exception->getMessage(),
+                $exception->getFile(),
+                $exception->getLine()
+            ),
+        ];
 
         $debug = Configure::read('debug');
-        if ($debug && $this->error instanceof CakeException) {
-            $attributes = $this->error->getAttributes();
+        if ($debug && $exception instanceof CakeException) {
+            $attributes = $exception->getAttributes();
             if ($attributes) {
                 $out[] = '';
                 $out[] = '<info>Exception Attributes</info>';
                 $out[] = '';
-                $out[] = var_export($this->error->getAttributes(), true);
+                $out[] = var_export($exception->getAttributes(), true);
             }
         }
 
@@ -91,11 +116,11 @@ class ConsoleExceptionRenderer implements ExceptionRendererInterface
             $out[] = '';
             $out[] = '<info>Stack Trace:</info>';
             $out[] = '';
-            $out[] = $this->error->getTraceAsString();
+            $out[] = $exception->getTraceAsString();
             $out[] = '';
         }
 
-        return join("\n", $out);
+        return $out;
     }
 
     /**

+ 9 - 3
src/Http/Client/FormData.php

@@ -17,6 +17,7 @@ namespace Cake\Http\Client;
 
 use Countable;
 use finfo;
+use Psr\Http\Message\UploadedFileInterface;
 use Stringable;
 
 /**
@@ -102,7 +103,7 @@ class FormData implements Countable, Stringable
         if (is_string($name)) {
             if (is_array($value)) {
                 $this->addRecursive($name, $value);
-            } elseif (is_resource($value)) {
+            } elseif (is_resource($value) || $value instanceof UploadedFileInterface) {
                 $this->addFile($name, $value);
             } else {
                 $this->_parts[] = $this->newPart($name, (string)$value);
@@ -137,7 +138,8 @@ class FormData implements Countable, Stringable
      * or a file handle.
      *
      * @param string $name The name to use.
-     * @param mixed $value Either a string filename, or a filehandle.
+     * @param \Psr\Http\Message\UploadedFileInterface|resource|string $value Either a string filename, or a filehandle,
+     *  or a UploadedFileInterface instance.
      * @return \Cake\Http\Client\FormDataPart
      */
     public function addFile(string $name, mixed $value): FormDataPart
@@ -146,7 +148,11 @@ class FormData implements Countable, Stringable
 
         $filename = false;
         $contentType = 'application/octet-stream';
-        if (is_resource($value)) {
+        if ($value instanceof UploadedFileInterface) {
+            $content = (string)$value->getStream();
+            $contentType = $value->getClientMediaType();
+            $filename = $value->getClientFilename();
+        } elseif (is_resource($value)) {
             $content = stream_get_contents($value);
             if (stream_is_local($value)) {
                 $finfo = new finfo(FILEINFO_MIME);

+ 20 - 0
tests/TestCase/Error/ExceptionTrapTest.php

@@ -27,6 +27,7 @@ use Cake\Log\Log;
 use Cake\TestSuite\TestCase;
 use Cake\Utility\Text;
 use InvalidArgumentException;
+use RuntimeException;
 use Throwable;
 
 class ExceptionTrapTest extends TestCase
@@ -146,6 +147,25 @@ class ExceptionTrapTest extends TestCase
         $this->assertStringContainsString('->testHandleExceptionConsoleRenderingWithStack', $out[0]);
     }
 
+    public function testHandleExceptionConsoleRenderingWithPrevious()
+    {
+        $output = new StubConsoleOutput();
+        $trap = new ExceptionTrap([
+            'exceptionRenderer' => ConsoleExceptionRenderer::class,
+            'stderr' => $output,
+            'trace' => true,
+        ]);
+        $previous = new RuntimeException('underlying error');
+        $error = new InvalidArgumentException('nope', 0, $previous);
+
+        $trap->handleException($error);
+        $out = $output->messages();
+
+        $this->assertStringContainsString('nope', $out[0]);
+        $this->assertStringContainsString('Caused by [RuntimeException] underlying error', $out[0]);
+        $this->assertEquals(2, substr_count($out[0], 'Stack Trace'));
+    }
+
     public function testHandleExceptionConsoleWithAttributes()
     {
         $output = new StubConsoleOutput();

+ 31 - 0
tests/TestCase/Http/Client/FormDataTest.php

@@ -17,6 +17,7 @@ namespace Cake\Test\TestCase\Http\Client;
 
 use Cake\Http\Client\FormData;
 use Cake\TestSuite\TestCase;
+use Laminas\Diactoros\UploadedFile;
 
 /**
  * Test case for FormData.
@@ -182,6 +183,36 @@ class FormDataTest extends TestCase
     }
 
     /**
+     * Test adding a part with a UploadedFileInterface instance.
+     */
+    public function testAddFileUploadedFile(): void
+    {
+        $file = new UploadedFile(
+            CORE_PATH . 'VERSION.txt',
+            filesize(CORE_PATH . 'VERSION.txt'),
+            0,
+            'VERSION.txt',
+            'text/plain'
+        );
+
+        $data = new FormData();
+        $data->add('upload', $file);
+        $boundary = $data->boundary();
+        $result = (string)$data;
+
+        $expected = [
+            '--' . $boundary,
+            'Content-Disposition: form-data; name="upload"; filename="VERSION.txt"',
+            'Content-Type: text/plain',
+            '',
+            (string)$file->getStream(),
+            '--' . $boundary . '--',
+            '',
+        ];
+        $this->assertSame(implode("\r\n", $expected), $result);
+    }
+
+    /**
      * Test contentType method.
      */
     public function testContentType(): void