Commit f2b371e747d for php.net

commit f2b371e747d26351cacba99d7fd696a5844e83fb
Author: Jakub Zelenka <bukka@php.net>
Date:   Thu May 28 22:54:36 2026 +0200

    Add new stream erros API

    This introduces new stream error handling that allows configurable
    handling of stream errors. This can be configured in stream contex.

    All streams, ext/standard and ext/phar stream errors are converted to
    use the new API.

    RFC: https://wiki.php.net/rfc/stream_errors

    Closes GH-20524

diff --git a/NEWS b/NEWS
index 700b958f7df..3c4d6993f46 100644
--- a/NEWS
+++ b/NEWS
@@ -234,10 +234,15 @@ PHP                                                                        NEWS
     http(s) stream wrapper). (David Carlier)

 - Streams:
+  . Added new stream errors API including new StreamException, StreamError
+    classes, StreamErrorStore, StreamErrorMode, StreamErrorCode enums,
+    stream_last_errors() and stream_clear_errors() functions, error_mode,
+    error_store and error_handler stream context options and extending some
+    stream functions with context param. (Jakub Zelenka)
   . Added so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt stream
-    socket context options.
+    socket context options. (Jakub Zelenka)
   . Added so_reuseaddr streams context socket option that allows disabling
-    address reuse.
+    address reuse. (Jakub Zelenka)
   . Fixed bug GH-20370 (User stream filters could violate typed property
     constraints). (alexandre-daubois)
   . Allowed filtered streams to be casted as fd for select. (Jakub Zelenka)
diff --git a/UPGRADING b/UPGRADING
index 3d6c89e90e0..c996f963a34 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -207,6 +207,10 @@ PHP 8.6 UPGRADE NOTES
     This makes it possible to override the timestamp and names of files.

 - Streams:
+  . Added new stream errors API including new classes, enums, functions and
+    internal API. It is controlled using error_mode, error_store and
+    error_handler stream context options.
+    RFC: https://wiki.php.net/rfc/stream_errors
   . Added stream socket context option so_reuseaddr that allows disabling
     address reuse (SO_REUSEADDR) and explicitly uses SO_EXCLUSIVEADDRUSE on
     Windows.
@@ -309,6 +313,8 @@ PHP 8.6 UPGRADE NOTES
   . `clamp()` returns the given value if in range, else return the nearest
     bound.
     RFC: https://wiki.php.net/rfc/clamp_v2
+  . `stream_last_errors()` and `stream_clear_errors()`.
+    RFC: https://wiki.php.net/rfc/stream_errors

 - Zip:
   . Added ZipArchive::openString() method.
@@ -326,6 +332,12 @@ PHP 8.6 UPGRADE NOTES
 - Standard:
   . enum SortDirection
     RFC: https://wiki.php.net/rfc/sort_direction_enum
+  . StreamError
+  . StreamException
+  . enum StreamErrorStore
+  . enum StreamErrorMode
+  . enum StreamErrorCode
+    RFC: https://wiki.php.net/rfc/stream_errors

 ========================================
 8. Removed Extensions and SAPIs
diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h
index b7b118c710c..b76785283dc 100644
--- a/Zend/Optimizer/zend_func_infos.h
+++ b/Zend/Optimizer/zend_func_infos.h
@@ -596,6 +596,7 @@ static const func_info_t func_infos[] = {
 	F1("stream_get_line", MAY_BE_STRING|MAY_BE_FALSE),
 	F1("stream_resolve_include_path", MAY_BE_STRING|MAY_BE_FALSE),
 	F1("stream_get_wrappers", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
+	F1("stream_last_errors", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_OBJECT),
 	F1("stream_get_transports", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
 #if defined(HAVE_GETTIMEOFDAY)
 	F1("uniqid", MAY_BE_STRING),
diff --git a/build/gen_stub.php b/build/gen_stub.php
index 619c4c905b6..34bbd34fd37 100755
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@ -3385,6 +3385,7 @@ public function __construct(
         private /* readonly */ array $propertyInfos,
         public array $funcInfos,
         private readonly array $enumCaseInfos,
+        private readonly bool $generateCNameTable,
         public readonly ?string $cond,
         public ?int $phpVersionIdMinimumCompatibility,
         public readonly bool $isUndocumentable,
@@ -3599,6 +3600,25 @@ public function getCDeclarations(): string

         $code .= "} {$cEnumName};\n";

+        $caseCount = count($this->enumCaseInfos);
+
+        if ($this->generateCNameTable && $caseCount > 0) {
+            $cPrefix = 'ZEND_ENUM_' . str_replace('\\', '_', $this->name->toString());
+            $useGuard = $cPrefix . '_USE_NAME_TABLE';
+
+            $code .= "\n#define {$cPrefix}_CASE_COUNT {$caseCount}\n";
+            $code .= "\n#ifdef {$useGuard}\n";
+            $code .= "static const char *{$cEnumName}_case_names[{$cPrefix}_CASE_COUNT + 1] = {\n";
+
+            foreach ($this->enumCaseInfos as $case) {
+                $cName = $cPrefix . '_' . $case->name->case;
+                $code .= "\t[{$cName}] = \"{$case->name->case}\",\n";
+            }
+
+            $code .= "};\n";
+            $code .= "#endif\n";
+        }
+
         if ($this->cond) {
             $code .= "#endif\n";
         }
@@ -5149,6 +5169,7 @@ function parseClass(
     $isStrictProperties = array_key_exists('strict-properties', $tagMap);
     $isNotSerializable = array_key_exists('not-serializable', $tagMap);
     $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap);
+    $generateCNameTable = array_key_exists('c-name-table', $tagMap);
     foreach ($tags as $tag) {
         if ($tag->name === 'alias') {
             $alias = $tag->getValue();
@@ -5210,6 +5231,7 @@ function parseClass(
         $properties,
         $methods,
         $enumCases,
+        $generateCNameTable,
         $cond,
         $minimumPhpVersionIdCompatibility,
         $isUndocumentable
diff --git a/configure.ac b/configure.ac
index 6c517ecc0a1..7b1fc3af784 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1689,6 +1689,7 @@ PHP_ADD_SOURCES([main/streams], m4_normalize([
     memory.c
     mmap.c
     plain_wrapper.c
+    stream_errors.c
     streams.c
     transports.c
     userspace.c
diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c
index 6c356c04e5e..b1c8c4747cc 100644
--- a/ext/phar/dirstream.c
+++ b/ext/phar/dirstream.c
@@ -247,27 +247,32 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path,
 {
 	char *error;

-	php_url *resource = phar_parse_url(wrapper, path, mode, options);
-	if (!resource) {
-		php_stream_wrapper_log_error(wrapper, options, "phar url \"%s\" is unknown", path);
+	php_url *resource = phar_parse_url(wrapper, context, path, mode, options);
+	if (resource == NULL) {
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+			"phar url \"%s\" is unknown", path);
 		return NULL;
 	}

 	/* we must have at the very least phar://alias.phar/ */
 	if (!resource->scheme || !resource->host || !resource->path) {
 		if (resource->host && !resource->path) {
-			php_stream_wrapper_log_error(wrapper, options, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", path, ZSTR_VAL(resource->host));
+			php_stream_wrapper_log_warn(wrapper, context, options, InvalidPath,
+				"phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)",
+				path, ZSTR_VAL(resource->host));
 			php_url_free(resource);
 			return NULL;
 		}
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\", must have at least phar://%s/", path, path);
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+			"phar error: invalid url \"%s\", must have at least phar://%s/", path, path);
 		return NULL;
 	}

 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar url \"%s\"", path);
+		php_stream_wrapper_log_warn(wrapper, context, options, ProtocolUnsupported,
+			"phar error: not a phar url \"%s\"", path);
 		return NULL;
 	}

@@ -276,10 +281,11 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path,
 	const phar_archive_data *phar = phar_get_archive(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error);
 	if (!phar) {
 		if (error) {
-			php_stream_wrapper_log_error(wrapper, options, "%s", error);
+			php_stream_wrapper_log_warn(wrapper, context, options, NotFound, "%s", error);
 			efree(error);
 		} else {
-			php_stream_wrapper_log_error(wrapper, options, "phar file \"%s\" is unknown", ZSTR_VAL(resource->host));
+			php_stream_wrapper_log_warn(wrapper, context, options, NotFound,
+				"phar file \"%s\" is unknown", ZSTR_VAL(resource->host));
 		}
 		php_url_free(resource);
 		return NULL;
@@ -344,7 +350,8 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
 	/* pre-readonly check, we need to know if this is a data phar */
 	zend_string *arch = phar_split_fname(url_from, strlen(url_from), NULL, 2, 2);
 	if (!arch) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", no phar archive specified", url_from);
+		php_stream_wrapper_log_warn(wrapper, context, options, NotFound,
+			"phar error: cannot create directory \"%s\", no phar archive specified", url_from);
 		return 0;
 	}

@@ -353,30 +360,35 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
 	zend_string_release_ex(arch, false);

 	if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", write operations disabled", url_from);
+		php_stream_wrapper_log_warn(wrapper, context, options, Readonly,
+			"phar error: cannot create directory \"%s\", write operations disabled", url_from);
 		return 0;
 	}

-	if ((resource = phar_parse_url(wrapper, url_from, "w", options)) == NULL) {
+	if ((resource = phar_parse_url(wrapper, context, url_from, "w", options)) == NULL) {
 		return 0;
 	}

 	/* we must have at the very least phar://alias.phar/internalfile.php */
 	if (!resource->scheme || !resource->host || !resource->path) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url_from);
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+			"phar error: invalid url \"%s\"", url_from);
 		return 0;
 	}

 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url_from);
+		php_stream_wrapper_log_warn(wrapper, context, options, ProtocolUnsupported,
+			"phar error: not a phar stream url \"%s\"", url_from);
 		return 0;
 	}

 	phar = phar_get_archive(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error);
 	if (!phar) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path) + 1, ZSTR_VAL(resource->host), error);
+		php_stream_wrapper_log_warn(wrapper, context, options, MkdirFailed,
+			"phar error: cannot create directory \"%s\" in phar \"%s\", error retrieving phar information: %s",
+			ZSTR_VAL(resource->path) + 1, ZSTR_VAL(resource->host), error);
 		efree(error);
 		php_url_free(resource);
 		return 0;
@@ -389,13 +401,17 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
 			zend_string_efree(e->filename);
 			efree(e);
 		}
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", directory already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
+		php_stream_wrapper_log_warn(wrapper, context, options, AlreadyExists,
+			"phar error: cannot create directory \"%s\" in phar \"%s\", directory already exists",
+			ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
 		php_url_free(resource);
 		return 0;
 	}

 	if (error) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
+		php_stream_wrapper_log_warn(wrapper, context, options, MkdirFailed,
+			"phar error: cannot create directory \"%s\" in phar \"%s\", %s",
+			ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
 		efree(error);
 		php_url_free(resource);
 		return 0;
@@ -403,13 +419,17 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo

 	if (phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, ZSTR_LEN(resource->path) - 1, 0, &error, true)) {
 		/* entry exists as a file */
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", file already exists", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
+		php_stream_wrapper_log_warn(wrapper, context, options, AlreadyExists,
+			"phar error: cannot create directory \"%s\" in phar \"%s\", file already exists",
+			ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
 		php_url_free(resource);
 		return 0;
 	}

 	if (error) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
+		php_stream_wrapper_log_warn(wrapper, context, options, MkdirFailed,
+			"phar error: cannot create directory \"%s\" in phar \"%s\", %s",
+			ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
 		efree(error);
 		php_url_free(resource);
 		return 0;
@@ -437,9 +457,10 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
 	entry.flags = PHAR_ENT_PERM_DEF_DIR;
 	entry.old_flags = PHAR_ENT_PERM_DEF_DIR;

-	void *had_been_added = zend_hash_add_mem(&phar->manifest, entry.filename, &entry, sizeof(phar_entry_info));
-	if (!had_been_added) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed", ZSTR_VAL(entry.filename), ZSTR_VAL(phar->fname));
+	if (NULL == zend_hash_add_mem(&phar->manifest, entry.filename, &entry, sizeof(phar_entry_info))) {
+		php_stream_wrapper_log_warn(wrapper, context, options, MkdirFailed,
+			"phar error: cannot create directory \"%s\" in phar \"%s\", adding to manifest failed",
+			ZSTR_VAL(entry.filename), ZSTR_VAL(phar->fname));
 		zend_string_efree(entry.filename);
 		return 0;
 	}
@@ -447,7 +468,9 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
 	phar_flush(phar, &error);

 	if (error) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(entry.filename), ZSTR_VAL(phar->fname), error);
+		php_stream_wrapper_log_warn(wrapper, context, options, MkdirFailed,
+			"phar error: cannot create directory \"%s\" in phar \"%s\", %s",
+			ZSTR_VAL(entry.filename), ZSTR_VAL(phar->fname), error);
 		zend_hash_del(&phar->manifest, entry.filename);
 		efree(error);
 		return 0;
@@ -468,7 +491,8 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 	/* pre-readonly check, we need to know if this is a data phar */
 	zend_string *arch = phar_split_fname(url, strlen(url), NULL, 2, 2);
 	if (!arch) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url);
+		php_stream_wrapper_log_warn(wrapper, context, options, NotFound,
+			"phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url);
 		return 0;
 	}

@@ -477,11 +501,12 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 	zend_string_release_ex(arch, false);

 	if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot rmdir directory \"%s\", write operations disabled", url);
+		php_stream_wrapper_log_warn(wrapper, context, options, Readonly,
+			"phar error: cannot rmdir directory \"%s\", write operations disabled", url);
 		return 0;
 	}

-	php_url *resource = phar_parse_url(wrapper, url, "w", options);
+	php_url *resource = phar_parse_url(wrapper, context, url, "w", options);
 	if (!resource) {
 		return 0;
 	}
@@ -489,19 +514,23 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 	/* we must have at the very least phar://alias.phar/internalfile.php */
 	if (!resource->scheme || !resource->host || !resource->path) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url);
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+			"phar error: invalid url \"%s\"", url);
 		return 0;
 	}

 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url);
+		php_stream_wrapper_log_warn(wrapper, context, options, ProtocolUnsupported,
+			"phar error: not a phar stream url \"%s\"", url);
 		return 0;
 	}

 	phar = phar_get_archive(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, &error);
 	if (!phar) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", error retrieving phar information: %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
+		php_stream_wrapper_log_warn(wrapper, context, options, RmdirFailed,
+			"phar error: cannot remove directory \"%s\" in phar \"%s\", error retrieving phar information: %s",
+			ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
 		efree(error);
 		php_url_free(resource);
 		return 0;
@@ -512,10 +541,14 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 	phar_entry_info *entry = phar_get_entry_info_dir(phar, ZSTR_VAL(resource->path) + 1, path_len, 2, &error, true);
 	if (!entry) {
 		if (error) {
-			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
+			php_stream_wrapper_log_warn(wrapper, context, options, RmdirFailed,
+				"phar error: cannot remove directory \"%s\" in phar \"%s\", %s",
+				ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host), error);
 			efree(error);
 		} else {
-			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", directory does not exist", ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
+			php_stream_wrapper_log_warn(wrapper, context, options, NotFound,
+				"phar error: cannot remove directory \"%s\" in phar \"%s\", directory does not exist",
+				ZSTR_VAL(resource->path)+1, ZSTR_VAL(resource->host));
 		}
 		php_url_free(resource);
 		return 0;
@@ -529,7 +562,8 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 				zend_string_starts_with_cstr(str_key, ZSTR_VAL(resource->path)+1, path_len)
 				&& IS_SLASH(ZSTR_VAL(str_key)[path_len])
 			) {
-				php_stream_wrapper_log_error(wrapper, options, "phar error: Directory not empty");
+				php_stream_wrapper_log_warn(wrapper, context, options, RmdirFailed,
+					"phar error: Directory not empty");
 				if (entry->is_temp_dir) {
 					zend_string_efree(entry->filename);
 					efree(entry);
@@ -545,7 +579,8 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 				zend_string_starts_with_cstr(str_key, ZSTR_VAL(resource->path)+1, path_len)
 				&& IS_SLASH(ZSTR_VAL(str_key)[path_len])
 			) {
-				php_stream_wrapper_log_error(wrapper, options, "phar error: Directory not empty");
+				php_stream_wrapper_log_warn(wrapper, context, options, RmdirFailed,
+					"phar error: Directory not empty");
 				if (entry->is_temp_dir) {
 					zend_string_efree(entry->filename);
 					efree(entry);
@@ -566,7 +601,9 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 		phar_flush(phar, &error);

 		if (error) {
-			php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\" in phar \"%s\", %s", ZSTR_VAL(entry->filename), ZSTR_VAL(phar->fname), error);
+			php_stream_wrapper_log_warn(wrapper, context, options, RmdirFailed,
+				"phar error: cannot remove directory \"%s\" in phar \"%s\", %s",
+				ZSTR_VAL(entry->filename), ZSTR_VAL(phar->fname), error);
 			php_url_free(resource);
 			efree(error);
 			return 0;
diff --git a/ext/phar/dirstream.h b/ext/phar/dirstream.h
index 41899f88a99..179150229e3 100644
--- a/ext/phar/dirstream.h
+++ b/ext/phar/dirstream.h
@@ -22,7 +22,7 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
 #ifdef PHAR_DIRSTREAM
 #include "ext/standard/url.h"

-php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options);
+php_url* phar_parse_url(php_stream_wrapper *wrapper, php_stream_context *context, const char *filename, const char *mode, int options);

 /* directory handlers */
 static ssize_t phar_dir_write(php_stream *stream, const char *buf, size_t count);
diff --git a/ext/phar/stream.c b/ext/phar/stream.c
index 772f9c2769a..3fee251d258 100644
--- a/ext/phar/stream.c
+++ b/ext/phar/stream.c
@@ -55,7 +55,8 @@ const php_stream_wrapper php_stream_phar_wrapper = {
 /**
  * Open a phar file for streams API
  */
-php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options) /* {{{ */
+php_url* phar_parse_url(php_stream_wrapper *wrapper, php_stream_context *context,
+		const char *filename, const char *mode, int options)
 {
 	php_url *resource;
 	char *error;
@@ -65,7 +66,8 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 	}
 	if (mode[0] == 'a') {
 		if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
-			php_stream_wrapper_log_error(wrapper, options, "phar error: open mode append not supported");
+			php_stream_wrapper_log_warn(wrapper, context, options, ModeNotSupported,
+				"phar error: open mode append not supported");
 		}
 		return NULL;
 	}
@@ -76,9 +78,13 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 	if (!arch) {
 		if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
 			if (arch_error && !entry) {
-				php_stream_wrapper_log_error(wrapper, options, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", filename, arch_error);
+				php_stream_wrapper_log_warn(wrapper, context, options, InvalidPath,
+					"phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)",
+					filename, arch_error);
+				arch = NULL;
 			} else {
-				php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url or non-existent phar \"%s\"", filename);
+				php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+					"phar error: invalid url or non-existent phar \"%s\"", filename);
 			}
 		}
 		return NULL;
@@ -109,7 +115,8 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 		}
 		if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) {
 			if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
-				php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly");
+				php_stream_wrapper_log_warn(wrapper, context, options, Readonly,
+					"phar error: write operations disabled by the php.ini setting phar.readonly");
 			}
 			php_url_free(resource);
 			return NULL;
@@ -118,7 +125,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 		{
 			if (error) {
 				if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
-					php_stream_wrapper_log_error(wrapper, options, "%s", error);
+					php_stream_wrapper_log_warn(wrapper, NULL, options, OpenFailed, "%s", error);
 				}
 				efree(error);
 			}
@@ -129,7 +136,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 			if (error) {
 				spprintf(&error, 0, "Cannot open cached phar '%s' as writeable, copy on write failed", ZSTR_VAL(resource->host));
 				if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
-					php_stream_wrapper_log_error(wrapper, options, "%s", error);
+					php_stream_wrapper_log_warn(wrapper, context, options, OpenFailed, "%s", error);
 				}
 				efree(error);
 			}
@@ -141,7 +148,7 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 		{
 			if (error) {
 				if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
-					php_stream_wrapper_log_error(wrapper, options, "%s", error);
+					php_stream_wrapper_log_warn(wrapper, context, options, NotFound, "%s", error);
 				}
 				efree(error);
 			}
@@ -151,7 +158,6 @@ php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const
 	}
 	return resource;
 }
-/* }}} */

 /**
  * used for fopen('phar://...') and company
@@ -166,7 +172,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
 	php_stream *fpf;
 	zval *pzoption, *metadata;

-	php_url *resource = phar_parse_url(wrapper, path, mode, options);
+	php_url *resource = phar_parse_url(wrapper, context, path, mode, options);
 	if (!resource) {
 		return NULL;
 	}
@@ -174,13 +180,15 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
 	/* we must have at the very least phar://alias.phar/internalfile.php */
 	if (!resource->scheme || !resource->host || !resource->path) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", path);
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+			"phar error: invalid url \"%s\"", path);
 		return NULL;
 	}

 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", path);
+		php_stream_wrapper_log_warn(wrapper, context, options, ProtocolUnsupported,
+			"phar error: not a phar stream url \"%s\"", path);
 		return NULL;
 	}

@@ -191,10 +199,11 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
 	if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) {
 		if (NULL == (idata = phar_get_or_create_entry_data(resource->host, internal_file, strlen(internal_file), mode, 0, &error, true, time(NULL)))) {
 			if (error) {
-				php_stream_wrapper_log_error(wrapper, options, "%s", error);
+				php_stream_wrapper_log_warn(wrapper, context, options, CreateFailed, "%s", error);
 				efree(error);
 			} else {
-				php_stream_wrapper_log_error(wrapper, options, "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, ZSTR_VAL(resource->host));
+				php_stream_wrapper_log_warn(wrapper, context, options, CreateFailed,
+					"phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, ZSTR_VAL(resource->host));
 			}
 			efree(internal_file);
 			php_url_free(resource);
@@ -232,7 +241,8 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
 			/* retrieve the stub */
 			phar = phar_get_archive(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), NULL, 0, NULL);
 			if (!phar) {
-				php_stream_wrapper_log_error(wrapper, options, "file %s is not a valid phar archive", ZSTR_VAL(resource->host));
+				php_stream_wrapper_log_warn(wrapper, context, options, InvalidFormat,
+					"file %s is not a valid phar archive", ZSTR_VAL(resource->host));
 				efree(internal_file);
 				php_url_free(resource);
 				return NULL;
@@ -252,7 +262,8 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
 				if (stream == NULL) {
 					stream = phar_open_archive_fp(phar);
 					if (UNEXPECTED(!stream)) {
-						php_stream_wrapper_log_error(wrapper, options, "phar error: could not reopen phar \"%s\"", ZSTR_VAL(resource->host));
+						php_stream_wrapper_log_warn(wrapper, context, options, OpenFailed,
+							"phar error: could not reopen phar \"%s\"", ZSTR_VAL(resource->host));
 						efree(internal_file);
 						php_url_free(resource);
 						return NULL;
@@ -289,10 +300,11 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
 		if ((FAILURE == phar_get_entry_data(&idata, resource->host, internal_file, strlen(internal_file), "r", 0, &error, false)) || !idata) {
 idata_error:
 			if (error) {
-				php_stream_wrapper_log_error(wrapper, options, "%s", error);
+				php_stream_wrapper_log_warn(wrapper, context, options, NotFound, "%s", error);
 				efree(error);
 			} else {
-				php_stream_wrapper_log_error(wrapper, options, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, ZSTR_VAL(resource->host));
+				php_stream_wrapper_log_warn(wrapper, context, options, NotFound,
+					"phar error: \"%s\" is not a file in phar \"%s\"", internal_file, ZSTR_VAL(resource->host));
 			}
 			efree(internal_file);
 			php_url_free(resource);
@@ -310,7 +322,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha

 	/* check length, crc32 */
 	if (!idata->internal_file->is_crc_checked && phar_postprocess_file(idata, idata->internal_file->crc32, &error, 2) != SUCCESS) {
-		php_stream_wrapper_log_error(wrapper, options, "%s", error);
+		php_stream_wrapper_log_warn(wrapper, context, options, ArchivingFailed, "%s", error);
 		efree(error);
 		phar_entry_delref(idata);
 		efree(internal_file);
@@ -441,7 +453,9 @@ static ssize_t phar_stream_write(php_stream *stream, const char *buf, size_t cou

 	php_stream_seek(data->fp, data->position + data->zero, SEEK_SET);
 	if (count != php_stream_write(data->fp, buf, count)) {
-		php_stream_wrapper_log_error(stream->wrapper, stream->flags, "phar error: Could not write %zu characters to \"%s\" in phar \"%s\"", count, ZSTR_VAL(data->internal_file->filename), ZSTR_VAL(data->phar->fname));
+		php_stream_warn(stream, WriteFailed,
+			"phar error: Could not write %zu characters to \"%s\" in phar \"%s\"",
+			count, ZSTR_VAL(data->internal_file->filename),  ZSTR_VAL(data->phar->fname));
 		return -1;
 	}
 	data->position = php_stream_tell(data->fp) - data->zero;
@@ -468,7 +482,7 @@ static int phar_stream_flush(php_stream *stream) /* {{{ */
 		data->internal_file->timestamp = time(0);
 		ret = phar_flush(data->phar, &error);
 		if (error) {
-			php_stream_wrapper_log_error(stream->wrapper, REPORT_ERRORS, "%s", error);
+			php_stream_warn(stream, FlushFailed, "%s", error);
 			efree(error);
 		}
 		return ret;
@@ -557,7 +571,7 @@ static int phar_wrapper_stat(php_stream_wrapper *wrapper, const char *url, int f
 	char *internal_file;
 	size_t internal_file_len;

-	php_url *resource = phar_parse_url(wrapper, url, "r", flags|PHP_STREAM_URL_STAT_QUIET);
+	php_url *resource = phar_parse_url(wrapper, context, url, "r", flags|PHP_STREAM_URL_STAT_QUIET);
 	if (!resource) {
 		return FAILURE;
 	}
@@ -660,22 +674,25 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
 	size_t internal_file_len;
 	phar_entry_data *idata;

-	php_url *resource = phar_parse_url(wrapper, url, "rb", options);
+	php_url *resource = phar_parse_url(wrapper, context, url, "rb", options);
 	if (!resource) {
-		php_stream_wrapper_log_error(wrapper, options, "phar error: unlink failed");
+		php_stream_wrapper_log_warn(wrapper, context, options, UnlinkFailed,
+			"phar error: unlink failed");
 		return 0;
 	}

 	/* we must have at the very least phar://alias.phar/internalfile.php */
 	if (!resource->scheme || !resource->host || !resource->path) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: invalid url \"%s\"", url);
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+			"phar error: invalid url \"%s\"", url);
 		return 0;
 	}

 	if (!zend_string_equals_literal_ci(resource->scheme, "phar")) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: not a phar stream url \"%s\"", url);
+		php_stream_wrapper_log_warn(wrapper, context, options, ProtocolUnsupported,
+			"phar error: not a phar stream url \"%s\"", url);
 		return 0;
 	}

@@ -684,7 +701,8 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
 	const phar_archive_data *pphar = zend_hash_find_ptr(&(PHAR_G(phar_fname_map)), resource->host);
 	if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) {
 		php_url_free(resource);
-		php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly");
+		php_stream_wrapper_log_warn(wrapper, context, options, Readonly,
+			"phar error: write operations disabled by the php.ini setting phar.readonly");
 		return 0;
 	}

@@ -694,10 +712,12 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
 	if (FAILURE == phar_get_entry_data(&idata, resource->host, internal_file, internal_file_len, "r", 0, &error, true)) {
 		/* constraints of fp refcount were not met */
 		if (error) {
-			php_stream_wrapper_log_error(wrapper, options, "unlink of \"%s\" failed: %s", url, error);
+			php_stream_wrapper_log_warn(wrapper, context, options, UnlinkFailed,
+				"unlink of \"%s\" failed: %s", url, error);
 			efree(error);
 		} else {
-			php_stream_wrapper_log_error(wrapper, options, "unlink of \"%s\" failed, file does not exist", url);
+			php_stream_wrapper_log_warn(wrapper, context, options, NotFound,
+				"unlink of \"%s\" failed, file does not exist", url);
 		}
 		efree(internal_file);
 		php_url_free(resource);
@@ -705,7 +725,9 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
 	}
 	if (idata->internal_file->fp_refcount > 1) {
 		/* more than just our fp resource is open for this file */
-		php_stream_wrapper_log_error(wrapper, options, "phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink", internal_file, ZSTR_VAL(resource->host));
+		php_stream_wrapper_log_warn(wrapper, context, options, UnlinkFailed,
+			"phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink",
+			internal_file, ZSTR_VAL(resource->host));
 		efree(internal_file);
 		php_url_free(resource);
 		phar_entry_delref(idata);
@@ -715,7 +737,7 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
 	efree(internal_file);
 	phar_entry_remove(idata, &error);
 	if (error) {
-		php_stream_wrapper_log_error(wrapper, options, "%s", error);
+		php_stream_wrapper_log_warn(wrapper, context, options, UnlinkFailed, "%s", error);
 		efree(error);
 	}
 	return 1;
@@ -730,23 +752,28 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from

 	error = NULL;

-	php_url *resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET);
+	php_url *resource_from = phar_parse_url(wrapper, context, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET);
 	if (!resource_from) {
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_from);
+		php_stream_wrapper_warn(wrapper, context, options, InvalidUrl,
+			"phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"",
+			url_from, url_to, url_from);
 		return 0;
 	}

 	phar_archive_data *pfrom = phar_get_archive(ZSTR_VAL(resource_from->host), ZSTR_LEN(resource_from->host), NULL, 0, NULL);
 	if (PHAR_G(readonly) && (!pfrom || !pfrom->is_data)) {
 		php_url_free(resource_from);
-		php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
+		php_stream_wrapper_warn(wrapper, context, options, Readonly,
+			"phar error: Write operations disabled by the php.ini setting phar.readonly");
 		return 0;
 	}

-	php_url *resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET);
+	php_url *resource_to = phar_parse_url(wrapper, context, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET);
 	if (!resource_to) {
 		php_url_free(resource_from);
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_to);
+		php_stream_wrapper_warn(wrapper, context, options, InvalidUrl,
+			"phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"",
+			url_from, url_to, url_to);
 		return 0;
 	}

@@ -754,14 +781,17 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 	if (PHAR_G(readonly) && (!pto || !pto->is_data)) {
 		php_url_free(resource_from);
 		php_url_free(resource_to);
-		php_error_docref(NULL, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
+		php_stream_wrapper_warn(wrapper, context, options, Readonly,
+			"phar error: Write operations disabled by the php.ini setting phar.readonly");
 		return 0;
 	}

 	if (!zend_string_equals(resource_from->host, resource_to->host)) {
 		php_url_free(resource_from);
 		php_url_free(resource_to);
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive", url_from, url_to);
+		php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
+			"phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive",
+			url_from, url_to);
 		return 0;
 	}

@@ -769,28 +799,36 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 	if (!resource_from->scheme || !resource_from->host || !resource_from->path) {
 		php_url_free(resource_from);
 		php_url_free(resource_to);
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_from);
+		php_stream_wrapper_warn(wrapper, context, options, InvalidUrl,
+			"phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"",
+			url_from, url_to, url_from);
 		return 0;
 	}

 	if (!resource_to->scheme || !resource_to->host || !resource_to->path) {
 		php_url_free(resource_from);
 		php_url_free(resource_to);
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_to);
+		php_stream_wrapper_warn(wrapper, context, options, InvalidUrl,
+			"phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"",
+			url_from, url_to, url_to);
 		return 0;
 	}

 	if (!zend_string_equals_literal_ci(resource_from->scheme, "phar")) {
 		php_url_free(resource_from);
 		php_url_free(resource_to);
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_from);
+		php_stream_wrapper_warn(wrapper, context, options, ProtocolUnsupported,
+			"phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"",
+			url_from, url_to, url_from);
 		return 0;
 	}

 	if (!zend_string_equals_literal_ci(resource_to->scheme, "phar")) {
 		php_url_free(resource_from);
 		php_url_free(resource_to);
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_to);
+		php_stream_wrapper_warn(wrapper, context, options, ProtocolUnsupported,
+			"phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"",
+			url_from, url_to, url_to);
 		return 0;
 	}

@@ -798,7 +836,8 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 	if (!phar) {
 		php_url_free(resource_from);
 		php_url_free(resource_to);
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
+		php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
+			"phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
 		efree(error);
 		return 0;
 	}
@@ -806,7 +845,9 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 	if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar)) {
 		php_url_free(resource_from);
 		php_url_free(resource_to);
-		php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable", url_from, url_to);
+		php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
+			"phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable",
+			url_from, url_to);
 		return 0;
 	}

@@ -818,7 +859,9 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 		if (entry->is_deleted) {
 			php_url_free(resource_from);
 			php_url_free(resource_to);
-			php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source has been deleted", url_from, url_to);
+			php_stream_wrapper_warn(wrapper, context, options, NotFound,
+				"phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source has been deleted",
+				url_from, url_to);
 			return 0;
 		}
 		/* transfer all data over to the new entry */
@@ -839,7 +882,8 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 		if (FAILURE == phar_copy_entry_fp(source, entry, &error)) {
 			php_url_free(resource_from);
 			php_url_free(resource_to);
-			php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
+			php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
+				"phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
 			efree(error);
 			zend_hash_del(&phar->manifest, entry->filename);
 			return 0;
@@ -853,7 +897,9 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 			/* file does not exist */
 			php_url_free(resource_from);
 			php_url_free(resource_to);
-			php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist", url_from, url_to);
+			php_stream_wrapper_warn(wrapper, context, options, NotFound,
+				"phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist",
+				url_from, url_to);
 			return 0;

 		}
@@ -932,7 +978,8 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 		if (error) {
 			php_url_free(resource_from);
 			php_url_free(resource_to);
-			php_error_docref(NULL, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
+			php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
+				"phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
 			efree(error);
 			return 0;
 		}
diff --git a/ext/phar/stream.h b/ext/phar/stream.h
index cf40e3129c6..ebac093402b 100644
--- a/ext/phar/stream.h
+++ b/ext/phar/stream.h
@@ -18,7 +18,7 @@
 BEGIN_EXTERN_C()
 #include "ext/standard/url.h"

-php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options);
+php_url* phar_parse_url(php_stream_wrapper *wrapper, php_stream_context *context, const char *filename, const char *mode, int options);
 ZEND_ATTRIBUTE_NONNULL void phar_entry_remove(phar_entry_data *idata, char **error);

 static php_stream* phar_wrapper_open_url(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
diff --git a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt
index 9d121070509..9ef87f42b22 100644
--- a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt
+++ b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt
@@ -17,5 +17,10 @@
 RoundingMode
 SortDirection
 StreamBucket
+StreamError
+StreamErrorCode
+StreamErrorMode
+StreamErrorStore
+StreamException
 __PHP_Incomplete_Class
 php_user_filter
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index 2a0e7a78673..0c1e2f8222a 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -335,6 +335,7 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */
 #endif
 	BASIC_MINIT_SUBMODULE(exec)

+	BASIC_MINIT_SUBMODULE(stream_errors)
 	BASIC_MINIT_SUBMODULE(user_streams)

 	php_register_url_stream_wrapper("php", &php_stream_php_wrapper);
diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php
index 1999c9b92be..b63d4f404a7 100644
--- a/ext/standard/basic_functions.stub.php
+++ b/ext/standard/basic_functions.stub.php
@@ -3385,7 +3385,10 @@ function soundex(string $string): string {}

 /* streamsfuncs.c */

-function stream_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, ?int $microseconds = null): int|false {}
+/**
+ * @param resource|null $context
+ */
+function stream_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, ?int $microseconds = null, $context = null): int|false {}

 /**
  * @return resource
@@ -3488,17 +3491,19 @@ function stream_socket_shutdown($stream, int $mode): bool {}

 #ifdef HAVE_SOCKETPAIR
 /**
+ * @param resource|null $context
  * @return array<int, resource>|false
  * @refcount 1
  */
-function stream_socket_pair(int $domain, int $type, int $protocol): array|false {}
+function stream_socket_pair(int $domain, int $type, int $protocol, $context = null): array|false {}
 #endif

 /**
  * @param resource $from
  * @param resource $to
+ * @param resource|null $context
  */
-function stream_copy_to_stream($from, $to, ?int $length = null, int $offset = 0): int|false {}
+function stream_copy_to_stream($from, $to, ?int $length = null, int $offset = 0, $context = null): int|false {}

 /**
  * @param resource $stream
@@ -3558,14 +3563,25 @@ function stream_resolve_include_path(string $filename): string|false {}
  */
 function stream_get_wrappers(): array {}

+/**
+ * @refcount 1
+ * @return array<int, StreamError>
+ */
+function stream_last_errors(): array {}
+
+function stream_clear_errors(): void {}
+
 /**
  * @return array<int, string>
  * @refcount 1
  */
 function stream_get_transports(): array {}

-/** @param resource|string $stream */
-function stream_is_local($stream): bool {}
+/**
+ * @param resource|string $stream
+ * @param resource|null $context
+ */
+function stream_is_local($stream, $context = null): bool {}

 /** @param resource $stream */
 function stream_isatty($stream): bool {}
diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h
index e51a837ffa4..ca72962d34e 100644
Binary files a/ext/standard/basic_functions_arginfo.h and b/ext/standard/basic_functions_arginfo.h differ
diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h
index b3eb25c5d98..8e92337ba37 100644
Binary files a/ext/standard/basic_functions_decl.h and b/ext/standard/basic_functions_decl.h differ
diff --git a/ext/standard/file.c b/ext/standard/file.c
index ba5b26f6d22..db0fc45385d 100644
--- a/ext/standard/file.c
+++ b/ext/standard/file.c
@@ -221,7 +221,9 @@ PHP_FUNCTION(flock)
 		Z_PARAM_ZVAL(wouldblock)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	php_flock_common(stream, operation, 2, wouldblock, return_value);
+	php_stream_error_operation_end_for_stream(stream);
 }
 /* }}} */

@@ -257,6 +259,8 @@ PHP_FUNCTION(get_meta_tags)
 		RETURN_FALSE;
 	}

+	php_stream_error_operation_begin();
+
 	array_init(return_value);

 	tok_last = TOK_EOF;
@@ -368,6 +372,8 @@ PHP_FUNCTION(get_meta_tags)
 	if (value) efree(value);
 	if (name) efree(name);
 	php_stream_close(md.stream);
+
+	php_stream_error_operation_end_for_stream(md.stream);
 }
 /* }}} */

@@ -402,12 +408,13 @@ PHP_FUNCTION(file_get_contents)
 		RETURN_THROWS();
 	}

+	php_stream_error_operation_begin();
 	context = php_stream_context_from_zval(zcontext, 0);
-
 	stream = php_stream_open_wrapper_ex(filename, "rb",
 				(use_include_path ? USE_PATH : 0) | REPORT_ERRORS,
 				NULL, context);
 	if (!stream) {
+		php_stream_error_operation_end(context);
 		RETURN_FALSE;
 	}

@@ -418,8 +425,9 @@ PHP_FUNCTION(file_get_contents)
 	}

 	if (offset != 0 && php_stream_seek(stream, offset, ((offset > 0) ? SEEK_SET : SEEK_END)) < 0) {
-		php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", offset);
 		php_stream_close(stream);
+		php_stream_error_operation_end(context);
+		php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", offset);
 		RETURN_FALSE;
 	}

@@ -430,6 +438,7 @@ PHP_FUNCTION(file_get_contents)
 	}

 	php_stream_close(stream);
+	php_stream_error_operation_end(context);
 }
 /* }}} */

@@ -459,6 +468,7 @@ PHP_FUNCTION(file_put_contents)
 		php_stream_from_zval(srcstream, data);
 	}

+	php_stream_error_operation_begin();
 	context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);

 	if (flags & PHP_FILE_APPEND) {
@@ -477,11 +487,13 @@ PHP_FUNCTION(file_put_contents)

 	stream = php_stream_open_wrapper_ex(filename, mode, ((flags & PHP_FILE_USE_INCLUDE_PATH) ? USE_PATH : 0) | REPORT_ERRORS, NULL, context);
 	if (stream == NULL) {
+		php_stream_error_operation_end(context);
 		RETURN_FALSE;
 	}

 	if ((flags & LOCK_EX) && (!php_stream_supports_lock(stream) || php_stream_lock(stream, LOCK_EX))) {
 		php_stream_close(stream);
+		php_stream_error_operation_end(context);
 		php_error_docref(NULL, E_WARNING, "Exclusive locks are not supported for this stream");
 		RETURN_FALSE;
 	}
@@ -564,6 +576,7 @@ PHP_FUNCTION(file_put_contents)
 			break;
 	}
 	php_stream_close(stream);
+	php_stream_error_operation_end(context);

 	if (numbytes < 0) {
 		RETURN_FALSE;
@@ -609,10 +622,12 @@ PHP_FUNCTION(file)
 	include_new_line = !(flags & PHP_FILE_IGNORE_NEW_LINES);
 	skip_blank_lines = flags & PHP_FILE_SKIP_EMPTY_LINES;

+	php_stream_error_operation_begin();
 	context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);

 	stream = php_stream_open_wrapper_ex(filename, "rb", (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, context);
 	if (!stream) {
+		php_stream_error_operation_end(context);
 		RETURN_FALSE;
 	}

@@ -666,6 +681,7 @@ PHP_FUNCTION(file)
 	}

 	php_stream_close(stream);
+	php_stream_error_operation_end(context);
 }
 /* }}} */

@@ -705,7 +721,9 @@ PHP_FUNCTION(tmpfile)

 	ZEND_PARSE_PARAMETERS_NONE();

+	php_stream_error_operation_begin();
 	stream = php_stream_fopen_tmpfile();
+	php_stream_error_operation_end_for_stream(stream);

 	if (stream) {
 		php_stream_to_zval(stream, return_value);
@@ -733,9 +751,11 @@ PHP_FUNCTION(fopen)
 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	context = php_stream_context_from_zval(zcontext, 0);

 	stream = php_stream_open_wrapper_ex(filename, mode, (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, context);
+	php_stream_error_operation_end(context);

 	if (stream == NULL) {
 		RETURN_FALSE;
@@ -759,9 +779,11 @@ PHPAPI PHP_FUNCTION(fclose)
 		RETURN_FALSE;
 	}

+	php_stream_error_operation_begin();
 	php_stream_free(stream,
 		PHP_STREAM_FREE_KEEP_RSRC |
 		(stream->is_persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : PHP_STREAM_FREE_CLOSE));
+	php_stream_error_operation_end_for_stream(stream);

 	RETURN_TRUE;
 }
@@ -809,6 +831,7 @@ PHP_FUNCTION(popen)
 		RETURN_FALSE;
 	}

+	php_stream_error_operation_begin();
 	stream = php_stream_fopen_from_pipe(fp, mode);

 	if (stream == NULL)	{
@@ -817,6 +840,7 @@ PHP_FUNCTION(popen)
 	} else {
 		php_stream_to_zval(stream, return_value);
 	}
+	php_stream_error_operation_end_for_stream(stream);

 	efree(posix_mode);
 }
@@ -831,9 +855,11 @@ PHP_FUNCTION(pclose)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	FG(pclose_wait) = 1;
 	zend_list_close(stream->res);
 	FG(pclose_wait) = 0;
+	php_stream_error_operation_end_for_stream(stream);
 	RETURN_LONG(FG(pclose_ret));
 }
 /* }}} */
@@ -847,11 +873,13 @@ PHPAPI PHP_FUNCTION(feof)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	if (php_stream_eof(stream)) {
-		RETURN_TRUE;
+		RETVAL_TRUE;
 	} else {
-		RETURN_FALSE;
+		RETVAL_FALSE;
 	}
+	php_stream_error_operation_end_for_stream(stream);
 }
 /* }}} */

@@ -871,9 +899,11 @@ PHPAPI PHP_FUNCTION(fgets)
 		Z_PARAM_LONG_OR_NULL(len, len_is_null)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	if (len_is_null) {
 		/* ask streams to give us a buffer of an appropriate size */
 		buf = php_stream_get_line(stream, NULL, 0, &line_len);
+		php_stream_error_operation_end_for_stream(stream);
 		if (buf == NULL) {
 			RETURN_FALSE;
 		}
@@ -887,7 +917,9 @@ PHPAPI PHP_FUNCTION(fgets)
 		}

 		str = zend_string_alloc(len, 0);
-		if (php_stream_get_line(stream, ZSTR_VAL(str), len, &line_len) == NULL) {
+		buf = php_stream_get_line(stream, ZSTR_VAL(str), len, &line_len);
+		php_stream_error_operation_end_for_stream(stream);
+		if (buf == NULL) {
 			zend_string_efree(str);
 			RETURN_FALSE;
 		}
@@ -912,7 +944,9 @@ PHPAPI PHP_FUNCTION(fgetc)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	int result = php_stream_getc(stream);
+	php_stream_error_operation_end_for_stream(stream);

 	if (result == EOF) {
 		RETVAL_FALSE;
@@ -931,7 +965,6 @@ PHP_FUNCTION(fscanf)
 	zval *file_handle;
 	char *buf, *format;
 	size_t len;
-	void *what;

 	ZEND_PARSE_PARAMETERS_START(2, -1)
 		Z_PARAM_RESOURCE(file_handle)
@@ -939,16 +972,18 @@ PHP_FUNCTION(fscanf)
 		Z_PARAM_VARIADIC('*', args, argc)
 	ZEND_PARSE_PARAMETERS_END();

-	what = zend_fetch_resource2(Z_RES_P(file_handle), "File-Handle", php_file_le_stream(), php_file_le_pstream());
+	php_stream *stream = zend_fetch_resource2(Z_RES_P(file_handle), "File-Handle", php_file_le_stream(), php_file_le_pstream());

-	/* we can't do a ZEND_VERIFY_RESOURCE(what), otherwise we end up
+	/* we can't do a ZEND_VERIFY_RESOURCE(stream), otherwise we end up
 	 * with a leak if we have an invalid filehandle. This needs changing
 	 * if the code behind ZEND_VERIFY_RESOURCE changed. - cc */
-	if (!what) {
+	if (!stream) {
 		RETURN_THROWS();
 	}

-	buf = php_stream_get_line((php_stream *) what, NULL, 0, &len);
+	php_stream_error_operation_begin();
+	buf = php_stream_get_line(stream, NULL, 0, &len);
+	php_stream_error_operation_end_for_stream(stream);
 	if (buf == NULL) {
 		RETURN_FALSE;
 	}
@@ -994,7 +1029,9 @@ PHPAPI PHP_FUNCTION(fwrite)
 		RETURN_LONG(0);
 	}

+	php_stream_error_operation_begin();
 	ret = php_stream_write(stream, input, num_bytes);
+	php_stream_error_operation_end_for_stream(stream);
 	if (ret < 0) {
 		RETURN_FALSE;
 	}
@@ -1013,8 +1050,9 @@ PHPAPI PHP_FUNCTION(fflush)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	ret = php_stream_flush(stream);
-
+	php_stream_error_operation_end_for_stream(stream);
 	RETURN_BOOL(!ret);
 }
 /* }}} */
@@ -1022,13 +1060,17 @@ PHPAPI PHP_FUNCTION(fflush)
 /* {{{ Rewind the position of a file pointer */
 PHPAPI PHP_FUNCTION(rewind)
 {
+	int ret;
 	php_stream *stream;

 	ZEND_PARSE_PARAMETERS_START(1, 1)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

-	RETURN_BOOL(-1 != php_stream_rewind(stream));
+	php_stream_error_operation_begin();
+	ret = php_stream_rewind(stream);
+	php_stream_error_operation_end_for_stream(stream);
+	RETURN_BOOL(-1 != ret);
 }
 /* }}} */

@@ -1042,7 +1084,9 @@ PHPAPI PHP_FUNCTION(ftell)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	ret = php_stream_tell(stream);
+	php_stream_error_operation_end_for_stream(stream);
 	if (ret == -1)	{
 		RETURN_FALSE;
 	}
@@ -1063,7 +1107,9 @@ PHPAPI PHP_FUNCTION(fseek)
 		Z_PARAM_LONG(whence)
 	ZEND_PARSE_PARAMETERS_END();

-	RETURN_LONG(php_stream_seek(stream, offset, (int) whence));
+	php_stream_error_operation_begin();
+	RETVAL_LONG(php_stream_seek(stream, offset, (int) whence));
+	php_stream_error_operation_end_for_stream(stream);
 }
 /* }}} */

@@ -1087,7 +1133,9 @@ PHP_FUNCTION(mkdir)

 	context = php_stream_context_from_zval(zcontext, 0);

-	RETURN_BOOL(php_stream_mkdir(dir, (int)mode, (recursive ? PHP_STREAM_MKDIR_RECURSIVE : 0) | REPORT_ERRORS, context));
+	php_stream_error_operation_begin();
+	RETVAL_BOOL(php_stream_mkdir(dir, (int)mode, (recursive ? PHP_STREAM_MKDIR_RECURSIVE : 0) | REPORT_ERRORS, context));
+	php_stream_error_operation_end(context);
 }
 /* }}} */

@@ -1107,7 +1155,9 @@ PHP_FUNCTION(rmdir)

 	context = php_stream_context_from_zval(zcontext, 0);

-	RETURN_BOOL(php_stream_rmdir(dir, REPORT_ERRORS, context));
+	php_stream_error_operation_begin();
+	RETVAL_BOOL(php_stream_rmdir(dir, REPORT_ERRORS, context));
+	php_stream_error_operation_end(context);
 }
 /* }}} */

@@ -1131,14 +1181,17 @@ PHP_FUNCTION(readfile)

 	context = php_stream_context_from_zval(zcontext, 0);

+	php_stream_error_operation_begin();
 	stream = php_stream_open_wrapper_ex(filename, "rb", (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, context);
 	if (stream) {
 		size = php_stream_passthru(stream);
 		php_stream_close(stream);
-		RETURN_LONG(size);
+		RETVAL_LONG(size);
+	} else {
+		RETVAL_FALSE;
 	}
+	php_stream_error_operation_end(context);

-	RETURN_FALSE;
 }
 /* }}} */

@@ -1180,7 +1233,9 @@ PHPAPI PHP_FUNCTION(fpassthru)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	size = php_stream_passthru(stream);
+	php_stream_error_operation_end_for_stream(stream);
 	RETURN_LONG(size);
 }
 /* }}} */
@@ -1201,26 +1256,31 @@ PHP_FUNCTION(rename)
 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
+	context = php_stream_context_from_zval(zcontext, 0);
+
 	wrapper = php_stream_locate_url_wrapper(old_name, NULL, 0);

 	if (!wrapper || !wrapper->wops) {
+		php_stream_error_operation_end(context);
 		php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper");
 		RETURN_FALSE;
 	}

 	if (!wrapper->wops->rename) {
+		php_stream_error_operation_end(context);
 		php_error_docref(NULL, E_WARNING, "%s wrapper does not support renaming", wrapper->wops->label ? wrapper->wops->label : "Source");
 		RETURN_FALSE;
 	}

 	if (wrapper != php_stream_locate_url_wrapper(new_name, NULL, 0)) {
+		php_stream_error_operation_end(context);
 		php_error_docref(NULL, E_WARNING, "Cannot rename a file across wrapper types");
 		RETURN_FALSE;
 	}

-	context = php_stream_context_from_zval(zcontext, 0);
-
-	RETURN_BOOL(wrapper->wops->rename(wrapper, old_name, new_name, 0, context));
+	RETVAL_BOOL(wrapper->wops->rename(wrapper, old_name, new_name, REPORT_ERRORS, context));
+	php_stream_error_operation_end(context);
 }
 /* }}} */

@@ -1239,20 +1299,24 @@ PHP_FUNCTION(unlink)
 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	context = php_stream_context_from_zval(zcontext, 0);

 	wrapper = php_stream_locate_url_wrapper(filename, NULL, 0);

 	if (!wrapper || !wrapper->wops) {
+		php_stream_error_operation_end(context);
 		php_error_docref(NULL, E_WARNING, "Unable to locate stream wrapper");
 		RETURN_FALSE;
 	}

 	if (!wrapper->wops->unlink) {
+		php_stream_error_operation_end(context);
 		php_error_docref(NULL, E_WARNING, "%s does not allow unlinking", wrapper->wops->label ? wrapper->wops->label : "Wrapper");
 		RETURN_FALSE;
 	}
-	RETURN_BOOL(wrapper->wops->unlink(wrapper, filename, REPORT_ERRORS, context));
+	RETVAL_BOOL(wrapper->wops->unlink(wrapper, filename, REPORT_ERRORS, context));
+	php_stream_error_operation_end(context);
 }
 /* }}} */

@@ -1264,12 +1328,15 @@ PHP_FUNCTION(fsync)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	if (!php_stream_sync_supported(stream)) {
+		php_stream_error_operation_end_for_stream(stream);
 		php_error_docref(NULL, E_WARNING, "Can't fsync this stream!");
 		RETURN_FALSE;
 	}

-	RETURN_BOOL(php_stream_sync(stream, /* data_only */ 0) == 0);
+	RETVAL_BOOL(php_stream_sync(stream, /* data_only */ 0) == 0);
+	php_stream_error_operation_end_for_stream(stream);
 }

 PHP_FUNCTION(fdatasync)
@@ -1280,12 +1347,15 @@ PHP_FUNCTION(fdatasync)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	if (!php_stream_sync_supported(stream)) {
+		php_stream_error_operation_end_for_stream(stream);
 		php_error_docref(NULL, E_WARNING, "Can't fsync this stream!");
 		RETURN_FALSE;
 	}

-	RETURN_BOOL(php_stream_sync(stream, /* data_only */ 1) == 0);
+	RETVAL_BOOL(php_stream_sync(stream, /* data_only */ 1) == 0);
+	php_stream_error_operation_end_for_stream(stream);
 }

 /* {{{ Truncate file to 'size' length */
@@ -1304,12 +1374,16 @@ PHP_FUNCTION(ftruncate)
 		RETURN_THROWS();
 	}

+	php_stream_error_operation_begin();
+
 	if (!php_stream_truncate_supported(stream)) {
+		php_stream_error_operation_end_for_stream(stream);
 		php_error_docref(NULL, E_WARNING, "Can't truncate this stream!");
 		RETURN_FALSE;
 	}

-	RETURN_BOOL(0 == php_stream_truncate_set_size(stream, size));
+	RETVAL_BOOL(0 == php_stream_truncate_set_size(stream, size));
+	php_stream_error_operation_end_for_stream(stream);
 }
 /* }}} */
 PHPAPI void php_fstat(php_stream *stream, zval *return_value)
@@ -1393,7 +1467,9 @@ PHP_FUNCTION(fstat)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	php_fstat(stream, return_value);
+	php_stream_error_operation_end_for_stream(stream);
 }
 /* }}} */

@@ -1412,13 +1488,16 @@ PHP_FUNCTION(copy)
 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
+	context = php_stream_context_from_zval(zcontext, 0);
+
 	if (php_stream_locate_url_wrapper(source, NULL, 0) == &php_plain_files_wrapper && php_check_open_basedir(source)) {
+		php_stream_error_operation_end(context);
 		RETURN_FALSE;
 	}

-	context = php_stream_context_from_zval(zcontext, 0);
-
-	RETURN_BOOL(php_copy_file_ctx(source, target, 0, context) == SUCCESS);
+	RETVAL_BOOL(php_copy_file_ctx(source, target, 0, context) == SUCCESS);
+	php_stream_error_operation_end(context);
 }
 /* }}} */

@@ -1543,7 +1622,9 @@ PHPAPI PHP_FUNCTION(fread)
 		RETURN_THROWS();
 	}

+	php_stream_error_operation_begin();
 	str = php_stream_read_to_str(stream, len);
+	php_stream_error_operation_end_for_stream(stream);
 	if (!str) {
 		RETURN_FALSE;
 	}
@@ -1662,7 +1743,9 @@ PHP_FUNCTION(fputcsv)
 		RETURN_THROWS();
 	}

+	php_stream_error_operation_begin();
 	ret = php_fputcsv(stream, fields, delimiter, enclosure, escape_char, eol_str);
+	php_stream_error_operation_end_for_stream(stream);
 	if (ret < 0) {
 		RETURN_FALSE;
 	}
@@ -1795,19 +1878,23 @@ PHP_FUNCTION(fgetcsv)
 		RETURN_THROWS();
 	}

+	php_stream_error_operation_begin();
 	if (len < 0) {
 		if ((buf = php_stream_get_line(stream, NULL, 0, &buf_len)) == NULL) {
+			php_stream_error_operation_end_for_stream(stream);
 			RETURN_FALSE;
 		}
 	} else {
 		buf = emalloc(len + 1);
 		if (php_stream_get_line(stream, buf, len + 1, &buf_len) == NULL) {
 			efree(buf);
+			php_stream_error_operation_end_for_stream(stream);
 			RETURN_FALSE;
 		}
 	}

 	HashTable *values = php_fgetcsv(stream, delimiter, enclosure, escape_char, buf_len, buf);
+	php_stream_error_operation_end_for_stream(stream);
 	if (values == NULL) {
 		values = php_bc_fgetcsv_empty_line();
 	}
diff --git a/ext/standard/file.h b/ext/standard/file.h
index 3c6160fd4bb..ac218169599 100644
--- a/ext/standard/file.h
+++ b/ext/standard/file.h
@@ -32,6 +32,7 @@ PHPAPI PHP_FUNCTION(ftell);
 PHPAPI PHP_FUNCTION(fseek);
 PHPAPI PHP_FUNCTION(fpassthru);

+PHP_MINIT_FUNCTION(stream_errors);
 PHP_MINIT_FUNCTION(user_streams);

 PHPAPI zend_result php_copy_file(const char *src, const char *dest);
@@ -98,7 +99,8 @@ typedef struct {
 	php_stream_context *default_context;
 	HashTable *stream_wrappers;			/* per-request copy of url_stream_wrappers_hash */
 	HashTable *stream_filters;			/* per-request copy of stream_filters_hash */
-	HashTable *wrapper_errors;			/* key: wrapper address; value: linked list of char* */
+	HashTable *wrapper_logged_errors;	/* key: wrapper address; value: linked list of error entries */
+	php_stream_error_state stream_error_state;
 	int pclose_wait;
 #ifdef HAVE_GETHOSTBYNAME_R
 	struct hostent tmp_host_info;
diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c
index e1f3a88d249..f99ae5e4b4e 100644
--- a/ext/standard/ftp_fopen_wrapper.c
+++ b/ext/standard/ftp_fopen_wrapper.c
@@ -106,7 +106,9 @@ static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, php_stream *
 			/* For write modes close data stream first to signal EOF to server */
 			result = GET_FTP_RESULT(controlstream);
 			if (result != 226 && result != 250) {
-				php_error_docref(NULL, E_WARNING, "FTP server error %d:%s", result, tmp_line);
+				php_stream_wrapper_warn(wrapper, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
+					ProtocolError,
+					"FTP server error %d:%s", result, tmp_line);
 				ret = EOF;
 			}
 		}
@@ -184,7 +186,8 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char
 			/* get the response */
 			result = GET_FTP_RESULT(stream);
 			if (result != 334) {
-				php_stream_wrapper_log_error(wrapper, options, "Server doesn't support FTPS.");
+				php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
+					"Server doesn't support FTPS.");
 				goto connect_errexit;
 			} else {
 				/* we must reuse the old SSL session id */
@@ -203,7 +206,8 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char
 		if (php_stream_xport_crypto_setup(stream,
 				STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0
 				|| php_stream_xport_crypto_enable(stream, 1) < 0) {
-			php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode");
+			php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
+				"Unable to activate SSL mode");
 			php_stream_close(stream);
 			stream = NULL;
 			goto connect_errexit;
@@ -234,7 +238,7 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char
 	unsigned char *s = (unsigned char *) val, *e = (unsigned char *) s + val_len;	\
 	while (s < e) {	\
 		if (iscntrl((unsigned char)*s)) {	\
-			php_stream_wrapper_log_error(wrapper, options, err_msg, val);	\
+			php_stream_wrapper_log_warn(wrapper, context, options, AuthFailed, err_msg, val);	\
 			goto connect_errexit;	\
 		}	\
 		s++;	\
@@ -431,7 +435,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa
 	}
 	if (strpbrk(mode, "wa+")) {
 		if (read_write) {
-			php_stream_wrapper_log_error(wrapper, options, "FTP does not support simultaneous read/write connections");
+			php_stream_wrapper_log_warn(wrapper, context, options, ModeNotSupported,
+				"FTP does not support simultaneous read/write connections");
 			return NULL;
 		}
 		if (strchr(mode, 'a')) {
@@ -442,7 +447,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa
 	}
 	if (!read_write) {
 		/* No mode specified? */
-		php_stream_wrapper_log_error(wrapper, options, "Unknown file open mode");
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidMode,
+			"Unknown file open mode");
 		return NULL;
 	}

@@ -453,7 +459,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa
 			return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC);
 		} else {
 			/* ftp proxy is read-only */
-			php_stream_wrapper_log_error(wrapper, options, "FTP proxy may only be used in read mode");
+			php_stream_wrapper_log_warn(wrapper, context, options, ModeNotSupported,
+				"FTP proxy may only be used in read mode");
 			return NULL;
 		}
 	}
@@ -505,7 +512,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa
 					goto errexit;
 				}
 			} else {
-				php_stream_wrapper_log_error(wrapper, options, "Remote file already exists and overwrite context option not specified");
+				php_stream_wrapper_log_warn(wrapper, context, options, AlreadyExists,
+					"Remote file already exists and overwrite context option not specified");
 				errno = EEXIST;
 				goto errexit;
 			}
@@ -529,7 +537,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa
 			php_stream_printf(stream, "REST " ZEND_LONG_FMT "\r\n", Z_LVAL_P(tmpzval));
 			result = GET_FTP_RESULT(stream);
 			if (result < 300 || result > 399) {
-				php_stream_wrapper_log_error(wrapper, options, "Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval));
+				php_stream_wrapper_log_warn(wrapper, context, options, ResumptionFailed,
+					"Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval));
 				goto errexit;
 			}
 		}
@@ -574,7 +583,8 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa
 			STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
 			php_stream_xport_crypto_enable(datastream, 1) < 0)) {

-		php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode");
+		php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
+			"Unable to activate SSL mode");
 		php_stream_close(datastream);
 		datastream = NULL;
 		tmp_line[0]='\0';
@@ -596,10 +606,12 @@ php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *pa
 		php_stream_close(stream);
 	}
 	if (tmp_line[0] != '\0')
-		php_stream_wrapper_log_error(wrapper, options, "FTP server reports %s", tmp_line);
+		php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
+			"FTP server reports %s", tmp_line);

 	if (error_message) {
-		php_stream_wrapper_log_error(wrapper, options, "Failed to set up data channel: %s", ZSTR_VAL(error_message));
+		php_stream_wrapper_log_warn(wrapper, context, options, NetworkSendFailed,
+			"Failed to set up data channel: %s", ZSTR_VAL(error_message));
 		zend_string_release(error_message);
 	}
 	return NULL;
@@ -744,7 +756,8 @@ static php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const ch
 			STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
 			php_stream_xport_crypto_enable(datastream, 1) < 0)) {

-		php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode");
+		php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
+			"Unable to activate SSL mode");
 		php_stream_close(datastream);
 		datastream = NULL;
 		goto opendir_errexit;
@@ -768,7 +781,8 @@ static php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const ch
 		php_stream_close(stream);
 	}
 	if (tmp_line[0] != '\0') {
-		php_stream_wrapper_log_error(wrapper, options, "FTP server reports %s", tmp_line);
+		php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
+			"FTP server reports %s", tmp_line);
 	}
 	return NULL;
 }
@@ -907,14 +921,16 @@ static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, i
 	stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
 	if (!stream) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url);
+			php_stream_wrapper_warn(wrapper, context, options, AuthFailed,
+				"Unable to connect to %s", url);
 		}
 		goto unlink_errexit;
 	}

 	if (resource->path == NULL) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url);
+			php_stream_wrapper_warn(wrapper, context, options, InvalidPath,
+				"Invalid path provided in %s", url);
 		}
 		goto unlink_errexit;
 	}
@@ -925,7 +941,8 @@ static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, i
 	result = GET_FTP_RESULT(stream);
 	if (result < 200 || result > 299) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Error Deleting file: %s", tmp_line);
+			php_stream_wrapper_warn(wrapper, context, options, UnlinkFailed,
+				"Error Deleting file: %s", tmp_line);
 		}
 		goto unlink_errexit;
 	}
@@ -989,7 +1006,8 @@ static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_fr
 	stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, context, NULL, NULL, NULL, NULL);
 	if (!stream) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Unable to connect to %s", ZSTR_VAL(resource_from->host));
+			php_stream_wrapper_warn(wrapper, context, options, AuthFailed,
+				"Unable to connect to %s", ZSTR_VAL(resource_from->host));
 		}
 		goto rename_errexit;
 	}
@@ -1000,7 +1018,8 @@ static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_fr
 	result = GET_FTP_RESULT(stream);
 	if (result < 300 || result > 399) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Error Renaming file: %s", tmp_line);
+			php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
+				"Error Renaming file: %s", tmp_line);
 		}
 		goto rename_errexit;
 	}
@@ -1011,7 +1030,8 @@ static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_fr
 	result = GET_FTP_RESULT(stream);
 	if (result < 200 || result > 299) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Error Renaming file: %s", tmp_line);
+			php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
+				"Error Renaming file: %s", tmp_line);
 		}
 		goto rename_errexit;
 	}
@@ -1044,14 +1064,16 @@ static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, in
 	stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
 	if (!stream) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url);
+			php_stream_wrapper_warn(wrapper, context, options, AuthFailed,
+				"Unable to connect to %s", url);
 		}
 		goto mkdir_errexit;
 	}

 	if (resource->path == NULL) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url);
+			php_stream_wrapper_warn(wrapper, context, options, InvalidPath,
+				"Invalid path provided in %s", url);
 		}
 		goto mkdir_errexit;
 	}
@@ -1092,7 +1114,8 @@ static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, in
 					result = GET_FTP_RESULT(stream);
 					if (result < 200 || result > 299) {
 						if (options & REPORT_ERRORS) {
-							php_error_docref(NULL, E_WARNING, "%s", tmp_line);
+							php_stream_wrapper_warn(wrapper, context, options, MkdirFailed,
+								"%s", tmp_line);
 						}
 						break;
 					}
@@ -1136,14 +1159,16 @@ static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, in
 	stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
 	if (!stream) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url);
+			php_stream_wrapper_warn(wrapper, context, options, AuthFailed,
+				"Unable to connect to %s", url);
 		}
 		goto rmdir_errexit;
 	}

 	if (resource->path == NULL) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url);
+			php_stream_wrapper_warn(wrapper, context, options, InvalidPath,
+				"Invalid path provided in %s", url);
 		}
 		goto rmdir_errexit;
 	}
@@ -1153,7 +1178,8 @@ static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, in

 	if (result < 200 || result > 299) {
 		if (options & REPORT_ERRORS) {
-			php_error_docref(NULL, E_WARNING, "%s", tmp_line);
+			php_stream_wrapper_warn(wrapper, context, options, RmdirFailed,
+				"%s", tmp_line);
 		}
 		goto rmdir_errexit;
 	}
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 95a6b1930a1..9e3db671604 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -243,7 +243,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w
 		while (last_header_name < last_header_value) {
 			if (*last_header_name == ' ' || *last_header_name == '\t') {
 				header_info->error = true;
-				php_stream_wrapper_log_error(wrapper, options,
+				php_stream_wrapper_log_warn(wrapper, context, options, InvalidResponse,
 					"HTTP invalid response format (space in header name)!");
 				zend_string_efree(last_header_line_str);
 				return NULL;
@@ -261,7 +261,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w
 	} else {
 		/* There is no colon which means invalid response so error. */
 		header_info->error = true;
-		php_stream_wrapper_log_error(wrapper, options,
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidResponse,
 				"HTTP invalid response format (no colon in header line)!");
 		zend_string_efree(last_header_line_str);
 		return NULL;
@@ -285,7 +285,7 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w
 		size_t last_header_value_len = strlen(last_header_value);
 		if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) {
 			header_info->error = true;
-			php_stream_wrapper_log_error(wrapper, options,
+			php_stream_wrapper_log_warn(wrapper, context, options, InvalidResponse,
 					"HTTP Location header size is over the limit of %d bytes",
 					HTTP_HEADER_MAX_LOCATION_SIZE);
 			zend_string_efree(last_header_line_str);
@@ -386,7 +386,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 	tmp_line[0] = '\0';

 	if (redirect_max < 1) {
-		php_stream_wrapper_log_error(wrapper, options, "Redirection limit reached, aborting");
+		php_stream_wrapper_log_warn(wrapper, context, options, RedirectLimit,
+			"Redirection limit reached, aborting");
 		return NULL;
 	}

@@ -419,7 +420,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 		/* Normal http request (possibly with proxy) */

 		if (strpbrk(mode, "awx+")) {
-			php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper does not support writeable connections");
+			php_stream_wrapper_log_warn(wrapper, context, options, ModeNotSupported,
+				"HTTP wrapper does not support writeable connections");
 			php_uri_struct_free(resource);
 			return NULL;
 		}
@@ -448,7 +450,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 	}

 	if (request_fulluri && (strchr(path, '\n') != NULL || strchr(path, '\r') != NULL)) {
-		php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper full URI path does not allow CR or LF characters");
+		php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+			"HTTP wrapper full URI path does not allow CR or LF characters");
 		php_uri_struct_free(resource);
 		zend_string_release(transport_string);
 		return NULL;
@@ -463,7 +466,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 #endif

 		if (d > timeoutmax) {
-			php_stream_wrapper_log_error(wrapper, options, "timeout must be lower than " ZEND_ULONG_FMT, (zend_ulong)timeoutmax);
+			php_stream_wrapper_log_warn(wrapper, context, options, InvalidParam,
+				"timeout must be lower than " ZEND_ULONG_FMT, (zend_ulong)timeoutmax);
 			zend_string_release(transport_string);
 			php_uri_struct_free(resource);
 			return NULL;
@@ -493,7 +497,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 	}

 	if (errstr) {
-		php_stream_wrapper_log_error(wrapper, options, "%s", ZSTR_VAL(errstr));
+		php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
+			"%s", ZSTR_VAL(errstr));
 		zend_string_release_ex(errstr, 0);
 		errstr = NULL;
 	}
@@ -547,8 +552,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 			if (reset_ssl_peer_name) {
 				php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name");
 			}
-
-			php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy");
+			php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
+				"Cannot connect to HTTPS server through proxy");
 			php_stream_close(stream);
 			stream = NULL;
 		}
@@ -573,7 +578,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,

 			if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
 			    php_stream_xport_crypto_enable(stream, 1) < 0) {
-				php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy");
+				php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
+					"Cannot connect to HTTPS server through proxy");
 				php_stream_close(stream);
 				stream = NULL;
 			}
@@ -830,7 +836,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 				ua[ua_len] = 0;
 				smart_str_appendl(&req_buf, ua, ua_len);
 			} else {
-				php_error_docref(NULL, E_WARNING, "Cannot construct User-agent header");
+				php_stream_wrapper_warn_nt(wrapper, context, options, InvalidHeader,
+					"Cannot construct User-agent header");
 			}
 			efree(ua);
 		}
@@ -869,7 +876,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 		}
 		if (!(have_header & HTTP_HEADER_TYPE)) {
 			smart_str_appends(&req_buf, "Content-Type: application/x-www-form-urlencoded\r\n");
-			php_error_docref(NULL, E_NOTICE, "Content-type not specified assuming application/x-www-form-urlencoded");
+			php_stream_wrapper_notice(wrapper, context, options, InvalidHeader,
+				"Content-type not specified assuming application/x-www-form-urlencoded");
 		}
 		smart_str_appends(&req_buf, "\r\n");
 		smart_str_append(&req_buf, Z_STR_P(tmpzval));
@@ -956,7 +964,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 		} else {
 			php_stream_close(stream);
 			stream = NULL;
-			php_stream_wrapper_log_error(wrapper, options, "HTTP request failed!");
+			php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
+				"HTTP request failed!");
 			goto out;
 		}
 	}
@@ -974,7 +983,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 				if (http_header_line[1] != '\n') {
 					php_stream_close(stream);
 					stream = NULL;
-					php_stream_wrapper_log_error(wrapper, options,
+					php_stream_wrapper_log_warn(wrapper, context, options, InvalidResponse,
 							"HTTP invalid header name (cannot start with CR character)!");
 					goto out;
 				}
@@ -1005,7 +1014,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 				if (*http_header_line == ' ' || *http_header_line == '\t') {
 					php_stream_close(stream);
 					stream = NULL;
-					php_stream_wrapper_log_error(wrapper, options,
+					php_stream_wrapper_log_warn(wrapper, context, options, InvalidResponse,
 							"HTTP invalid response format (folding header at the start)!");
 					goto out;
 				}
@@ -1101,7 +1110,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 			php_uri_struct_free(resource);
 			/* check for invalid redirection URLs */
 			if ((resource = php_uri_parse_to_struct(uri_parser, new_path, strlen(new_path), PHP_URI_COMPONENT_READ_MODE_RAW, true)) == NULL) {
-				php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path);
+				php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl,
+					"Invalid redirect URL! %s", new_path);
 				efree(new_path);
 				goto out;
 			}
@@ -1113,7 +1123,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 		s = (unsigned char*)ZSTR_VAL(val); e = s + ZSTR_LEN(val); \
 		while (s < e) { \
 			if (iscntrl(*s)) { \
-				php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \
+				php_stream_wrapper_log_warn(wrapper, context, options, InvalidUrl, \
+					"Invalid redirect URL! %s", new_path); \
 				efree(new_path); \
 				goto out; \
 			} \
@@ -1139,7 +1150,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
 				--redirect_max, new_flags, response_header STREAMS_CC);
 			efree(new_path);
 		} else {
-			php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line);
+			php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
+				"HTTP request failed! %s", tmp_line);
 		}
 	}
 out:
diff --git a/ext/standard/php_fopen_wrapper.c b/ext/standard/php_fopen_wrapper.c
index 89c2d9c49b0..3f062bf2ea1 100644
--- a/ext/standard/php_fopen_wrapper.c
+++ b/ext/standard/php_fopen_wrapper.c
@@ -156,14 +156,18 @@ static void php_stream_apply_filter_list(php_stream *stream, char *filterlist, i
 			if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
 				php_stream_filter_append(&stream->readfilters, temp_filter);
 			} else {
-				php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p);
+				php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
+					CreateFailed,
+					"Unable to create filter (%s)", p);
 			}
 		}
 		if (write_chain) {
 			if ((temp_filter = php_stream_filter_create(p, NULL, php_stream_is_persistent(stream)))) {
 				php_stream_filter_append(&stream->writefilters, temp_filter);
 			} else {
-				php_error_docref(NULL, E_WARNING, "Unable to create filter (%s)", p);
+				php_stream_wrapper_warn_nt(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
+					CreateFailed,
+					"Unable to create filter (%s)", p);
 			}
 		}
 		p = php_strtok_r(NULL, "|", &token);
@@ -217,7 +221,9 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c

 		if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
 			if (options & REPORT_ERRORS) {
-				php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
+				php_stream_wrapper_warn(wrapper, context, options,
+					Disabled,
+					"URL file-access is disabled in the server configuration");
 			}
 			return NULL;
 		}
@@ -236,7 +242,9 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c
 	if (!strcasecmp(path, "stdin")) {
 		if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
 			if (options & REPORT_ERRORS) {
-				php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
+				php_stream_wrapper_warn(wrapper, context, options,
+					Disabled,
+					"URL file-access is disabled in the server configuration");
 			}
 			return NULL;
 		}
@@ -295,14 +303,18 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c

 		if (strcmp(sapi_module.name, "cli")) {
 			if (options & REPORT_ERRORS) {
-				php_error_docref(NULL, E_WARNING, "Direct access to file descriptors is only available from command-line PHP");
+				php_stream_wrapper_warn(wrapper, context, options,
+					Disabled,
+					"Direct access to file descriptors is only available from command-line PHP");
 			}
 			return NULL;
 		}

 		if ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include) ) {
 			if (options & REPORT_ERRORS) {
-				php_error_docref(NULL, E_WARNING, "URL file-access is disabled in the server configuration");
+				php_stream_wrapper_warn(wrapper, context, options,
+					Disabled,
+					"URL file-access is disabled in the server configuration");
 			}
 			return NULL;
 		}
@@ -310,7 +322,8 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c
 		start = &path[3];
 		fildes_ori = ZEND_STRTOL(start, &end, 10);
 		if (end == start || *end != '\0') {
-			php_stream_wrapper_log_error(wrapper, options,
+			php_stream_wrapper_log_warn(wrapper, context, options,
+				InvalidUrl,
 				"php://fd/ stream must be specified in the form php://fd/<orig fd>");
 			return NULL;
 		}
@@ -322,14 +335,16 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c
 #endif

 		if (fildes_ori < 0 || fildes_ori >= dtablesize) {
-			php_stream_wrapper_log_error(wrapper, options,
+			php_stream_wrapper_log_warn(wrapper, context, options,
+				InvalidParam,
 				"The file descriptors must be non-negative numbers smaller than %d", dtablesize);
 			return NULL;
 		}

 		fd = dup((int)fildes_ori);
 		if (fd == -1) {
-			php_stream_wrapper_log_error(wrapper, options,
+			php_stream_wrapper_log_warn(wrapper, context, options,
+				DupFailed,
 				"Error duping file descriptor " ZEND_LONG_FMT "; possibly it doesn't exist: "
 				"[%d]: %s", fildes_ori, errno, strerror(errno));
 			return NULL;
@@ -378,7 +393,8 @@ static php_stream * php_stream_url_wrap_php(php_stream_wrapper *wrapper, const c
 		return stream;
 	} else {
 		/* invalid php://thingy */
-		php_error_docref(NULL, E_WARNING, "Invalid php:// URL specified");
+		php_stream_wrapper_warn(wrapper, context, options,
+				InvalidUrl, "Invalid php:// URL specified");
 		return NULL;
 	}

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index 68f79783c6f..3609ee1f3f2 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -48,11 +48,15 @@ PHP_FUNCTION(stream_socket_pair)
 	zend_long domain, type, protocol;
 	php_stream *s1, *s2;
 	php_socket_t pair[2];
+	zval *zcontext = NULL;
+	php_stream_context *context = NULL;

-	ZEND_PARSE_PARAMETERS_START(3, 3)
+	ZEND_PARSE_PARAMETERS_START(3, 4)
 		Z_PARAM_LONG(domain)
 		Z_PARAM_LONG(type)
 		Z_PARAM_LONG(protocol)
+		Z_PARAM_OPTIONAL
+		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

 	if (0 != socketpair((int)domain, (int)type, (int)protocol, pair)) {
@@ -62,10 +66,14 @@ PHP_FUNCTION(stream_socket_pair)
 		RETURN_FALSE;
 	}

+	php_stream_error_operation_begin();
+	context = php_stream_context_from_zval(zcontext, 0);
+
     s1 = php_stream_sock_open_from_socket(pair[0], 0);
     if (s1 == NULL) {
         close(pair[0]);
         close(pair[1]);
+		php_stream_error_operation_end(context);
         php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair");
         RETURN_FALSE;
     }
@@ -73,6 +81,7 @@ PHP_FUNCTION(stream_socket_pair)
     if (s2 == NULL) {
         php_stream_free(s1, PHP_STREAM_FREE_CLOSE);
         close(pair[1]);
+		php_stream_error_operation_end(context);
         php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair");
         RETURN_FALSE;
     }
@@ -86,6 +95,8 @@ PHP_FUNCTION(stream_socket_pair)

 	add_next_index_resource(return_value, s1->res);
 	add_next_index_resource(return_value, s2->res);
+
+	php_stream_error_operation_end(context);
 }
 /* }}} */
 #endif
@@ -124,6 +135,7 @@ PHP_FUNCTION(stream_socket_client)
 		RETURN_THROWS();
 	}

+	php_stream_error_operation_begin();
 	context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);

 	if (flags & PHP_STREAM_CLIENT_PERSISTENT) {
@@ -158,6 +170,7 @@ PHP_FUNCTION(stream_socket_client)
 			(flags & PHP_STREAM_CLIENT_ASYNC_CONNECT ? STREAM_XPORT_CONNECT_ASYNC : 0),
 			hashkey, tv_pointer, context, &errstr, &err);

+	php_stream_error_operation_end(context);

 	if (stream == NULL) {
 		/* host might contain binary characters */
@@ -215,6 +228,7 @@ PHP_FUNCTION(stream_socket_server)
 		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);

 	if (zerrno) {
@@ -228,6 +242,8 @@ PHP_FUNCTION(stream_socket_server)
 			STREAM_XPORT_SERVER | (int)flags,
 			NULL, NULL, context, &errstr, &err);

+	php_stream_error_operation_end(context);
+
 	if (stream == NULL) {
 		php_error_docref(NULL, E_WARNING, "Unable to connect to %s (%s)", host, errstr == NULL ? "Unknown error" : ZSTR_VAL(errstr));
 	}
@@ -293,6 +309,8 @@ PHP_FUNCTION(stream_socket_accept)
 		tv_pointer = &tv;
 	}

+	php_stream_error_operation_begin();
+
 	if (0 == php_stream_xport_accept(stream, &clistream,
 				zpeername ? &peername : NULL,
 				NULL, NULL,
@@ -311,6 +329,8 @@ PHP_FUNCTION(stream_socket_accept)
 		RETVAL_FALSE;
 	}

+	php_stream_error_operation_end_for_stream(stream);
+
 	if (errstr) {
 		zend_string_release_ex(errstr, 0);
 	}
@@ -329,10 +349,11 @@ PHP_FUNCTION(stream_socket_get_name)
 		Z_PARAM_BOOL(want_peer)
 	ZEND_PARSE_PARAMETERS_END();

-	if (0 != php_stream_xport_get_name(stream, want_peer,
-				&name,
-				NULL, NULL
-				) || !name) {
+	php_stream_error_operation_begin();
+	int ret = php_stream_xport_get_name(stream, want_peer, &name, NULL, NULL);
+	php_stream_error_operation_end_for_stream(stream);
+
+	if (0 != ret || !name) {
 		RETURN_FALSE;
 	}

@@ -363,15 +384,18 @@ PHP_FUNCTION(stream_socket_sendto)
 		Z_PARAM_STRING(target_addr, target_addr_len)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
 	if (target_addr_len) {
 		/* parse the address */
 		if (FAILURE == php_network_parse_network_address_with_port(target_addr, target_addr_len, (struct sockaddr*)&sa, &sl)) {
+			php_stream_error_operation_end_for_stream(stream);
 			php_error_docref(NULL, E_WARNING, "Failed to parse `%s' into a valid network address", target_addr);
 			RETURN_FALSE;
 		}
 	}

-	RETURN_LONG(php_stream_xport_sendto(stream, data, datalen, (int)flags, target_addr_len ? &sa : NULL, sl));
+	RETVAL_LONG(php_stream_xport_sendto(stream, data, datalen, (int)flags, target_addr_len ? &sa : NULL, sl));
+	php_stream_error_operation_end_for_stream(stream);
 }
 /* }}} */

@@ -405,9 +429,10 @@ PHP_FUNCTION(stream_socket_recvfrom)

 	read_buf = zend_string_alloc(to_read, 0);

+	php_stream_error_operation_begin();
 	recvd = php_stream_xport_recvfrom(stream, ZSTR_VAL(read_buf), to_read, (int)flags, NULL, NULL,
-			zremote ? &remote_addr : NULL
-			);
+			zremote ? &remote_addr : NULL);
+	php_stream_error_operation_end_for_stream(stream);

 	if (recvd >= 0) {
 		if (zremote && remote_addr) {
@@ -445,6 +470,8 @@ PHP_FUNCTION(stream_get_contents)
 		RETURN_THROWS();
 	}

+	php_stream_error_operation_begin();
+
 	if (desiredpos >= 0) {
 		int		seek_res = 0;
 		zend_off_t	position;
@@ -459,6 +486,7 @@ PHP_FUNCTION(stream_get_contents)
 		}

 		if (seek_res != 0) {
+			php_stream_error_operation_end_for_stream(stream);
 			php_error_docref(NULL, E_WARNING,
 				"Failed to seek to position " ZEND_LONG_FMT " in the stream", desiredpos);
 			RETURN_FALSE;
@@ -466,10 +494,11 @@ PHP_FUNCTION(stream_get_contents)
 	}

 	if ((contents = php_stream_copy_to_mem(stream, maxlen, 0))) {
-		RETURN_STR(contents);
+		RETVAL_STR(contents);
 	} else {
-		RETURN_EMPTY_STRING();
+		RETVAL_EMPTY_STRING();
 	}
+	php_stream_error_operation_end_for_stream(stream);
 }
 /* }}} */

@@ -480,28 +509,37 @@ PHP_FUNCTION(stream_copy_to_stream)
 	zend_long maxlen, pos = 0;
 	bool maxlen_is_null = 1;
 	size_t len;
+	zval *zcontext = NULL;
+	php_stream_context *context = NULL;

-	ZEND_PARSE_PARAMETERS_START(2, 4)
+	ZEND_PARSE_PARAMETERS_START(2, 5)
 		PHP_Z_PARAM_STREAM(src)
 		PHP_Z_PARAM_STREAM(dest)
 		Z_PARAM_OPTIONAL
 		Z_PARAM_LONG_OR_NULL(maxlen, maxlen_is_null)
 		Z_PARAM_LONG(pos)
+		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

 	if (maxlen_is_null) {
 		maxlen = PHP_STREAM_COPY_ALL;
 	}

+	php_stream_error_operation_begin();
+	context = php_stream_context_from_zval(zcontext, 0);
+
 	if (pos > 0 && php_stream_seek(src, pos, SEEK_SET) < 0) {
+		php_stream_error_operation_end(context);
 		php_error_docref(NULL, E_WARNING, "Failed to seek to position " ZEND_LONG_FMT " in the stream", pos);
 		RETURN_FALSE;
 	}

 	if (php_stream_copy_to_stream_ex(src, dest, maxlen, &len) != SUCCESS) {
-		RETURN_FALSE;
+		RETVAL_FALSE;
+	} else {
+		RETVAL_LONG(len);
 	}
-	RETURN_LONG(len);
+	php_stream_error_operation_end(context);
 }
 /* }}} */

@@ -516,11 +554,13 @@ PHP_FUNCTION(stream_get_meta_data)

 	array_init(return_value);

+	php_stream_error_operation_begin();
 	if (!php_stream_populate_meta_data(stream, return_value)) {
 		add_assoc_bool(return_value, "timed_out", 0);
 		add_assoc_bool(return_value, "blocked", 1);
 		add_assoc_bool(return_value, "eof", php_stream_eof(stream));
 	}
+	php_stream_error_operation_end_for_stream(stream);

 	if (!Z_ISUNDEF(stream->wrapperdata)) {
 		Z_ADDREF_P(&stream->wrapperdata);
@@ -592,6 +632,18 @@ PHP_FUNCTION(stream_get_wrappers)
 }
 /* }}} */

+PHP_FUNCTION(stream_last_errors)
+{
+	ZEND_PARSE_PARAMETERS_NONE();
+	php_stream_error_get_last(return_value);
+}
+
+PHP_FUNCTION(stream_clear_errors)
+{
+	ZEND_PARSE_PARAMETERS_NONE();
+	php_stream_error_clear_stored();
+}
+
 /* {{{ stream_select related functions */
 static int stream_array_to_fd_set(const HashTable *stream_array, fd_set *fds, php_socket_t *max_fd)
 {
@@ -724,7 +776,7 @@ static int stream_array_emulate_read_fd_set(zval *stream_array)
 /* {{{ Runs the select() system call on the sets of streams with a timeout specified by tv_sec and tv_usec */
 PHP_FUNCTION(stream_select)
 {
-	zval *r_array, *w_array, *e_array;
+	zval *r_array, *w_array, *e_array, *zcontext = NULL;
 	struct timeval tv, *tv_p = NULL;
 	fd_set rfds, wfds, efds;
 	php_socket_t max_fd = 0;
@@ -733,20 +785,25 @@ PHP_FUNCTION(stream_select)
 	bool secnull;
 	bool usecnull = 1;
 	int set_count, max_set_count = 0;
+	php_stream_context *context = NULL;

-	ZEND_PARSE_PARAMETERS_START(4, 5)
+	ZEND_PARSE_PARAMETERS_START(4, 6)
 		Z_PARAM_ARRAY_EX2(r_array, 1, 1, 0)
 		Z_PARAM_ARRAY_EX2(w_array, 1, 1, 0)
 		Z_PARAM_ARRAY_EX2(e_array, 1, 1, 0)
 		Z_PARAM_LONG_OR_NULL(sec, secnull)
 		Z_PARAM_OPTIONAL
 		Z_PARAM_LONG_OR_NULL(usec, usecnull)
+		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

 	FD_ZERO(&rfds);
 	FD_ZERO(&wfds);
 	FD_ZERO(&efds);

+	php_stream_error_operation_begin();
+	context = php_stream_context_from_zval(zcontext, 0);
+
 	if (r_array != NULL) {
 		set_count = stream_array_to_fd_set(Z_ARR_P(r_array), &rfds, &max_fd);
 		if (set_count > max_set_count)
@@ -769,6 +826,7 @@ PHP_FUNCTION(stream_select)
 	}

 	if (!sets) {
+		php_stream_error_operation_end(context);
 		zend_value_error("No stream arrays were passed");
 		RETURN_THROWS();
 	}
@@ -779,6 +837,7 @@ PHP_FUNCTION(stream_select)

 	if (secnull && !usecnull) {
 		if (usec != 0) {
+			php_stream_error_operation_end(context);
 			zend_argument_value_error(5, "must be null when argument #4 ($seconds) is null");
 			RETURN_THROWS();
 		}
@@ -787,9 +846,11 @@ PHP_FUNCTION(stream_select)
 	/* If seconds is not set to null, build the timeval, else we wait indefinitely */
 	if (!secnull) {
 		if (sec < 0) {
+			php_stream_error_operation_end(context);
 			zend_argument_value_error(4, "must be greater than or equal to 0");
 			RETURN_THROWS();
 		} else if (usec < 0) {
+			php_stream_error_operation_end(context);
 			zend_argument_value_error(5, "must be greater than or equal to 0");
 			RETURN_THROWS();
 		}
@@ -806,6 +867,7 @@ PHP_FUNCTION(stream_select)
 	if (r_array != NULL) {
 		retval = stream_array_emulate_read_fd_set(r_array);
 		if (retval > 0) {
+			php_stream_error_operation_end(context);
 			if (w_array != NULL) {
 				zval_ptr_dtor(w_array);
 				ZVAL_EMPTY_ARRAY(w_array);
@@ -819,6 +881,7 @@ PHP_FUNCTION(stream_select)
 	}

 	retval = php_select(max_fd+1, &rfds, &wfds, &efds, tv_p);
+	php_stream_error_operation_end(context);

 	if (retval == -1) {
 		php_error_docref(NULL, E_WARNING, "Unable to select [%d]: %s (max_fd=" PHP_SOCKET_FMT ")",
@@ -1145,6 +1208,24 @@ PHP_FUNCTION(stream_context_get_default)
 }
 /* }}} */

+/* Check if options contain stream error handling settings */
+static bool php_stream_context_options_has_error_settings(const HashTable *options)
+{
+	zval *stream_options = zend_hash_str_find(options, ZEND_STRL("stream"));
+	if (!stream_options) {
+		return false;
+	}
+
+	ZVAL_DEREF(stream_options);
+	if (Z_TYPE_P(stream_options) != IS_ARRAY) {
+		return false;
+	}
+
+	return zend_hash_str_exists(Z_ARRVAL_P(stream_options), ZEND_STRL("error_mode"))
+			|| zend_hash_str_exists(Z_ARRVAL_P(stream_options), ZEND_STRL("error_store"))
+			|| zend_hash_str_exists(Z_ARRVAL_P(stream_options), ZEND_STRL("error_handler"));
+}
+
 /* {{{ Set default file/stream context, returns the context as a resource */
 PHP_FUNCTION(stream_context_set_default)
 {
@@ -1160,6 +1241,11 @@ PHP_FUNCTION(stream_context_set_default)
 	}
 	context = FG(default_context);

+	if (php_stream_context_options_has_error_settings(options)) {
+		zend_value_error("Stream error handling options cannot be set on the default context");
+		RETURN_THROWS();
+	}
+
 	if (parse_context_options(context, options) == FAILURE) {
 		RETURN_THROWS();
 	}
@@ -1339,11 +1425,13 @@ PHP_FUNCTION(stream_get_line)
 		max_length = PHP_SOCK_CHUNK_SIZE;
 	}

+	php_stream_error_operation_begin();
 	if ((buf = php_stream_get_record(stream, max_length, str, str_len))) {
-		RETURN_STR(buf);
+		RETVAL_STR(buf);
 	} else {
-		RETURN_FALSE;
+		RETVAL_FALSE;
 	}
+	php_stream_error_operation_end_for_stream(stream);
 }

 /* }}} */
@@ -1359,7 +1447,9 @@ PHP_FUNCTION(stream_set_blocking)
 		Z_PARAM_BOOL(block)
 	ZEND_PARSE_PARAMETERS_END();

-	RETURN_BOOL(-1 != php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, block, NULL));
+	php_stream_error_operation_begin();
+	RETVAL_BOOL(-1 != php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, block, NULL));
+	php_stream_error_operation_end_for_stream(stream);
 }

 /* }}} */
@@ -1400,7 +1490,9 @@ PHP_FUNCTION(stream_set_timeout)
 	}
 #endif

-	RETURN_BOOL(PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t));
+	php_stream_error_operation_begin();
+	RETVAL_BOOL(PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t));
+	php_stream_error_operation_end_for_stream(stream);
 }
 #endif /* HAVE_SYS_TIME_H || defined(PHP_WIN32) */
 /* }}} */
@@ -1420,12 +1512,14 @@ PHP_FUNCTION(stream_set_write_buffer)

 	buff = arg2;

+	php_stream_error_operation_begin();
 	/* if buff is 0 then set to non-buffered */
 	if (buff == 0) {
 		ret = php_stream_set_option(stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
 	} else {
 		ret = php_stream_set_option(stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buff);
 	}
+	php_stream_error_operation_end_for_stream(stream);

 	RETURN_LONG(ret == 0 ? 0 : EOF);
 }
@@ -1456,7 +1550,9 @@ PHP_FUNCTION(stream_set_chunk_size)
 		RETURN_THROWS();
 	}

+	php_stream_error_operation_begin();
 	ret = php_stream_set_option(stream, PHP_STREAM_OPTION_SET_CHUNK_SIZE, (int)csize, NULL);
+	php_stream_error_operation_end_for_stream(stream);

 	RETURN_LONG(ret > 0 ? (zend_long)ret : (zend_long)EOF);
 }
@@ -1477,12 +1573,14 @@ PHP_FUNCTION(stream_set_read_buffer)

 	buff = arg2;

+	php_stream_error_operation_begin();
 	/* if buff is 0 then set to non-buffered */
 	if (buff == 0) {
 		ret = php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
 	} else {
 		ret = php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_FULL, &buff);
 	}
+	php_stream_error_operation_end_for_stream(stream);

 	RETURN_LONG(ret == 0 ? 0 : EOF);
 }
@@ -1504,11 +1602,14 @@ PHP_FUNCTION(stream_socket_enable_crypto)
 		PHP_Z_PARAM_STREAM_OR_NULL(sessstream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
+
 	if (enable) {
 		if (cryptokindnull) {
 			zval *val;

 			if (!GET_CTX_OPT(stream, "ssl", "crypto_method", val)) {
+				php_stream_error_operation_end_for_stream(stream);
 				zend_argument_value_error(3, "must be specified when enabling encryption");
 				RETURN_THROWS();
 			}
@@ -1517,11 +1618,13 @@ PHP_FUNCTION(stream_socket_enable_crypto)
 		}

 		if (php_stream_xport_crypto_setup(stream, cryptokind, sessstream) < 0) {
+			php_stream_error_operation_end_for_stream(stream);
 			RETURN_FALSE;
 		}
 	}

 	ret = php_stream_xport_crypto_enable(stream, enable);
+	php_stream_error_operation_end_for_stream(stream);
 	switch (ret) {
 		case -1:
 			RETURN_FALSE;
@@ -1557,12 +1660,15 @@ PHP_FUNCTION(stream_resolve_include_path)
 /* {{{ */
 PHP_FUNCTION(stream_is_local)
 {
-	zval *zstream;
+	zval *zstream, *zcontext = NULL;
 	php_stream *stream = NULL;
 	php_stream_wrapper *wrapper = NULL;
+	php_stream_context *context = NULL;

-	ZEND_PARSE_PARAMETERS_START(1, 1)
+	ZEND_PARSE_PARAMETERS_START(1, 2)
 		Z_PARAM_ZVAL(zstream)
+		Z_PARAM_OPTIONAL
+		Z_PARAM_RESOURCE_OR_NULL(zcontext)
 	ZEND_PARSE_PARAMETERS_END();

 	if (Z_TYPE_P(zstream) == IS_RESOURCE) {
@@ -1573,7 +1679,10 @@ PHP_FUNCTION(stream_is_local)
 			RETURN_THROWS();
 		}

+		php_stream_error_operation_begin();
+		context = php_stream_context_from_zval(zcontext, 0);
 		wrapper = php_stream_locate_url_wrapper(Z_STRVAL_P(zstream), NULL, 0);
+		php_stream_error_operation_end(context);
 	}

 	RETURN_BOOL(wrapper && wrapper->is_url == 0);
@@ -1589,7 +1698,9 @@ PHP_FUNCTION(stream_supports_lock)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

-	RETURN_BOOL(php_stream_supports_lock(stream));
+	php_stream_error_operation_begin();
+	RETVAL_BOOL(php_stream_supports_lock(stream));
+	php_stream_error_operation_end_for_stream(stream);
 }

 /* {{{ Check if a stream is a TTY. */
@@ -1602,6 +1713,8 @@ PHP_FUNCTION(stream_isatty)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
+
 	/* get the fd.
 	 * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag when casting.
 	 * It is only used here so that the buffered data warning is not displayed.
@@ -1611,8 +1724,10 @@ PHP_FUNCTION(stream_isatty)
 	} else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL) == SUCCESS) {
 		php_stream_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, (void*)&fileno, 0);
 	} else {
+		php_stream_error_operation_end_for_stream(stream);
 		RETURN_FALSE;
 	}
+	php_stream_error_operation_end_for_stream(stream);

 #ifdef PHP_WIN32
 	/* Check if the Windows standard handle is redirected to file */
@@ -1642,6 +1757,8 @@ PHP_FUNCTION(sapi_windows_vt100_support)
 		Z_PARAM_BOOL_OR_NULL(enable, enable_is_null)
 	ZEND_PARSE_PARAMETERS_END();

+	php_stream_error_operation_begin();
+
 	/* get the fd.
 	 * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag when casting.
 	 * It is only used here so that the buffered data warning is not displayed.
@@ -1651,6 +1768,7 @@ PHP_FUNCTION(sapi_windows_vt100_support)
 	} else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL) == SUCCESS) {
 		php_stream_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, (void*)&fileno, 0);
 	} else {
+		php_stream_error_operation_end_for_stream(stream);
 		if (!enable_is_null) {
 			php_error_docref(
 				NULL,
@@ -1660,6 +1778,7 @@ PHP_FUNCTION(sapi_windows_vt100_support)
 		}
 		RETURN_FALSE;
 	}
+	php_stream_error_operation_end_for_stream(stream);

 	/* Check if the file descriptor is a console */
 	if (!php_win32_console_fileno_is_console(fileno)) {
@@ -1699,7 +1818,9 @@ PHP_FUNCTION(stream_socket_shutdown)
 		RETURN_THROWS();
 	}

-	RETURN_BOOL(php_stream_xport_shutdown(stream, (stream_shutdown_t)how) == 0);
+	php_stream_error_operation_begin();
+	RETVAL_BOOL(php_stream_xport_shutdown(stream, (stream_shutdown_t)how) == 0);
+	php_stream_error_operation_end_for_stream(stream);
 }
 /* }}} */
 #endif
diff --git a/ext/standard/tests/file/php_fd_wrapper_03.phpt b/ext/standard/tests/file/php_fd_wrapper_03.phpt
index 991c497f5e1..a19d1f5acd9 100644
--- a/ext/standard/tests/file/php_fd_wrapper_03.phpt
+++ b/ext/standard/tests/file/php_fd_wrapper_03.phpt
@@ -10,7 +10,6 @@
 echo "\nDone.\n";
 ?>
 --EXPECTF--
-Warning: fopen(): Invalid php:// URL specified in %s on line %d

 Warning: fopen(php://fd): Failed to open stream: operation failed in %s on line 2

diff --git a/ext/standard/tests/streams/gh14506.phpt b/ext/standard/tests/streams/gh14506.phpt
index f83eba4f1ff..3d82350221d 100644
--- a/ext/standard/tests/streams/gh14506.phpt
+++ b/ext/standard/tests/streams/gh14506.phpt
@@ -86,10 +86,10 @@ function stream_flush(): bool

 Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d

-Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d
-
 Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d

+Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d
+
 Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d
 No stream arrays were passed
 fclose(): Argument #1 ($stream) must be an open stream resource
diff --git a/ext/standard/tests/streams/stream_errors_error_has_code.phpt b/ext/standard/tests/streams/stream_errors_error_has_code.phpt
new file mode 100644
index 00000000000..6cd6b70411a
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_error_has_code.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Stream errors - multiple operations stored
+--FILE--
+<?php
+
+$context = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Silent,
+        'error_store' => StreamErrorStore::All,
+    ]
+]);
+
+// First operation
+$stream1 = fopen('php://nonexistent1', 'r', false, $context);
+$error1 = stream_last_errors()[0];
+
+// Second operation
+$stream2 = fopen('php://nonexistent2', 'r', false, $context);
+$error2 = stream_last_errors()[0];
+
+// Should get the most recent error (second operation)
+if ($error2) {
+    echo "Got most recent error\n";
+    echo "Param contains 'nonexistent2': " . (strpos($error2->param ?? '', 'nonexistent2') !== false ? 'yes' : 'no') . "\n";
+}
+
+?>
+--EXPECT--
+Got most recent error
+Param contains 'nonexistent2': yes
diff --git a/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt
new file mode 100644
index 00000000000..37f5d39bfa7
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_exception_mode_terminal.phpt
@@ -0,0 +1,31 @@
+--TEST--
+Stream errors - exception mode for terminal errors
+--FILE--
+<?php
+
+$context = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Exception,
+    ]
+]);
+
+try {
+    $stream = fopen('php://nonexistent', 'r', false, $context);
+} catch (StreamException $e) {
+    echo "Caught: " . $e->getMessage() . "\n";
+    echo "Code: " . $e->getCode() . "\n";
+
+    $errors = $e->getErrors();
+    if (!empty($errors)) {
+        $error = $errors[0];
+        echo "Wrapper: " . $error->wrapperName . "\n";
+        echo "Error code name: " . $error->code->name . "\n";
+    }
+}
+
+?>
+--EXPECTF--
+Caught: Failed to open stream: operation failed
+Code: 20
+Wrapper: PHP
+Error code name: OpenFailed
diff --git a/ext/standard/tests/streams/stream_errors_invalid_types.phpt b/ext/standard/tests/streams/stream_errors_invalid_types.phpt
new file mode 100644
index 00000000000..1b3ab5e8931
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_invalid_types.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Stream errors - invalid enum type throws TypeError
+--FILE--
+<?php
+
+try {
+    $context = stream_context_create([
+        'stream' => [
+            'error_mode' => 'invalid',
+        ]
+    ]);
+
+    fopen('php://nonexistent', 'r', false, $context);
+} catch (TypeError $e) {
+    echo "Caught TypeError for error_mode\n";
+}
+
+try {
+    $context = stream_context_create([
+        'stream' => [
+            'error_store' => 123,
+        ]
+    ]);
+
+    fopen('php://nonexistent', 'r', false, $context);
+} catch (TypeError $e) {
+    echo "Caught TypeError for error_store\n";
+}
+
+?>
+--EXPECTF--
+
+Warning: fopen(php://nonexistent): Failed to open stream: operation failed in %s on line %d
+Caught TypeError for error_mode
+
+Warning: fopen(php://nonexistent): Failed to open stream: operation failed in %s on line %d
+Caught TypeError for error_store
diff --git a/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt
new file mode 100644
index 00000000000..49c18616dad
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_mix_modes_storage.phpt
@@ -0,0 +1,149 @@
+--TEST--
+Stream errors - silent mode with mixed error stores
+--FILE--
+<?php
+
+class TestStream {
+    public $context;
+    private $position = 0;
+
+    public function stream_open($path, $mode, $options, &$opened_path) {
+        return true;
+    }
+
+    public function stream_read($count) {
+        $this->position += $count;
+        return str_repeat('x', $count + 10);
+    }
+
+    public function stream_eof() {
+        return $this->position >= 100;
+    }
+
+    public function stream_stat() {
+        return [];
+    }
+}
+
+stream_wrapper_register('test', 'TestStream');
+
+function stream_test_errors($title, $contextOptions) {
+    stream_clear_errors();
+    $context = stream_context_create($contextOptions);
+    $stream = fopen('test://foo', 'r', false, $context);
+    try {
+        echo $title . "\n";
+        $readin = fopen('php://stdin', 'r', false, $context);
+        $data = fread($stream, 10);
+
+        $read = [$readin, $stream];
+        $write = NULL;
+        $except = NULL;
+        stream_select($read, $write, $except, 0, 0, $context);
+    } catch (StreamException $e) {
+        echo 'EXCEPTION: ' . $e->getMessage() . "\n";
+    }
+
+    $errors = stream_last_errors();
+    if ($errors) {
+        $first = $errors[0];
+        echo "Error details:\n";
+        echo "- Message: $first->message\n";
+        echo "- Code: " . $first->code->name . "\n";
+        echo "- Wrapper: $first->wrapperName\n";
+        echo "- Terminating: " . ($first->terminating ? 'yes' : 'no') . "\n";
+        echo "- Count: " . count($errors) . "\n";
+
+        foreach ($errors as $idx => $error) {
+            echo "  [$idx] " . $error->code->name . ": " . $error->message . "\n";
+        }
+    } else {
+        echo "No errors stored\n";
+    }
+    echo "\n";
+}
+
+stream_test_errors('ALL', [
+    'stream' => [
+        'error_mode' => StreamErrorMode::Silent,
+        'error_store' => StreamErrorStore::All,
+    ]
+]);
+
+stream_test_errors('NON TERMINATING', [
+    'stream' => [
+        'error_mode' => StreamErrorMode::Silent,
+        'error_store' => StreamErrorStore::NonTerminating,
+    ]
+]);
+
+stream_test_errors('TERMINATING', [
+    'stream' => [
+        'error_mode' => StreamErrorMode::Silent,
+        'error_store' => StreamErrorStore::Terminating,
+    ]
+]);
+
+stream_test_errors('AUTO EXCEPTION', [
+    'stream' => [
+        'error_mode' => StreamErrorMode::Exception,
+        'error_store' => StreamErrorStore::Auto,
+    ]
+]);
+
+stream_test_errors('AUTO ERROR', [
+    'stream' => [
+        'error_mode' => StreamErrorMode::Error,
+        'error_store' => StreamErrorStore::Auto,
+    ]
+]);
+
+?>
+--EXPECTF--
+ALL
+Error details:
+- Message: TestStream::stream_cast is not implemented!
+- Code: NotImplemented
+- Wrapper: user-space
+- Terminating: yes
+- Count: 2
+  [0] NotImplemented: TestStream::stream_cast is not implemented!
+  [1] CastNotSupported: Cannot represent a stream of type user-space as a select()able descriptor
+
+NON TERMINATING
+Error details:
+- Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost
+- Code: UserspaceInvalidReturn
+- Wrapper: user-space
+- Terminating: no
+- Count: 1
+  [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost
+
+TERMINATING
+Error details:
+- Message: TestStream::stream_cast is not implemented!
+- Code: NotImplemented
+- Wrapper: user-space
+- Terminating: yes
+- Count: 2
+  [0] NotImplemented: TestStream::stream_cast is not implemented!
+  [1] CastNotSupported: Cannot represent a stream of type user-space as a select()able descriptor
+
+AUTO EXCEPTION
+EXCEPTION: TestStream::stream_cast is not implemented!
+Error details:
+- Message: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost
+- Code: UserspaceInvalidReturn
+- Wrapper: user-space
+- Terminating: no
+- Count: 1
+  [0] UserspaceInvalidReturn: TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost
+
+AUTO ERROR
+
+Warning: fread(): TestStream::stream_read - read 10 bytes more data than requested (8202 read, 8192 max) - excess data will be lost in %s on line %d
+
+Warning: stream_select(): TestStream::stream_cast is not implemented! in %s on line %d
+
+Warning: stream_select(): Cannot represent a stream of type user-space as a select()able descriptor in %s on line %d
+No errors stored
diff --git a/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt b/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt
new file mode 100644
index 00000000000..2575a8a795f
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_modes_with_auto_store.phpt
@@ -0,0 +1,49 @@
+--TEST--
+Stream errors - error_store AUTO mode behavior
+--FILE--
+<?php
+
+// AUTO with ERROR mode should store NONE
+$context1 = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Error,
+        'error_store' => StreamErrorStore::Auto,
+    ]
+]);
+
+@fopen('php://nonexistent', 'r', false, $context1);
+$errors1 = stream_last_errors();
+echo "ERROR mode AUTO: " . (!empty($errors1) ? "has error" : "no error") . "\n";
+
+// AUTO with EXCEPTION mode should store NON_TERM
+$context2 = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Exception,
+        'error_store' => StreamErrorStore::Auto,
+    ]
+]);
+
+try {
+    fopen('php://nonexistent2', 'r', false, $context2);
+} catch (StreamException $e) {}
+
+$errors2 = stream_last_errors();
+echo "EXCEPTION mode AUTO: " . (!empty($errors2) ? "has error" : "no error") . "\n";
+
+// AUTO with SILENT mode should store ALL
+$context3 = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Silent,
+        'error_store' => StreamErrorStore::Auto,
+    ]
+]);
+
+fopen('php://nonexistent3', 'r', false, $context3);
+$errors3 = stream_last_errors();
+echo "SILENT mode AUTO: " . (!empty($errors3) ? "has error" : "no error") . "\n";
+
+?>
+--EXPECTF--
+ERROR mode AUTO: no error
+EXCEPTION mode AUTO: %s
+SILENT mode AUTO: has error
diff --git a/ext/standard/tests/streams/stream_errors_set_default_context_error.phpt b/ext/standard/tests/streams/stream_errors_set_default_context_error.phpt
new file mode 100644
index 00000000000..46fc0f7fd34
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_set_default_context_error.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Stream errors - prohibit setting error mode in default context
+--FILE--
+<?php
+
+try {
+    stream_context_set_default([
+        'stream' => [
+            'error_mode' => StreamErrorMode::Exception,
+        ]
+    ]);
+} catch (\ValueError $e) {
+    echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+Stream error handling options cannot be set on the default context
diff --git a/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt b/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt
new file mode 100644
index 00000000000..456575affc3
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_silent_with_handler.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Stream errors - custom error handler
+--FILE--
+<?php
+
+$handler_called = false;
+
+$context = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Silent,
+        'error_handler' => function(array $errors) use (&$handler_called) {
+            $handler_called = true;
+            echo "Handler called\n";
+            foreach ($errors as $error) {
+                echo "Wrapper: " . $error->wrapperName . "\n";
+                echo "Code: " . $error->code->name . "\n";
+                echo "Message: " . $error->message . "\n";
+                echo "Param: " . ($error->param ?? 'null') . "\n";
+                echo "Terminating: " . ($error->terminating ? 'yes' : 'no') . "\n";
+            }
+        }
+    ]
+]);
+
+$stream = fopen('php://nonexistent', 'r', false, $context);
+
+var_dump($handler_called);
+
+?>
+--EXPECT--
+Handler called
+Wrapper: PHP
+Code: OpenFailed
+Message: Failed to open stream: operation failed
+Param: php://nonexistent
+Terminating: yes
+bool(true)
diff --git a/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt b/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt
new file mode 100644
index 00000000000..9e356216b0d
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_silent_with_storage.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Stream errors - silent mode with error storage
+--FILE--
+<?php
+
+$context = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Silent,
+    ]
+]);
+
+$stream = fopen('php://nonexistent', 'r', false, $context);
+var_dump($stream);
+
+$errors = stream_last_errors();
+foreach ($errors as $error) {
+    echo "Has error: yes\n";
+    echo "Error code: " . $error->code->name . "\n";
+    echo "Error wrapper: " . $error->wrapperName . "\n";
+    echo "Error message: " . $error->message . "\n";
+}
+
+?>
+--EXPECT--
+bool(false)
+Has error: yes
+Error code: OpenFailed
+Error wrapper: PHP
+Error message: Failed to open stream: operation failed
diff --git a/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt b/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt
new file mode 100644
index 00000000000..8155ce60bfe
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_silent_without_storage.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Stream errors - error_store NONE option
+--FILE--
+<?php
+
+$context = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Silent,
+        'error_store' => StreamErrorStore::None,
+    ]
+]);
+
+$stream = fopen('php://nonexistent', 'r', false, $context);
+
+$errors = stream_last_errors();
+echo "Has error: " . (!empty($error) ? "yes" : "no") . "\n";
+
+?>
+--EXPECT--
+Has error: no
diff --git a/ext/standard/tests/streams/stream_errors_standard_error.phpt b/ext/standard/tests/streams/stream_errors_standard_error.phpt
new file mode 100644
index 00000000000..42de8cc3b17
--- /dev/null
+++ b/ext/standard/tests/streams/stream_errors_standard_error.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Stream errors - error mode with standard error reporting
+--FILE--
+<?php
+
+$context = stream_context_create([
+    'stream' => [
+        'error_mode' => StreamErrorMode::Error,
+    ]
+]);
+
+// This will trigger a warning
+$stream = fopen('php://nonexistent', 'r', false, $context);
+
+var_dump($stream);
+
+?>
+--EXPECTF--
+Warning: fopen(php://nonexistent): Failed to open stream: %s in %s on line %d
+bool(false)
diff --git a/main/php_streams.h b/main/php_streams.h
index d248de7a816..7622a7295af 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -246,6 +246,8 @@ struct _php_stream  {
 #endif

 	struct _php_stream *enclosing_stream; /* this is a private stream owned by enclosing_stream */
+
+	zend_llist *error_list;
 }; /* php_stream */

 #define PHP_STREAM_CONTEXT(stream) \
@@ -537,6 +539,7 @@ PHPAPI ssize_t _php_stream_passthru(php_stream * src STREAMS_DC);
 #define php_stream_passthru(stream)	_php_stream_passthru((stream) STREAMS_CC)
 END_EXTERN_C()

+#include "streams/php_stream_errors.h"
 #include "streams/php_stream_transport.h"
 #include "streams/php_stream_plain_wrapper.h"
 #include "streams/php_stream_glob_wrapper.h"
@@ -640,10 +643,6 @@ PHPAPI const char *php_stream_locate_eol(php_stream *stream, zend_string *buf);

 #define php_stream_open_wrapper(path, mode, options, opened)	_php_stream_open_wrapper_ex((path), (mode), (options), (opened), NULL STREAMS_CC)
 #define php_stream_open_wrapper_ex(path, mode, options, opened, context)	_php_stream_open_wrapper_ex((path), (mode), (options), (opened), (context) STREAMS_CC)
-
-/* pushes an error message onto the stack for a wrapper instance */
-PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, int options, const char *fmt, ...) PHP_ATTRIBUTE_FORMAT(printf, 3, 4);
-
 typedef enum {
 	PHP_STREAM_UNCHANGED = 0, /* orig stream was seekable anyway */
 	PHP_STREAM_RELEASED = 1, /* newstream should be used; origstream is no longer valid */
diff --git a/main/streams/cast.c b/main/streams/cast.c
index 10c93cbb3ff..f480f3cd59a 100644
--- a/main/streams/cast.c
+++ b/main/streams/cast.c
@@ -187,7 +187,6 @@ void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *resul
 	result[res_curs] = '\0';
 }
 /* }}} */
-
 /* {{{ php_stream_cast */
 PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err)
 {
@@ -257,7 +256,7 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret,
 			b) no memory
 			-> lets bail
 		*/
-		php_error_docref(NULL, E_ERROR, "fopencookie failed");
+		php_stream_fatal(stream, CastFailed, "fopencookie failed");
 		return FAILURE;
 #endif

@@ -297,7 +296,8 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret,

 	if (php_stream_is_filtered(stream) && castas != PHP_STREAM_AS_FD_FOR_SELECT) {
 		if (show_err) {
-			php_error_docref(NULL, E_WARNING, "Cannot cast a filtered stream on this system");
+			php_stream_warn(stream, CastNotSupported,
+				"Cannot cast a filtered stream on this system");
 		}
 		return FAILURE;
 	} else if (stream->ops->cast && stream->ops->cast(stream, castas, ret) == SUCCESS) {
@@ -313,7 +313,8 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret,
 			"select()able descriptor"
 		};

-		php_error_docref(NULL, E_WARNING, "Cannot represent a stream of type %s as a %s", stream->ops->label, cast_names[castas]);
+		php_stream_warn(stream, CastNotSupported,
+			"Cannot represent a stream of type %s as a %s", stream->ops->label, cast_names[castas]);
 	}

 	return FAILURE;
@@ -328,7 +329,9 @@ PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret,
 		 * will be accessing the stream.  Emit a warning so that the end-user will
 		 * know that they should try something else */

-		php_error_docref(NULL, E_WARNING, ZEND_LONG_FMT " bytes of buffered data lost during stream conversion!", (zend_long)(stream->writepos - stream->readpos));
+		php_stream_warn_nt(stream, BufferedDataLost,
+			ZEND_LONG_FMT " bytes of buffered data lost during stream conversion!",
+			(zend_long)(stream->writepos - stream->readpos));
 	}

 	if (castas == PHP_STREAM_AS_STDIO && ret) {
diff --git a/main/streams/filter.c b/main/streams/filter.c
index 3a19f5ce918..35dbef455a3 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -391,7 +391,8 @@ PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, p
 					php_stream_bucket_unlink(bucket);
 					php_stream_bucket_delref(bucket);
 				}
-				php_error_docref(NULL, E_WARNING, "Filter failed to process pre-buffered data");
+				php_stream_warn(stream, FilterFailed,
+						"Filter failed to process pre-buffered data");
 				return FAILURE;
 			case PSFS_FEED_ME:
 				/* We don't actually need data yet,
diff --git a/main/streams/memory.c b/main/streams/memory.c
index 44336f0e3e2..af54c46dd9a 100644
--- a/main/streams/memory.c
+++ b/main/streams/memory.c
@@ -362,7 +362,9 @@ static ssize_t php_stream_temp_write(php_stream *stream, const char *buf, size_t
 			zend_string *membuf = php_stream_memory_get_buffer(ts->innerstream);
 			php_stream *file = php_stream_fopen_temporary_file(ts->tmpdir, "php", NULL);
 			if (file == NULL) {
-				php_error_docref(NULL, E_WARNING, "Unable to create temporary file, Check permissions in temporary files directory.");
+				php_stream_wrapper_warn(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
+						PermissionDenied,
+						"Unable to create temporary file, Check permissions in temporary files directory.");
 				return 0;
 			}
 			php_stream_write(file, ZSTR_VAL(membuf), ZSTR_LEN(membuf));
@@ -489,7 +491,8 @@ static int php_stream_temp_cast(php_stream *stream, int castas, void **ret)

 	file = php_stream_fopen_tmpfile();
 	if (file == NULL) {
-		php_error_docref(NULL, E_WARNING, "Unable to create temporary file.");
+		php_stream_wrapper_warn(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
+				PermissionDenied, "Unable to create temporary file.");
 		return FAILURE;
 	}

@@ -639,7 +642,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con
 	}

 	if ((comma = (char *) memchr(path, ',', dlen)) == NULL) {
-		php_stream_wrapper_log_error(wrapper, options, "rfc2397: no comma in URL");
+		php_stream_wrapper_log_warn(wrapper, context, options,
+				InvalidUrl, "rfc2397: no comma in URL");
 		return NULL;
 	}

@@ -651,7 +655,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con
 		sep = memchr(path, '/', mlen);

 		if (!semi && !sep) {
-			php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal media type");
+			php_stream_wrapper_log_warn(wrapper, context, options,
+					InvalidUrl, "rfc2397: illegal media type");
 			return NULL;
 		}

@@ -666,7 +671,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con
 			path += plen;
 		} else if (semi != path || mlen != sizeof(";base64")-1 || memcmp(path, ";base64", sizeof(";base64")-1)) { /* must be error since parameters are only allowed after mediatype */
 			zval_ptr_dtor(&meta);
-			php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal media type");
+			php_stream_wrapper_log_warn(wrapper, context, options,
+					InvalidUrl, "rfc2397: illegal media type");
 			return NULL;
 		}
 		/* get parameters and potentially ';base64' */
@@ -679,7 +685,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con
 				if (mlen != sizeof("base64")-1 || memcmp(path, "base64", sizeof("base64")-1)) {
 					/* must be error since parameters are only allowed after mediatype and we have no '=' sign */
 					zval_ptr_dtor(&meta);
-					php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal parameter");
+					php_stream_wrapper_log_warn(wrapper, context, options,
+							InvalidParam, "rfc2397: illegal parameter");
 					return NULL;
 				}
 				base64 = 1;
@@ -699,7 +706,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con
 		}
 		if (mlen) {
 			zval_ptr_dtor(&meta);
-			php_stream_wrapper_log_error(wrapper, options, "rfc2397: illegal URL");
+			php_stream_wrapper_log_warn(wrapper, context, options,
+					InvalidUrl, "rfc2397: illegal URL");
 			return NULL;
 		}
 	} else {
@@ -715,7 +723,8 @@ static php_stream * php_stream_url_wrap_rfc2397(php_stream_wrapper *wrapper, con
 		base64_comma = php_base64_decode_ex((const unsigned char *)comma, dlen, 1);
 		if (!base64_comma) {
 			zval_ptr_dtor(&meta);
-			php_stream_wrapper_log_error(wrapper, options, "rfc2397: unable to decode");
+			php_stream_wrapper_log_warn(wrapper, context, options,
+					DecodingFailed, "rfc2397: unable to decode");
 			return NULL;
 		}
 		comma = ZSTR_VAL(base64_comma);
diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h
new file mode 100644
index 00000000000..c55c06e37f2
--- /dev/null
+++ b/main/streams/php_stream_errors.h
@@ -0,0 +1,239 @@
+/*
+   +----------------------------------------------------------------------+
+   | Copyright (c) The PHP Group                                          |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | https://www.php.net/license/3_01.txt                                 |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Jakub Zelenka <bukka@php.net>                               |
+   +----------------------------------------------------------------------+
+ */
+
+#ifndef PHP_STREAM_ERRORS_H
+#define PHP_STREAM_ERRORS_H
+
+#include "php.h"
+#include "php_streams.h"
+#include "stream_errors_decl.h"
+
+BEGIN_EXTERN_C()
+
+/* Error mode context options (internal C constants) */
+#define PHP_STREAM_ERROR_MODE_ERROR 0
+#define PHP_STREAM_ERROR_MODE_EXCEPTION 1
+#define PHP_STREAM_ERROR_MODE_SILENT 2
+
+/* Error store context options (internal C constants) */
+#define PHP_STREAM_ERROR_STORE_AUTO 0
+#define PHP_STREAM_ERROR_STORE_NONE 1
+#define PHP_STREAM_ERROR_STORE_NON_TERM 2
+#define PHP_STREAM_ERROR_STORE_TERMINAL 3
+#define PHP_STREAM_ERROR_STORE_ALL 4
+
+/* Maximum operation nesting depth */
+#define PHP_STREAM_ERROR_MAX_DEPTH 1000
+/* Operations pool size to prevent extra allocations */
+#define PHP_STREAM_ERROR_OPERATION_POOL_SIZE 8
+
+/* Shorthand for error code enum values */
+#define PHP_STREAM_EC(name) ZEND_ENUM_StreamErrorCode_##name
+
+/* Error code range boundaries (case_id based, ranges are [start, end)) */
+#define STREAM_EC_IO_START        PHP_STREAM_EC(ReadFailed)
+#define STREAM_EC_IO_END          PHP_STREAM_EC(Disabled)
+#define STREAM_EC_FS_START        PHP_STREAM_EC(Disabled)
+#define STREAM_EC_FS_END          PHP_STREAM_EC(NotImplemented)
+#define STREAM_EC_WRAPPER_START   PHP_STREAM_EC(NotImplemented)
+#define STREAM_EC_WRAPPER_END     PHP_STREAM_EC(FilterNotFound)
+#define STREAM_EC_FILTER_START    PHP_STREAM_EC(FilterNotFound)
+#define STREAM_EC_FILTER_END      PHP_STREAM_EC(CastFailed)
+#define STREAM_EC_CAST_START      PHP_STREAM_EC(CastFailed)
+#define STREAM_EC_CAST_END        PHP_STREAM_EC(NetworkSendFailed)
+#define STREAM_EC_NETWORK_START   PHP_STREAM_EC(NetworkSendFailed)
+#define STREAM_EC_NETWORK_END     PHP_STREAM_EC(ArchivingFailed)
+#define STREAM_EC_ENCODING_START  PHP_STREAM_EC(ArchivingFailed)
+#define STREAM_EC_ENCODING_END    PHP_STREAM_EC(AllocationFailed)
+#define STREAM_EC_RESOURCE_START  PHP_STREAM_EC(AllocationFailed)
+#define STREAM_EC_RESOURCE_END    PHP_STREAM_EC(LockFailed)
+#define STREAM_EC_LOCK_START      PHP_STREAM_EC(LockFailed)
+#define STREAM_EC_LOCK_END        PHP_STREAM_EC(UserspaceNotImplemented)
+#define STREAM_EC_USERSPACE_START PHP_STREAM_EC(UserspaceNotImplemented)
+#define STREAM_EC_USERSPACE_END   (PHP_STREAM_EC(UserspaceCallFailed) + 1)
+
+/* Wrapper name for PHP errors */
+#define PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME ":na"
+#define PHP_STREAM_ERROR_WRAPPER_NAME(_wrapper) \
+	(_wrapper ? _wrapper->wops->label : PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)
+
+/* Error entry in chain (internal linked list) */
+typedef struct _php_stream_error_entry {
+	zend_string *message;
+	zend_enum_StreamErrorCode code;
+	char *wrapper_name;
+	char *param;
+	char *docref;
+	int severity;
+	bool terminating;
+	struct _php_stream_error_entry *next;
+} php_stream_error_entry;
+
+/* Active error operation */
+typedef struct _php_stream_error_operation {
+	php_stream_error_entry *first_error;
+	php_stream_error_entry *last_error;
+	uint32_t error_count;
+} php_stream_error_operation;
+
+/* Stored completed operation */
+typedef struct _php_stream_stored_error {
+	php_stream_error_entry *first_error;
+	uint32_t error_count;
+	struct _php_stream_stored_error *next;
+} php_stream_stored_error;
+
+typedef struct {
+	php_stream_error_operation *current_operation;
+	uint32_t operation_depth;
+	php_stream_stored_error *stored_errors;
+	uint32_t stored_count;
+	php_stream_error_operation operation_pool[PHP_STREAM_ERROR_OPERATION_POOL_SIZE];
+	php_stream_error_operation *overflow_operations;
+	uint32_t overflow_capacity;
+} php_stream_error_state;
+
+/* Error operation management */
+PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void);
+PHPAPI void php_stream_error_operation_end(php_stream_context *context);
+PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream);
+PHPAPI void php_stream_error_operation_abort(void);
+
+/* State cleanup function */
+PHPAPI void php_stream_error_state_cleanup(void);
+
+/* Retrieve last stored errors as array of StreamError objects */
+PHPAPI void php_stream_error_get_last(zval *return_value);
+
+/* Clear all stored errors */
+PHPAPI void php_stream_error_clear_stored(void);
+
+/* Wrapper error reporting functions */
+PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name,
+		php_stream_context *context, const char *docref, int options, int severity,
+		bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
+		ZEND_ATTRIBUTE_FORMAT(printf, 8, 9);
+
+PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context,
+		const char *docref, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, const char *fmt, ...)
+		ZEND_ATTRIBUTE_FORMAT(printf, 8, 9);
+
+PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context,
+		const char *docref, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, const char *param, const char *fmt, ...)
+		ZEND_ATTRIBUTE_FORMAT(printf, 9, 10);
+
+PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper,
+		php_stream_context *context, const char *docref, int options, int severity,
+		bool terminating, zend_enum_StreamErrorCode code, const char *param1, const char *param2,
+		const char *fmt, ...) ZEND_ATTRIBUTE_FORMAT(printf, 10, 11);
+
+PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity,
+		bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
+		ZEND_ATTRIBUTE_FORMAT(printf, 6, 7);
+
+/* Legacy wrapper error log functions */
+PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper,
+		php_stream_context *context, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, const char *fmt, ...)
+		ZEND_ATTRIBUTE_FORMAT(printf, 7, 8);
+
+PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper,
+		php_stream_context *context, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, const char *param, const char *fmt, ...)
+		ZEND_ATTRIBUTE_FORMAT(printf, 8, 9);
+
+PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name,
+		php_stream_context *context, zend_enum_StreamErrorCode code, const char *path,
+		const char *caption);
+
+PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper,
+		php_stream_context *context, zend_enum_StreamErrorCode code, const char *path,
+		const char *caption);
+
+PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name);
+PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper);
+
+/* Convenience macros - code argument is the bare case name (e.g. RenameFailed) */
+#define php_stream_wrapper_warn(wrapper, context, options, code, ...) \
+	php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, true, \
+			PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_warn_name(wrapper_name, context, options, code, ...) \
+	php_stream_wrapper_error_with_name( \
+			wrapper_name, context, NULL, options, E_WARNING, true, \
+			PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_warn_nt(wrapper, context, options, code, ...) \
+	php_stream_wrapper_error(wrapper, context, NULL, options, E_WARNING, false, \
+			PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_notice(wrapper, context, options, code, ...) \
+	php_stream_wrapper_error(wrapper, context, NULL, options, E_NOTICE, false, \
+			PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_warn_param(wrapper, context, options, code, param, ...) \
+	php_stream_wrapper_error_param( \
+			wrapper, context, NULL, options, E_WARNING, true, \
+			PHP_STREAM_EC(code), param, __VA_ARGS__)
+
+#define php_stream_wrapper_warn_param_nt(wrapper, context, options, code, param, ...) \
+	php_stream_wrapper_error_param( \
+			wrapper, context, NULL, options, E_WARNING, false, \
+			PHP_STREAM_EC(code), param, __VA_ARGS__)
+
+#define php_stream_wrapper_warn_param2(wrapper, context, options, code, param1, param2, ...) \
+	php_stream_wrapper_error_param2( \
+			wrapper, context, NULL, options, E_WARNING, true, \
+			PHP_STREAM_EC(code), param1, param2, __VA_ARGS__)
+
+#define php_stream_wrapper_warn_param2_nt(wrapper, context, options, code, param1, param2, ...) \
+	php_stream_wrapper_error_param2( \
+			wrapper, context, NULL, options, E_WARNING, false, \
+			PHP_STREAM_EC(code), param1, param2, __VA_ARGS__)
+
+#define php_stream_warn(stream, code, ...) \
+	php_stream_error(stream, NULL, E_WARNING, true, PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_warn_nt(stream, code, ...) \
+	php_stream_error(stream, NULL, E_WARNING, false, PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_warn_docref(stream, docref, code, ...) \
+	php_stream_error(stream, docref, E_WARNING, true, PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_notice(stream, code, ...) \
+	php_stream_error(stream, NULL, E_NOTICE, false, PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_fatal(stream, code, ...) \
+	php_stream_error(stream, NULL, E_ERROR, true, PHP_STREAM_EC(code), __VA_ARGS__)
+
+/* Legacy log variants */
+#define php_stream_wrapper_log_warn(wrapper, context, options, code, ...) \
+	php_stream_wrapper_log_error(wrapper, context, options, E_WARNING, true, \
+			PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_log_warn_nt(wrapper, context, options, code, ...) \
+	php_stream_wrapper_log_error(wrapper, context, options, E_WARNING, false, \
+			PHP_STREAM_EC(code), __VA_ARGS__)
+
+#define php_stream_wrapper_log_notice(wrapper, context, options, code, ...) \
+	php_stream_wrapper_log_error(wrapper, context, options, E_NOTICE, false, \
+			PHP_STREAM_EC(code), __VA_ARGS__)
+
+END_EXTERN_C()
+
+#endif /* PHP_STREAM_ERRORS_H */
diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c
index 69258158fbe..9335ab3fdb6 100644
--- a/main/streams/plain_wrapper.c
+++ b/main/streams/plain_wrapper.c
@@ -392,7 +392,8 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun
 			}
 			if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) {
 				char errstr[256];
-				php_error_docref(NULL, E_NOTICE, "Write of %zu bytes failed with errno=%d %s",
+				php_stream_notice(stream, WriteFailed,
+						"Write of %zu bytes failed with errno=%d %s",
 						count, errno, php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 			}
 		}
@@ -470,7 +471,8 @@ static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count)
 			} else {
 				if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) {
 					char errstr[256];
-					php_error_docref(NULL, E_NOTICE, "Read of %zu bytes failed with errno=%d %s",
+					php_stream_notice(stream, ReadFailed,
+							"Read of %zu bytes failed with errno=%d %s",
 							count, errno, php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 				}

@@ -619,7 +621,8 @@ static int php_stdiop_seek(php_stream *stream, zend_off_t offset, int whence, ze
 	assert(data != NULL);

 	if (!data->is_seekable) {
-		php_error_docref(NULL, E_WARNING, "Cannot seek on this stream");
+		php_stream_wrapper_warn(NULL, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
+				SeekNotSupported, "Cannot seek on this stream");
 		return -1;
 	}

@@ -1156,7 +1159,8 @@ PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zen
 	char *persistent_id = NULL;

 	if (FAILURE == php_stream_parse_fopen_modes(mode, &open_flags)) {
-		php_stream_wrapper_log_error(&php_plain_files_wrapper, options, "`%s' is not a valid mode for fopen", mode);
+		php_stream_wrapper_log_warn(&php_plain_files_wrapper, NULL, options,
+				InvalidMode, "`%s' is not a valid mode for fopen", mode);
 		return NULL;
 	}

@@ -1309,8 +1313,9 @@ static int php_plain_files_unlink(php_stream_wrapper *wrapper, const char *url,
 	if (ret == -1) {
 		if (options & REPORT_ERRORS) {
 			char errstr[256];
-			php_error_docref1(NULL, url, E_WARNING, "%s",
-					php_socket_strerror_s(errno, errstr, sizeof(errstr)));
+			php_stream_wrapper_warn_param(wrapper, context, options,
+					UnlinkFailed, url,
+					"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 		}
 		return 0;
 	}
@@ -1377,20 +1382,22 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f
 					 * access to the file in the meantime.
 					 */
 					if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) {
-						php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s",
-								php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 						if (errno != EPERM) {
 							success = 0;
 						}
+						php_stream_wrapper_error_param2(wrapper, context, NULL, options, E_WARNING,
+								!success, PHP_STREAM_EC(ChownFailed), url_from, url_to,
+								"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 					}

 					if (success) {
 						if (VCWD_CHMOD(url_to, sb.st_mode)) {
-							php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s",
-									php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 							if (errno != EPERM) {
 								success = 0;
 							}
+							php_stream_wrapper_error_param2(wrapper, context, NULL, options, E_WARNING,
+									!success, PHP_STREAM_EC(ChownFailed), url_from, url_to,
+									"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 						}
 					}
 #  endif
@@ -1398,12 +1405,14 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f
 						VCWD_UNLINK(url_from);
 					}
 				} else {
-					php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s",
-							php_socket_strerror_s(errno, errstr, sizeof(errstr)));
+					php_stream_wrapper_warn_param2_nt(wrapper, context, options, StatFailed,
+							url_from, url_to,
+							"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 				}
 			} else {
-				php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s",
-						php_socket_strerror_s(errno, errstr, sizeof(errstr)));
+				php_stream_wrapper_warn_param2_nt(wrapper, context, options, CopyFailed,
+						url_from, url_to,
+						"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 			}
 #  if !defined(ZTS) && !defined(TSRM_WIN32)
 			umask(oldmask);
@@ -1416,8 +1425,9 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f
 #ifdef PHP_WIN32
 		php_win32_docref2_from_error(GetLastError(), url_from, url_to);
 #else
-		php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s",
-				php_socket_strerror_s(errno, errstr, sizeof(errstr)));
+		php_stream_wrapper_warn_param2(wrapper, context, options,
+				RenameFailed, url_from, url_to,
+				"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 #endif
 		return 0;
 	}
@@ -1441,7 +1451,8 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i

 		int ret = VCWD_MKDIR(dir, (mode_t)mode);
 		if (ret < 0 && (options & REPORT_ERRORS)) {
-			php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
+			php_stream_wrapper_warn(wrapper, context, options,
+					MkdirFailed, "%s", strerror(errno));
 			return 0;
 		}

@@ -1450,7 +1461,8 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i

 	char buf[MAXPATHLEN];
 	if (!expand_filepath_with_mode(dir, buf, NULL, 0, CWD_EXPAND)) {
-		php_error_docref(NULL, E_WARNING, "Invalid path");
+		php_stream_wrapper_warn(wrapper, context, options,
+				InvalidPath, "Invalid path");
 		return 0;
 	}

@@ -1502,7 +1514,9 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i
 		int ret = VCWD_MKDIR(buf, (mode_t) mode);
 		if (ret < 0 && errno != EEXIST) {
 			if (options & REPORT_ERRORS) {
-				php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
+				php_stream_wrapper_warn(wrapper, context, options,
+						MkdirFailed,
+						"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 			}
 			return 0;
 		}
@@ -1522,7 +1536,9 @@ static int php_plain_files_mkdir(php_stream_wrapper *wrapper, const char *dir, i
 			/* issue a warning to client when the last directory was created failed */
 			if (ret < 0) {
 				if (options & REPORT_ERRORS) {
-					php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
+					php_stream_wrapper_warn(wrapper, context, options,
+							MkdirFailed,
+							"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 				}
 				return 0;
 			}
@@ -1544,13 +1560,17 @@ static int php_plain_files_rmdir(php_stream_wrapper *wrapper, const char *url, i
 	char errstr[256];
 #ifdef PHP_WIN32
 	if (!php_win32_check_trailing_space(url, strlen(url))) {
-		php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr)));
+		php_stream_wrapper_warn_param(wrapper, context, options,
+				NotFound, url,
+				"%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr)));
 		return 0;
 	}
 #endif

 	if (VCWD_RMDIR(url) < 0) {
-		php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
+		php_stream_wrapper_warn_param(wrapper, context, options,
+				RmdirFailed, url,
+				"%s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 		return 0;
 	}

@@ -1573,7 +1593,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url

 #ifdef PHP_WIN32
 	if (!php_win32_check_trailing_space(url, strlen(url))) {
-		php_error_docref1(NULL, url, E_WARNING, "%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr)));
+		php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS,
+				NotFound, url,
+				"%s", php_socket_strerror_s(ENOENT, errstr, sizeof(errstr)));
 		return 0;
 	}
 #endif
@@ -1592,7 +1614,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url
 			if (VCWD_ACCESS(url, F_OK) != 0) {
 				FILE *file = VCWD_FOPEN(url, "w");
 				if (file == NULL) {
-					php_error_docref1(NULL, url, E_WARNING, "Unable to create file %s because %s", url,
+					php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS,
+							PermissionDenied, url,
+							"Unable to create file %s because %s", url,
 							php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 					return 0;
 				}
@@ -1606,7 +1630,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url
 		case PHP_STREAM_META_OWNER:
 			if(option == PHP_STREAM_META_OWNER_NAME) {
 				if(php_get_uid_by_name((char *)value, &uid) != SUCCESS) {
-					php_error_docref1(NULL, url, E_WARNING, "Unable to find uid for %s", (char *)value);
+					php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS,
+							MetaFailed, url,
+							"Unable to find uid for %s", (char *)value);
 					return 0;
 				}
 			} else {
@@ -1618,7 +1644,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url
 		case PHP_STREAM_META_GROUP_NAME:
 			if(option == PHP_STREAM_META_GROUP_NAME) {
 				if(php_get_gid_by_name((char *)value, &gid) != SUCCESS) {
-					php_error_docref1(NULL, url, E_WARNING, "Unable to find gid for %s", (char *)value);
+					php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS,
+							MetaFailed, url,
+							"Unable to find gid for %s", (char *)value);
 					return 0;
 				}
 			} else {
@@ -1636,8 +1664,9 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url
 			return 0;
 	}
 	if (ret == -1) {
-		php_error_docref1(NULL, url, E_WARNING, "Operation failed: %s",
-				php_socket_strerror_s(errno, errstr, sizeof(errstr)));
+		php_stream_wrapper_warn_param(wrapper, context, REPORT_ERRORS,
+				MetaFailed, url,
+				"Operation failed: %s", php_socket_strerror_s(errno, errstr, sizeof(errstr)));
 		return 0;
 	}
 	php_clear_stat_cache(0, NULL, 0);
@@ -1730,7 +1759,9 @@ PHPAPI php_stream *_php_stream_fopen_with_path(const char *filename, const char
 		*(cwd+3) = '\0';

 		if (snprintf(trypath, MAXPATHLEN, "%s%s", cwd, filename) >= MAXPATHLEN) {
-			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
+			php_stream_wrapper_notice(NULL, NULL, REPORT_ERRORS,
+					PathTooLong,
+					"%s/%s path was truncated to %d", cwd, filename, MAXPATHLEN);
 		}

 		efree(cwd);
@@ -1785,7 +1816,9 @@ PHPAPI php_stream *_php_stream_fopen_with_path(const char *filename, const char
 			goto stream_skip;
 		}
 		if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
-			php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
+			php_stream_wrapper_notice(NULL, NULL, REPORT_ERRORS,
+					PathTooLong,
+					"%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
 		}

 		if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir_ex(trypath, 0)) {
diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c
new file mode 100644
index 00000000000..1131f70ec29
--- /dev/null
+++ b/main/streams/stream_errors.c
@@ -0,0 +1,921 @@
+/*
+   +----------------------------------------------------------------------+
+   | Copyright (c) The PHP Group                                          |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | https://www.php.net/license/3_01.txt                                 |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Jakub Zelenka <bukka@php.net>                               |
+   +----------------------------------------------------------------------+
+ */
+
+#define ZEND_ENUM_StreamErrorCode_USE_NAME_TABLE
+#include "php.h"
+#include "php_globals.h"
+#include "php_streams.h"
+#include "php_stream_errors.h"
+#include "zend_enum.h"
+#include "zend_exceptions.h"
+#include "ext/standard/file.h"
+#include "stream_errors_arginfo.h"
+
+/* Class entries */
+static zend_class_entry *php_ce_stream_error_code;
+static zend_class_entry *php_ce_stream_error_mode;
+static zend_class_entry *php_ce_stream_error_store;
+static zend_class_entry *php_ce_stream_error;
+static zend_class_entry *php_ce_stream_exception;
+
+/* Forward declarations */
+static void php_stream_error_entry_free(php_stream_error_entry *entry);
+
+/* Helper to create a single StreamError object from an entry */
+static void php_stream_error_create_object(zval *zv, php_stream_error_entry *entry)
+{
+	object_init_ex(zv, php_ce_stream_error);
+
+	const char *case_name = NULL;
+	if (entry->code > 0 && entry->code <= ZEND_ENUM_StreamErrorCode_CASE_COUNT) {
+		case_name = zend_enum_StreamErrorCode_case_names[entry->code];
+	}
+	if (!case_name) {
+		case_name = "Generic";
+	}
+
+	zend_object *enum_obj = zend_enum_get_case_cstr(php_ce_stream_error_code, case_name);
+	ZEND_ASSERT(enum_obj != NULL);
+
+	zval code_enum;
+	ZVAL_OBJ_COPY(&code_enum, enum_obj);
+
+	zend_update_property(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("code"), &code_enum);
+	zval_ptr_dtor(&code_enum);
+
+	zend_update_property_str(
+			php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("message"), entry->message);
+
+	zend_update_property_string(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("wrapperName"),
+			entry->wrapper_name ? entry->wrapper_name : "");
+
+	zend_update_property_long(
+			php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("severity"), entry->severity);
+
+	zend_update_property_bool(
+			php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("terminating"), entry->terminating);
+
+	if (entry->param) {
+		zend_update_property_string(
+				php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("param"), entry->param);
+	} else {
+		zend_update_property_null(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("param"));
+	}
+}
+
+/* Create array of StreamError objects from error chain */
+PHPAPI void php_stream_error_create_array(zval *zv, php_stream_error_entry *first)
+{
+	array_init(zv);
+
+	php_stream_error_entry *entry = first;
+	while (entry) {
+		zval error_obj;
+		php_stream_error_create_object(&error_obj, entry);
+		zend_hash_next_index_insert_new(Z_ARRVAL_P(zv), &error_obj);
+		entry = entry->next;
+	}
+}
+
+/* Context option helpers */
+
+static int php_stream_auto_decide_error_store_mode(int error_mode)
+{
+	switch (error_mode) {
+		case PHP_STREAM_ERROR_MODE_ERROR:
+			return PHP_STREAM_ERROR_STORE_NONE;
+		case PHP_STREAM_ERROR_MODE_EXCEPTION:
+			return PHP_STREAM_ERROR_STORE_NON_TERM;
+		case PHP_STREAM_ERROR_MODE_SILENT:
+			return PHP_STREAM_ERROR_STORE_ALL;
+		default:
+			return PHP_STREAM_ERROR_STORE_NONE;
+	}
+}
+
+static int php_stream_get_error_mode(php_stream_context *context)
+{
+	if (!context) {
+		return PHP_STREAM_ERROR_MODE_ERROR;
+	}
+
+	zval *option = php_stream_context_get_option(context, "stream", "error_mode");
+	if (!option) {
+		return PHP_STREAM_ERROR_MODE_ERROR;
+	}
+
+	if (Z_TYPE_P(option) != IS_OBJECT
+			|| !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_mode)) {
+		zend_type_error("stream context option 'error_mode' must be of type StreamErrorMode");
+		return PHP_STREAM_ERROR_MODE_ERROR;
+	}
+
+	switch ((zend_enum_StreamErrorMode) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
+		case ZEND_ENUM_StreamErrorMode_Error:
+			return PHP_STREAM_ERROR_MODE_ERROR;
+		case ZEND_ENUM_StreamErrorMode_Exception:
+			return PHP_STREAM_ERROR_MODE_EXCEPTION;
+		case ZEND_ENUM_StreamErrorMode_Silent:
+			return PHP_STREAM_ERROR_MODE_SILENT;
+	}
+
+	return PHP_STREAM_ERROR_MODE_ERROR;
+}
+
+static int php_stream_get_error_store_mode(php_stream_context *context, int error_mode)
+{
+	if (!context) {
+		return php_stream_auto_decide_error_store_mode(error_mode);
+	}
+
+	zval *option = php_stream_context_get_option(context, "stream", "error_store");
+	if (!option) {
+		return php_stream_auto_decide_error_store_mode(error_mode);
+	}
+
+	if (Z_TYPE_P(option) != IS_OBJECT
+			|| !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_store)) {
+		zend_type_error("stream context option 'error_store' must be of type StreamErrorStore");
+		return php_stream_auto_decide_error_store_mode(error_mode);
+	}
+
+	switch ((zend_enum_StreamErrorStore) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
+		case ZEND_ENUM_StreamErrorStore_Auto:
+			return php_stream_auto_decide_error_store_mode(error_mode);
+		case ZEND_ENUM_StreamErrorStore_None:
+			return PHP_STREAM_ERROR_STORE_NONE;
+		case ZEND_ENUM_StreamErrorStore_NonTerminating:
+			return PHP_STREAM_ERROR_STORE_NON_TERM;
+		case ZEND_ENUM_StreamErrorStore_Terminating:
+			return PHP_STREAM_ERROR_STORE_TERMINAL;
+		case ZEND_ENUM_StreamErrorStore_All:
+			return PHP_STREAM_ERROR_STORE_ALL;
+	}
+
+	return php_stream_auto_decide_error_store_mode(error_mode);
+}
+
+/* Helper functions */
+
+static bool php_stream_has_terminating_error(php_stream_error_operation *op)
+{
+	php_stream_error_entry *entry = op->first_error;
+	while (entry) {
+		if (entry->terminating) {
+			return true;
+		}
+		entry = entry->next;
+	}
+	return false;
+}
+
+static inline php_stream_error_operation *php_stream_get_operation_at_depth(uint32_t depth)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+
+	if (depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
+		return &state->operation_pool[depth];
+	} else {
+		uint32_t overflow_index = depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
+		ZEND_ASSERT(overflow_index < state->overflow_capacity);
+		return &state->overflow_operations[overflow_index];
+	}
+}
+
+static inline php_stream_error_operation *php_stream_get_parent_operation(void)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+
+	if (state->operation_depth <= 1) {
+		return NULL;
+	}
+
+	return php_stream_get_operation_at_depth(state->operation_depth - 2);
+}
+
+/* Clean up functions */
+
+static void php_stream_error_entry_free(php_stream_error_entry *entry)
+{
+	while (entry) {
+		php_stream_error_entry *next = entry->next;
+		zend_string_release(entry->message);
+		efree(entry->wrapper_name);
+		efree(entry->param);
+		efree(entry);
+		entry = next;
+	}
+}
+
+PHPAPI void php_stream_error_state_cleanup(void)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+
+	while (state->current_operation) {
+		php_stream_error_operation *op = state->current_operation;
+		state->operation_depth--;
+		state->current_operation = php_stream_get_parent_operation();
+
+		php_stream_error_entry_free(op->first_error);
+
+		op->first_error = NULL;
+		op->last_error = NULL;
+		op->error_count = 0;
+	}
+
+	php_stream_stored_error *stored = state->stored_errors;
+	while (stored) {
+		php_stream_stored_error *next = stored->next;
+		php_stream_error_entry_free(stored->first_error);
+		efree(stored);
+		stored = next;
+	}
+
+	state->stored_errors = NULL;
+	state->stored_count = 0;
+	state->operation_depth = 0;
+
+	if (state->overflow_operations) {
+		efree(state->overflow_operations);
+		state->overflow_operations = NULL;
+		state->overflow_capacity = 0;
+	}
+}
+
+PHPAPI void php_stream_error_get_last(zval *return_value)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+
+	if (!state->stored_errors) {
+		ZVAL_EMPTY_ARRAY(return_value);
+		return;
+	}
+
+	php_stream_error_create_array(return_value, state->stored_errors->first_error);
+}
+
+PHPAPI void php_stream_error_clear_stored(void)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+
+	php_stream_stored_error *stored = state->stored_errors;
+	while (stored) {
+		php_stream_stored_error *next = stored->next;
+		php_stream_error_entry_free(stored->first_error);
+		efree(stored);
+		stored = next;
+	}
+
+	state->stored_errors = NULL;
+	state->stored_count = 0;
+}
+
+/* Error operation stack management */
+
+PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+
+	if (state->operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) {
+		php_error_docref(NULL, E_WARNING,
+				"Stream error operation depth exceeded (%u), possible infinite recursion",
+				state->operation_depth);
+		return NULL;
+	}
+
+	php_stream_error_operation *op;
+
+	if (state->operation_depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
+		op = &state->operation_pool[state->operation_depth];
+	} else {
+		uint32_t overflow_index = state->operation_depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
+
+		if (overflow_index >= state->overflow_capacity) {
+			uint32_t new_capacity
+					= state->overflow_capacity == 0 ? 8 : state->overflow_capacity * 2;
+			php_stream_error_operation *new_overflow = erealloc(
+					state->overflow_operations, sizeof(php_stream_error_operation) * new_capacity);
+			state->overflow_operations = new_overflow;
+			state->overflow_capacity = new_capacity;
+		}
+
+		op = &state->overflow_operations[overflow_index];
+	}
+
+	op->first_error = NULL;
+	op->last_error = NULL;
+	op->error_count = 0;
+
+	state->current_operation = op;
+	state->operation_depth++;
+
+	return op;
+}
+
+static void php_stream_error_add(zend_enum_StreamErrorCode code, const char *wrapper_name,
+		zend_string *message, const char *docref, char *param, int severity, bool terminating)
+{
+	php_stream_error_operation *op = FG(stream_error_state).current_operation;
+	ZEND_ASSERT(op != NULL);
+
+	php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry));
+	entry->message = message;
+	entry->code = code;
+	entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
+	entry->param = param;
+	entry->docref = docref ? estrdup(docref) : NULL;
+	entry->severity = severity;
+	entry->terminating = terminating;
+	entry->next = NULL;
+
+	if (op->last_error) {
+		op->last_error->next = entry;
+	} else {
+		op->first_error = entry;
+	}
+	op->last_error = entry;
+	op->error_count++;
+}
+
+/* Error reporting */
+
+static void php_stream_call_error_handler(zval *handler, zval *errors_array)
+{
+	zend_fcall_info_cache fcc;
+	char *is_callable_error = NULL;
+
+	if (!zend_is_callable_ex(handler, NULL, 0, NULL, &fcc, &is_callable_error)) {
+		if (is_callable_error) {
+			zend_type_error("stream error handler must be a valid callback, %s", is_callable_error);
+			efree(is_callable_error);
+		}
+		return;
+	}
+
+	zval retval;
+
+	call_user_function(NULL, NULL, handler, &retval, 1, errors_array);
+
+	zval_ptr_dtor(&retval);
+}
+
+static void php_stream_throw_exception_with_errors(php_stream_error_operation *op)
+{
+	if (!op->first_error) {
+		return;
+	}
+
+	zval ex;
+	object_init_ex(&ex, php_ce_stream_exception);
+
+	/* Set message from first error */
+	zend_update_property_string(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("message"),
+			ZSTR_VAL(op->first_error->message));
+
+	/* Set code from first error */
+	zend_update_property_long(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("code"),
+			(zend_long) op->first_error->code);
+
+	/* Build errors array and set it */
+	zval errors_array;
+	php_stream_error_create_array(&errors_array, op->first_error);
+	zend_update_property(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("errors"), &errors_array);
+	zval_ptr_dtor(&errors_array);
+
+	zend_throw_exception_object(&ex);
+}
+
+static void php_stream_report_errors(php_stream_context *context, php_stream_error_operation *op,
+		int error_mode, bool is_terminating)
+{
+	switch (error_mode) {
+		case PHP_STREAM_ERROR_MODE_ERROR: {
+			php_stream_error_entry *entry = op->first_error;
+			while (entry) {
+				if (entry->param) {
+					php_error_docref1(entry->docref, entry->param, entry->severity, "%s",
+							ZSTR_VAL(entry->message));
+				} else {
+					php_error_docref(
+							entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message));
+				}
+				entry = entry->next;
+			}
+			break;
+		}
+
+		case PHP_STREAM_ERROR_MODE_EXCEPTION: {
+			if (is_terminating) {
+				php_stream_throw_exception_with_errors(op);
+			}
+			break;
+		}
+
+		case PHP_STREAM_ERROR_MODE_SILENT:
+			break;
+	}
+
+	/* Call user error handler if set */
+	zval *handler
+			= context ? php_stream_context_get_option(context, "stream", "error_handler") : NULL;
+
+	if (handler) {
+		zval errors_array;
+		php_stream_error_create_array(&errors_array, op->first_error);
+
+		php_stream_call_error_handler(handler, &errors_array);
+
+		zval_ptr_dtor(&errors_array);
+	}
+}
+
+/* Error storage */
+
+PHPAPI void php_stream_error_operation_end(php_stream_context *context)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+	php_stream_error_operation *op = state->current_operation;
+
+	if (!op) {
+		return;
+	}
+
+	state->operation_depth--;
+	state->current_operation = php_stream_get_parent_operation();
+
+	if (op->error_count > 0) {
+		if (context == NULL) {
+			context = FG(default_context);
+		}
+
+		int error_mode = php_stream_get_error_mode(context);
+		int store_mode = php_stream_get_error_store_mode(context, error_mode);
+
+		bool is_terminating = php_stream_has_terminating_error(op);
+
+		php_stream_report_errors(context, op, error_mode, is_terminating);
+
+		if (store_mode == PHP_STREAM_ERROR_STORE_NONE) {
+			php_stream_error_entry_free(op->first_error);
+			op->first_error = NULL;
+		} else {
+			php_stream_error_entry *entry = op->first_error;
+			php_stream_error_entry *prev = NULL;
+			php_stream_error_entry *to_store_first = NULL;
+			php_stream_error_entry *to_store_last = NULL;
+			uint32_t to_store_count = 0;
+			php_stream_error_entry *remaining_first = NULL;
+
+			while (entry) {
+				php_stream_error_entry *next = entry->next;
+				bool should_store = false;
+
+				if (store_mode == PHP_STREAM_ERROR_STORE_ALL) {
+					should_store = true;
+				} else if (store_mode == PHP_STREAM_ERROR_STORE_NON_TERM && !entry->terminating) {
+					should_store = true;
+				} else if (store_mode == PHP_STREAM_ERROR_STORE_TERMINAL && entry->terminating) {
+					should_store = true;
+				}
+
+				if (should_store) {
+					entry->next = NULL;
+					if (to_store_last) {
+						to_store_last->next = entry;
+					} else {
+						to_store_first = entry;
+					}
+					to_store_last = entry;
+					to_store_count++;
+				} else {
+					entry->next = NULL;
+					if (prev) {
+						prev->next = entry;
+					} else {
+						remaining_first = entry;
+					}
+					prev = entry;
+				}
+
+				entry = next;
+			}
+
+			if (to_store_first) {
+				php_stream_stored_error *stored = emalloc(sizeof(php_stream_stored_error));
+				stored->first_error = to_store_first;
+				stored->error_count = to_store_count;
+				stored->next = state->stored_errors;
+
+				state->stored_errors = stored;
+				state->stored_count++;
+			}
+
+			if (remaining_first) {
+				php_stream_error_entry_free(remaining_first);
+			}
+
+			op->first_error = NULL;
+		}
+	}
+
+	op->first_error = NULL;
+	op->last_error = NULL;
+	op->error_count = 0;
+}
+
+PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+	php_stream_error_operation *op = state->current_operation;
+
+	if (!op) {
+		return;
+	}
+
+	if (op->error_count == 0) {
+		state->operation_depth--;
+		state->current_operation = php_stream_get_parent_operation();
+
+		op->first_error = NULL;
+		op->last_error = NULL;
+		return;
+	}
+
+	php_stream_context *context = PHP_STREAM_CONTEXT(stream);
+	php_stream_error_operation_end(context);
+}
+
+PHPAPI void php_stream_error_operation_abort(void)
+{
+	php_stream_error_state *state = &FG(stream_error_state);
+	php_stream_error_operation *op = state->current_operation;
+
+	if (!op) {
+		return;
+	}
+
+	state->operation_depth--;
+	state->current_operation = php_stream_get_parent_operation();
+
+	php_stream_error_entry_free(op->first_error);
+	op->first_error = NULL;
+	op->last_error = NULL;
+	op->error_count = 0;
+}
+
+/* Wrapper error reporting */
+
+static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stream_context *context,
+		const char *docref, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, char *param, zend_string *message)
+{
+	bool implicit_operation = (FG(stream_error_state).current_operation == NULL);
+	if (implicit_operation) {
+		php_stream_error_operation_begin();
+	}
+
+	php_stream_error_add(code, wrapper_name, message, docref, param, severity, terminating);
+
+	if (implicit_operation) {
+		php_stream_error_operation_end(context);
+	}
+}
+
+PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name,
+		php_stream_context *context, const char *docref, int options, int severity,
+		bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
+{
+	if (!(options & REPORT_ERRORS)) {
+		return;
+	}
+
+	va_list args;
+	va_start(args, fmt);
+	zend_string *message = vstrpprintf(0, fmt, args);
+	va_end(args);
+
+	php_stream_wrapper_error_internal(
+			wrapper_name, context, docref, options, severity, terminating, code, NULL, message);
+}
+
+PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context,
+		const char *docref, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, const char *fmt, ...)
+{
+	if (!(options & REPORT_ERRORS)) {
+		return;
+	}
+
+	va_list args;
+	va_start(args, fmt);
+	zend_string *message = vstrpprintf(0, fmt, args);
+	va_end(args);
+
+	const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
+
+	php_stream_wrapper_error_internal(
+			wrapper_name, context, docref, options, severity, terminating, code, NULL, message);
+}
+
+PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context,
+		const char *docref, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, const char *param, const char *fmt, ...)
+{
+	if (!(options & REPORT_ERRORS)) {
+		return;
+	}
+
+	va_list args;
+	va_start(args, fmt);
+	zend_string *message = vstrpprintf(0, fmt, args);
+	va_end(args);
+
+	const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
+	char *param_copy = param ? estrdup(param) : NULL;
+
+	php_stream_wrapper_error_internal(wrapper_name, context, docref, options, severity, terminating,
+			code, param_copy, message);
+}
+
+PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper,
+		php_stream_context *context, const char *docref, int options, int severity,
+		bool terminating, zend_enum_StreamErrorCode code, const char *param1, const char *param2,
+		const char *fmt, ...)
+{
+	if (!(options & REPORT_ERRORS)) {
+		return;
+	}
+
+	char *combined_param;
+	spprintf(&combined_param, 0, "%s,%s", param1, param2);
+
+	va_list args;
+	va_start(args, fmt);
+	zend_string *message = vstrpprintf(0, fmt, args);
+	va_end(args);
+
+	const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
+
+	php_stream_wrapper_error_internal(wrapper_name, context, docref, options, severity, terminating,
+			code, combined_param, message);
+}
+
+/* Stream error reporting */
+
+PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity,
+		bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+
+	zend_string *message = vstrpprintf(0, fmt, args);
+	va_end(args);
+
+	const char *wrapper_name = stream->wrapper ? stream->wrapper->wops->label : "stream";
+
+	php_stream_context *context = PHP_STREAM_CONTEXT(stream);
+
+	php_stream_wrapper_error_internal(wrapper_name, context, docref, REPORT_ERRORS, severity,
+			terminating, code, NULL, message);
+}
+
+/* Legacy wrapper error logging */
+
+static void php_stream_error_entry_dtor_legacy(void *error)
+{
+	php_stream_error_entry *entry = *(php_stream_error_entry **) error;
+	zend_string_release(entry->message);
+	efree(entry->wrapper_name);
+	efree(entry->param);
+	efree(entry->docref);
+	efree(entry);
+}
+
+static void php_stream_error_list_dtor(zval *item)
+{
+	zend_llist *list = (zend_llist *) Z_PTR_P(item);
+	zend_llist_destroy(list);
+	efree(list);
+}
+
+static void php_stream_wrapper_log_store_error(zend_string *message, zend_enum_StreamErrorCode code,
+		const char *wrapper_name, const char *param, int severity, bool terminating)
+{
+	char *param_copy = param ? estrdup(param) : NULL;
+
+	php_stream_error_entry *entry = ecalloc(1, sizeof(php_stream_error_entry));
+	entry->message = message;
+	entry->code = code;
+	entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
+	entry->param = param_copy;
+	entry->severity = severity;
+	entry->terminating = terminating;
+
+	if (!FG(wrapper_logged_errors)) {
+		ALLOC_HASHTABLE(FG(wrapper_logged_errors));
+		zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0);
+	}
+
+	zend_llist *list
+			= zend_hash_str_find_ptr(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
+
+	if (!list) {
+		zend_llist new_list;
+		zend_llist_init(
+				&new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor_legacy, 0);
+		list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name,
+				strlen(wrapper_name), &new_list, sizeof(new_list));
+	}
+
+	zend_llist_add_element(list, &entry);
+}
+
+static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper,
+		php_stream_context *context, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, char *param, const char *fmt, va_list args)
+{
+	zend_string *message = vstrpprintf(0, fmt, args);
+	const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
+
+	if (options & REPORT_ERRORS) {
+		php_stream_wrapper_error_internal(
+				wrapper_name, context, NULL, options, severity, terminating, code, param, message);
+	} else {
+		php_stream_wrapper_log_store_error(
+				message, code, wrapper_name, param, severity, terminating);
+	}
+}
+
+PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper,
+		php_stream_context *context, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	php_stream_wrapper_log_error_internal(
+			wrapper, context, options, severity, terminating, code, NULL, fmt, args);
+	va_end(args);
+}
+
+PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper,
+		php_stream_context *context, int options, int severity, bool terminating,
+		zend_enum_StreamErrorCode code, const char *param, const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	char *param_copy = param ? estrdup(param) : NULL;
+	php_stream_wrapper_log_error_internal(
+			wrapper, context, options, severity, terminating, code, param_copy, fmt, args);
+	va_end(args);
+}
+
+static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name)
+{
+	if (!FG(wrapper_logged_errors)) {
+		return NULL;
+	}
+	return (zend_llist *) zend_hash_str_find_ptr(
+			FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
+}
+
+PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name,
+		php_stream_context *context, zend_enum_StreamErrorCode code, const char *path,
+		const char *caption)
+{
+	char *msg;
+	char errstr[256];
+	int free_msg = 0;
+
+	if (EG(exception)) {
+		return;
+	}
+
+	char *tmp = estrdup(path);
+	if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) {
+		zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name);
+		if (err_list) {
+			size_t l = 0;
+			int brlen;
+			int i;
+			int count = (int) zend_llist_count(err_list);
+			const char *br;
+			php_stream_error_entry **err_entry_p;
+			zend_llist_position pos;
+
+			if (PG(html_errors)) {
+				brlen = 7;
+				br = "<br />\n";
+			} else {
+				brlen = 1;
+				br = "\n";
+			}
+
+			for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p;
+					err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
+				l += ZSTR_LEN((*err_entry_p)->message);
+				if (i < count - 1) {
+					l += brlen;
+				}
+			}
+			msg = emalloc(l + 1);
+			msg[0] = '\0';
+			for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p;
+					err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
+				strcat(msg, ZSTR_VAL((*err_entry_p)->message));
+				if (i < count - 1) {
+					strcat(msg, br);
+				}
+			}
+
+			free_msg = 1;
+		} else {
+			if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) {
+				msg = php_socket_strerror_s(errno, errstr, sizeof(errstr));
+			} else {
+				msg = "operation failed";
+			}
+		}
+	} else {
+		msg = "no suitable wrapper could be found";
+	}
+
+	php_strip_url_passwd(tmp);
+
+	zend_string *message = strpprintf(0, "%s: %s", caption, msg);
+
+	php_stream_wrapper_error_internal(wrapper_name, context, NULL, REPORT_ERRORS, E_WARNING, true,
+			code, tmp, message);
+
+	if (free_msg) {
+		efree(msg);
+	}
+}
+
+PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper,
+		php_stream_context *context, zend_enum_StreamErrorCode code, const char *path,
+		const char *caption)
+{
+	if (wrapper) {
+		const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
+		php_stream_display_wrapper_name_errors(wrapper_name, context, code, path, caption);
+	}
+}
+
+PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name)
+{
+	if (FG(wrapper_logged_errors)) {
+		zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
+	}
+}
+
+PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper)
+{
+	if (wrapper) {
+		const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
+		php_stream_tidy_wrapper_name_error_log(wrapper_name);
+	}
+}
+
+/* StreamException methods */
+
+PHP_METHOD(StreamException, getErrors)
+{
+	ZEND_PARSE_PARAMETERS_NONE();
+
+	zval *errors = zend_read_property(
+			php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), 1, NULL);
+
+	RETURN_COPY(errors);
+}
+
+/* Module init */
+
+PHP_MINIT_FUNCTION(stream_errors)
+{
+	php_ce_stream_error_code = register_class_StreamErrorCode();
+	php_ce_stream_error_mode = register_class_StreamErrorMode();
+	php_ce_stream_error_store = register_class_StreamErrorStore();
+
+	php_ce_stream_error = register_class_StreamError();
+	php_ce_stream_exception = register_class_StreamException(zend_ce_exception);
+
+	return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(stream_errors)
+{
+	return SUCCESS;
+}
diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php
new file mode 100644
index 00000000000..2c56932c485
--- /dev/null
+++ b/main/streams/stream_errors.stub.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @generate-class-entries static
+ * @generate-c-enums
+ */
+
+/** @c-name-table */
+enum StreamErrorCode
+{
+    /* General errors */
+    case None;
+    case Generic;
+
+    /* I/O operation errors */
+    case ReadFailed;
+    case WriteFailed;
+    case SeekFailed;
+    case SeekNotSupported;
+    case FlushFailed;
+    case TruncateFailed;
+    case ConnectFailed;
+    case BindFailed;
+    case ListenFailed;
+    case NotWritable;
+    case NotReadable;
+
+    /* File system operations */
+    case Disabled;
+    case NotFound;
+    case PermissionDenied;
+    case AlreadyExists;
+    case InvalidPath;
+    case PathTooLong;
+    case OpenFailed;
+    case CreateFailed;
+    case DupFailed;
+    case UnlinkFailed;
+    case RenameFailed;
+    case MkdirFailed;
+    case RmdirFailed;
+    case StatFailed;
+    case MetaFailed;
+    case ChmodFailed;
+    case ChownFailed;
+    case CopyFailed;
+    case TouchFailed;
+    case InvalidMode;
+    case InvalidMeta;
+    case ModeNotSupported;
+    case Readonly;
+    case RecursionDetected;
+
+    /* Wrapper/protocol operations */
+    case NotImplemented;
+    case NoOpener;
+    case PersistentNotSupported;
+    case WrapperNotFound;
+    case WrapperDisabled;
+    case ProtocolUnsupported;
+    case WrapperRegistrationFailed;
+    case WrapperUnregistrationFailed;
+    case WrapperRestorationFailed;
+
+    /* Filter operations */
+    case FilterNotFound;
+    case FilterFailed;
+
+    /* Cast/conversion operations */
+    case CastFailed;
+    case CastNotSupported;
+    case MakeSeekableFailed;
+    case BufferedDataLost;
+
+    /* Network/socket operations */
+    case NetworkSendFailed;
+    case NetworkRecvFailed;
+    case SslNotSupported;
+    case ResumptionFailed;
+    case SocketPathTooLong;
+    case OobNotSupported;
+    case ProtocolError;
+    case InvalidUrl;
+    case InvalidResponse;
+    case InvalidHeader;
+    case InvalidParam;
+    case RedirectLimit;
+    case AuthFailed;
+
+    /* Encoding/decoding/archiving operations */
+    case ArchivingFailed;
+    case EncodingFailed;
+    case DecodingFailed;
+    case InvalidFormat;
+
+    /* Resource/allocation operations */
+    case AllocationFailed;
+    case TemporaryFileFailed;
+
+    /* Locking operations */
+    case LockFailed;
+    case LockNotSupported;
+
+    /* Userspace stream operations */
+    case UserspaceNotImplemented;
+    case UserspaceInvalidReturn;
+    case UserspaceCallFailed;
+}
+
+enum StreamErrorMode
+{
+    case Error;
+    case Exception;
+    case Silent;
+}
+
+enum StreamErrorStore
+{
+    case Auto;
+    case None;
+    case NonTerminating;
+    case Terminating;
+    case All;
+}
+
+final readonly class StreamError
+{
+    public StreamErrorCode $code;
+    public string $message;
+    public string $wrapperName;
+    public int $severity;
+    public bool $terminating;
+    public ?string $param;
+}
+
+class StreamException extends Exception
+{
+    /** @var array<int, StreamError> */
+    private array $errors = [];
+
+    /** @return array<int, StreamError> */
+    public function getErrors(): array {}
+}
diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h
new file mode 100644
index 00000000000..3a10e9bc5a4
Binary files /dev/null and b/main/streams/stream_errors_arginfo.h differ
diff --git a/main/streams/stream_errors_decl.h b/main/streams/stream_errors_decl.h
new file mode 100644
index 00000000000..4748768195f
Binary files /dev/null and b/main/streams/stream_errors_decl.h differ
diff --git a/main/streams/streams.c b/main/streams/streams.c
index e638c52159a..715bbcfe037 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -138,141 +138,6 @@ PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream *
 	return PHP_STREAM_PERSISTENT_NOT_EXIST;
 }

-/* }}} */
-
-static zend_llist *php_get_wrapper_errors_list(php_stream_wrapper *wrapper)
-{
-	if (!FG(wrapper_errors)) {
-		return NULL;
-	} else {
-		return (zend_llist*) zend_hash_str_find_ptr(FG(wrapper_errors), (const char*)&wrapper, sizeof(wrapper));
-	}
-}
-
-/* {{{ wrapper error reporting */
-static void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char *path, const char *caption)
-{
-	char *tmp;
-	char *msg;
-	char errstr[256];
-	int free_msg = 0;
-
-	if (EG(exception)) {
-		/* Don't emit additional warnings if an exception has already been thrown. */
-		return;
-	}
-
-	tmp = estrdup(path);
-	if (wrapper) {
-		zend_llist *err_list = php_get_wrapper_errors_list(wrapper);
-		if (err_list) {
-			size_t l = 0;
-			int brlen;
-			int i;
-			int count = (int)zend_llist_count(err_list);
-			const char *br;
-			const char **err_buf_p;
-			zend_llist_position pos;
-
-			if (PG(html_errors)) {
-				brlen = 7;
-				br = "<br />\n";
-			} else {
-				brlen = 1;
-				br = "\n";
-			}
-
-			for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0;
-					err_buf_p;
-					err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) {
-				l += strlen(*err_buf_p);
-				if (i < count - 1) {
-					l += brlen;
-				}
-			}
-			msg = emalloc(l + 1);
-			msg[0] = '\0';
-			for (err_buf_p = zend_llist_get_first_ex(err_list, &pos), i = 0;
-					err_buf_p;
-					err_buf_p = zend_llist_get_next_ex(err_list, &pos), i++) {
-				strcat(msg, *err_buf_p);
-				if (i < count - 1) {
-					strcat(msg, br);
-				}
-			}
-
-			free_msg = 1;
-		} else {
-			if (wrapper == &php_plain_files_wrapper) {
-				msg = php_socket_strerror_s(errno, errstr, sizeof(errstr));
-			} else {
-				msg = "operation failed";
-			}
-		}
-	} else {
-		msg = "no suitable wrapper could be found";
-	}
-
-	php_strip_url_passwd(tmp);
-	php_error_docref1(NULL, tmp, E_WARNING, "%s: %s", caption, msg);
-	efree(tmp);
-	if (free_msg) {
-		efree(msg);
-	}
-}
-
-static void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper)
-{
-	if (wrapper && FG(wrapper_errors)) {
-		zend_hash_str_del(FG(wrapper_errors), (const char*)&wrapper, sizeof(wrapper));
-	}
-}
-
-static void wrapper_error_dtor(void *error)
-{
-	efree(*(char**)error);
-}
-
-static void wrapper_list_dtor(zval *item) {
-	zend_llist *list = (zend_llist*)Z_PTR_P(item);
-	zend_llist_destroy(list);
-	efree(list);
-}
-
-PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, int options, const char *fmt, ...)
-{
-	va_list args;
-	char *buffer = NULL;
-
-	va_start(args, fmt);
-	vspprintf(&buffer, 0, fmt, args);
-	va_end(args);
-
-	if ((options & REPORT_ERRORS) || wrapper == NULL) {
-		php_error_docref(NULL, E_WARNING, "%s", buffer);
-		efree(buffer);
-	} else {
-		zend_llist *list = NULL;
-		if (!FG(wrapper_errors)) {
-			ALLOC_HASHTABLE(FG(wrapper_errors));
-			zend_hash_init(FG(wrapper_errors), 8, NULL, wrapper_list_dtor, 0);
-		} else {
-			list = zend_hash_str_find_ptr(FG(wrapper_errors), (const char*)&wrapper, sizeof(wrapper));
-		}
-
-		if (!list) {
-			zend_llist new_list;
-			zend_llist_init(&new_list, sizeof(buffer), wrapper_error_dtor, 0);
-			list = zend_hash_str_update_mem(FG(wrapper_errors), (const char*)&wrapper,
-					sizeof(wrapper), &new_list, sizeof(new_list));
-		}
-
-		/* append to linked list */
-		zend_llist_add_element(list, &buffer);
-	}
-}
-
-
 /* }}} */

 /* allocate a new stream for a particular ops */
@@ -511,6 +376,11 @@ fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remov
 			ZVAL_UNDEF(&stream->wrapperdata);
 		}

+		if (stream->error_list) {
+			zend_llist_destroy(stream->error_list);
+			pefree(stream->error_list, stream->is_persistent);
+		}
+
 		if (stream->readbuf) {
 			pefree(stream->readbuf, stream->is_persistent);
 			stream->readbuf = NULL;
@@ -1318,7 +1188,7 @@ PHPAPI ssize_t _php_stream_write(php_stream *stream, const char *buf, size_t cou

 	ZEND_ASSERT(buf != NULL);
 	if (stream->ops->write == NULL) {
-		php_error_docref(NULL, E_NOTICE, "Stream is not writable");
+		php_stream_notice(stream, NotWritable, "Stream is not writable");
 		return (ssize_t) -1;
 	}

@@ -1513,7 +1383,8 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
 		return 0;
 	}

-	php_error_docref(NULL, E_WARNING, "Stream does not support seeking");
+	php_stream_warn(stream, SeekNotSupported,
+			"Stream does not support seeking");

 	return -1;
 }
@@ -1937,11 +1808,13 @@ void php_shutdown_stream_hashes(void)
 		FG(stream_filters) = NULL;
 	}

-	if (FG(wrapper_errors)) {
-		zend_hash_destroy(FG(wrapper_errors));
-		efree(FG(wrapper_errors));
-		FG(wrapper_errors) = NULL;
+	if (FG(wrapper_logged_errors)) {
+		zend_hash_destroy(FG(wrapper_logged_errors));
+		efree(FG(wrapper_logged_errors));
+		FG(wrapper_logged_errors) = NULL;
 	}
+
+	php_stream_error_state_cleanup();
 }

 zend_result php_init_stream_wrappers(int module_number)
@@ -2108,7 +1981,9 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const
 			if (!localhost && path[n+3] != '\0' && path[n+3] != '/') {
 #endif
 				if (options & REPORT_ERRORS) {
-					php_error_docref(NULL, E_WARNING, "Remote host file access not supported, %s", path);
+					php_stream_wrapper_warn(plain_files_wrapper, NULL, options,
+							ProtocolUnsupported,
+							"Remote host file access not supported, %s", path);
 				}
 				return NULL;
 			}
@@ -2147,7 +2022,9 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const
 			}

 			if (options & REPORT_ERRORS) {
-				php_error_docref(NULL, E_WARNING, "file:// wrapper is disabled in the server configuration");
+				php_stream_wrapper_warn(plain_files_wrapper, NULL, options,
+					Disabled,
+					"file:// wrapper is disabled in the server configuration");
 			}
 			return NULL;
 		}
@@ -2245,10 +2122,12 @@ PHPAPI php_stream *_php_stream_opendir(const char *path, int options,
 			stream->flags |= PHP_STREAM_FLAG_NO_BUFFER | PHP_STREAM_FLAG_IS_DIR;
 		}
 	} else if (wrapper) {
-		php_stream_wrapper_log_error(wrapper, options & ~REPORT_ERRORS, "not implemented");
+		php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS,
+				NoOpener, "not implemented");
 	}
 	if (stream == NULL && (options & REPORT_ERRORS)) {
-		php_stream_display_wrapper_errors(wrapper, path, "Failed to open directory");
+		php_stream_display_wrapper_errors(wrapper, context, PHP_STREAM_EC(OpenFailed),
+				path, "Failed to open directory");
 	}
 	php_stream_tidy_wrapper_error_log(wrapper);

@@ -2315,16 +2194,25 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod

 	wrapper = php_stream_locate_url_wrapper(path, &path_to_open, options);
 	if ((options & STREAM_USE_URL) && (!wrapper || !wrapper->is_url)) {
-		php_error_docref(NULL, E_WARNING, "This function may only be used against URLs");
+		if (wrapper) {
+			php_stream_wrapper_warn(wrapper, context, options,
+					ProtocolUnsupported,
+					"This function may only be used against URLs");
+		} else {
+			php_error_docref(NULL, E_WARNING, "This function may only be used against URLs");
+		}
 		if (resolved_path) {
 			zend_string_release_ex(resolved_path, 0);
 		}
 		return NULL;
 	}

+	/* wrapper name needs to be stored as wrapper can be removed in opener (user stream) */
+	char *wrapper_name = pestrdup(PHP_STREAM_ERROR_WRAPPER_NAME(wrapper), persistent);
 	if (wrapper) {
 		if (!wrapper->wops->stream_opener) {
-			php_stream_wrapper_log_error(wrapper, options & ~REPORT_ERRORS,
+			php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS,
+					NoOpener,
 					"wrapper does not support stream open");
 		} else {
 			stream = wrapper->wops->stream_opener(wrapper,
@@ -2335,7 +2223,8 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 		/* if the caller asked for a persistent stream but the wrapper did not
 		 * return one, force an error here */
 		if (stream && persistent && !stream->is_persistent) {
-			php_stream_wrapper_log_error(wrapper, options & ~REPORT_ERRORS,
+			php_stream_wrapper_log_warn(wrapper, context, options & ~REPORT_ERRORS,
+					PersistentNotSupported,
 					"wrapper does not support persistent streams");
 			php_stream_close(stream);
 			stream = NULL;
@@ -2359,6 +2248,9 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 		stream->open_filename = __zend_orig_filename ? __zend_orig_filename : __zend_filename;
 		stream->open_lineno = __zend_orig_lineno ? __zend_orig_lineno : __zend_lineno;
 #endif
+		if (stream->ctx == NULL && context != NULL && !persistent) {
+			php_stream_context_set(stream, context);
+		}
 	}

 	if (stream != NULL && (options & STREAM_MUST_SEEK)) {
@@ -2371,6 +2263,7 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 				if (resolved_path) {
 					zend_string_release_ex(resolved_path, 0);
 				}
+				pefree(wrapper_name, persistent);
 				return stream;
 			case PHP_STREAM_RELEASED:
 				if (newstream->orig_path) {
@@ -2380,6 +2273,7 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 				if (resolved_path) {
 					zend_string_release_ex(resolved_path, 0);
 				}
+				pefree(wrapper_name, persistent);
 				return newstream;
 			default:
 				php_stream_close(stream);
@@ -2387,8 +2281,9 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 				if (options & REPORT_ERRORS) {
 					char *tmp = estrdup(path);
 					php_strip_url_passwd(tmp);
-					php_error_docref1(NULL, tmp, E_WARNING, "could not make seekable - %s",
-							tmp);
+					php_stream_wrapper_warn_param(wrapper, context, options,
+							SeekNotSupported, tmp,
+							"could not make seekable - %s", tmp);
 					efree(tmp);

 					options &= ~REPORT_ERRORS;
@@ -2406,13 +2301,15 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 	}

 	if (stream == NULL && (options & REPORT_ERRORS)) {
-		php_stream_display_wrapper_errors(wrapper, path, "Failed to open stream");
+		php_stream_display_wrapper_name_errors(wrapper_name, context, PHP_STREAM_EC(OpenFailed),
+				path, "Failed to open stream");
 		if (opened_path && *opened_path) {
 			zend_string_release_ex(*opened_path, 0);
 			*opened_path = NULL;
 		}
 	}
-	php_stream_tidy_wrapper_error_log(wrapper);
+	php_stream_tidy_wrapper_name_error_log(wrapper_name);
+	pefree(wrapper_name, persistent);
 	if (resolved_path) {
 		zend_string_release_ex(resolved_path, 0);
 	}
diff --git a/main/streams/transports.c b/main/streams/transports.c
index 014e435cfb0..3231670dd9a 100644
--- a/main/streams/transports.c
+++ b/main/streams/transports.c
@@ -37,13 +37,13 @@ PHPAPI int php_stream_xport_unregister(const char *protocol)
 	return zend_hash_str_del(&xport_hash, protocol, strlen(protocol));
 }

-#define ERR_REPORT(out_err, fmt, arg) \
+#define ERR_REPORT(code, out_err, fmt, arg) \
 	if (out_err) { *out_err = strpprintf(0, fmt, arg); } \
-	else { php_error_docref(NULL, E_WARNING, fmt, arg); }
+	else { php_stream_wrapper_warn(NULL, NULL, REPORT_ERRORS, code, fmt, arg); }

-#define ERR_RETURN(out_err, local_err, fmt) \
+#define ERR_RETURN(code, out_err, local_err, fmt) \
 	if (out_err) { *out_err = local_err; } \
-	else { php_error_docref(NULL, E_WARNING, fmt, local_err ? ZSTR_VAL(local_err) : "Unspecified error"); \
+	else { php_stream_wrapper_warn(NULL, NULL, REPORT_ERRORS, code, fmt, local_err ? ZSTR_VAL(local_err) : "Unspecified error"); \
 		if (local_err) { zend_string_release_ex(local_err, 0); local_err = NULL; } \
 	}

@@ -114,7 +114,8 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in
 				n = sizeof(wrapper_name) - 1;
 			PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);

-			ERR_REPORT(error_string, "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?",
+			ERR_REPORT(WrapperNotFound, error_string,
+					"Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?",
 					wrapper_name);

 			return NULL;
@@ -123,7 +124,8 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in

 	if (factory == NULL) {
 		/* should never happen */
-		php_error_docref(NULL, E_WARNING, "Could not find a factory !?");
+		php_stream_wrapper_warn(NULL, context, REPORT_ERRORS,
+			WrapperNotFound, "Could not find a factory !?");
 		return NULL;
 	}

@@ -144,7 +146,7 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in
 								flags & STREAM_XPORT_CONNECT_ASYNC ? 1 : 0,
 								timeout, &error_text, error_code)) {

-						ERR_RETURN(error_string, error_text, "connect() failed: %s");
+						ERR_RETURN(ConnectFailed, error_string, error_text, "connect() failed: %s");

 						failed = true;
 					}
@@ -154,7 +156,7 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in
 				/* server */
 				if (flags & STREAM_XPORT_BIND) {
 					if (0 != php_stream_xport_bind(stream, name, namelen, &error_text)) {
-						ERR_RETURN(error_string, error_text, "bind() failed: %s");
+						ERR_RETURN(BindFailed, error_string, error_text, "bind() failed: %s");
 						failed = true;
 					} else if (flags & STREAM_XPORT_LISTEN) {
 						zval *zbacklog = NULL;
@@ -165,7 +167,7 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in
 						}

 						if (0 != php_stream_xport_listen(stream, backlog, &error_text)) {
-							ERR_RETURN(error_string, error_text, "listen() failed: %s");
+							ERR_RETURN(ListenFailed, error_string, error_text, "listen() failed: %s");
 							failed = true;
 						}
 					}
@@ -368,7 +370,8 @@ PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_cr
 		return param.outputs.returncode;
 	}

-	php_error_docref("streams.crypto", E_WARNING, "This stream does not support SSL/crypto");
+	php_stream_warn_docref(stream, "streams.crypto", SslNotSupported,
+		"This stream does not support SSL/crypto");

 	return ret;
 }
@@ -388,7 +391,8 @@ PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate)
 		return param.outputs.returncode;
 	}

-	php_error_docref("streams.crypto", E_WARNING, "This stream does not support SSL/crypto");
+	php_stream_warn_docref(stream, "streams.crypto", SslNotSupported,
+		"This stream does not support SSL/crypto");

 	return ret;
 }
@@ -410,7 +414,8 @@ PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t bufle
 	}

 	if (stream->readfilters.head) {
-		php_error_docref(NULL, E_WARNING, "Cannot peek or fetch OOB data from a filtered stream");
+		php_stream_warn(stream, FilterFailed,
+			"Cannot peek or fetch OOB data from a filtered stream");
 		return -1;
 	}

@@ -480,7 +485,8 @@ PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t b
 	oob = (flags & STREAM_OOB) == STREAM_OOB;

 	if ((oob || addr) && stream->writefilters.head) {
-		php_error_docref(NULL, E_WARNING, "Cannot write OOB data, or data to a targeted address on a filtered stream");
+		php_stream_warn(stream, FilterFailed,
+			"Cannot write OOB data, or data to a targeted address on a filtered stream");
 		return -1;
 	}

diff --git a/main/streams/userspace.c b/main/streams/userspace.c
index 335ef3aa4f2..bf6ffa50096 100644
--- a/main/streams/userspace.c
+++ b/main/streams/userspace.c
@@ -291,7 +291,8 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *

 	/* Try to catch bad usage without preventing flexibility */
 	if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
-		php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented");
+		php_stream_wrapper_log_warn(wrapper, context, options,
+				RecursionDetected, "infinite recursion prevented");
 		return NULL;
 	}
 	FG(user_stream_current_filename) = filename;
@@ -332,8 +333,8 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
 	zval_ptr_dtor(&args[0]);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" is not implemented",
-			ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_wrapper_log_warn(wrapper, context, options,NotImplemented,
+				"\"%s::" USERSTREAM_OPEN "\" is not implemented", ZSTR_VAL(us->wrapper->ce->name));
 		zval_ptr_dtor(&args[3]);
 		goto end;
 	}
@@ -355,8 +356,9 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
 		/* set wrapper data to be a reference to our object */
 		ZVAL_COPY(&stream->wrapperdata, &us->object);
 	} else {
-		php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" call failed",
-			ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_wrapper_log_warn(wrapper, context, options,
+				UserspaceCallFailed,
+				"\"%s::" USERSTREAM_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name));
 	}

 	zval_ptr_dtor(&zretval);
@@ -392,7 +394,8 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char

 	/* Try to catch bad usage without preventing flexibility */
 	if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) {
-		php_stream_wrapper_log_error(wrapper, options, "infinite recursion prevented");
+		php_stream_wrapper_log_warn(wrapper, context, options,
+				RecursionDetected, "infinite recursion prevented");
 		return NULL;
 	}
 	FG(user_stream_current_filename) = filename;
@@ -417,8 +420,9 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
 	zval_ptr_dtor(&args[0]);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" is not implemented",
-			ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_wrapper_log_warn(wrapper, context, options, NotImplemented,
+				"\"%s::" USERSTREAM_DIR_OPEN "\" is not implemented",
+				ZSTR_VAL(us->wrapper->ce->name));
 		goto end;
 	}
 	/* Exception occurred in call */
@@ -433,8 +437,9 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
 		/* set wrapper data to be a reference to our object */
 		ZVAL_COPY(&stream->wrapperdata, &us->object);
 	} else {
-		php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed",
-			ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_wrapper_log_warn(wrapper, context, options,
+				UserspaceCallFailed,
+				"\"%s::" USERSTREAM_DIR_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name));
 	}
 	zval_ptr_dtor(&zretval);

@@ -477,10 +482,15 @@ PHP_FUNCTION(stream_wrapper_register)

 	/* We failed.  But why? */
 	if (zend_hash_exists(php_stream_get_url_stream_wrappers_hash(), protocol)) {
-		php_error_docref(NULL, E_WARNING, "Protocol %s:// is already defined.", ZSTR_VAL(protocol));
+		php_stream_wrapper_warn(&uwrap->wrapper, NULL, REPORT_ERRORS,
+				WrapperRegistrationFailed,
+				"Protocol %s:// is already defined.", ZSTR_VAL(protocol));
 	} else {
 		/* Hash doesn't exist so it must have been an invalid protocol scheme */
-		php_error_docref(NULL, E_WARNING, "Invalid protocol scheme specified. Unable to register wrapper class %s to %s://", ZSTR_VAL(uwrap->ce->name), ZSTR_VAL(protocol));
+		php_stream_wrapper_warn(&uwrap->wrapper, NULL, REPORT_ERRORS,
+				WrapperRegistrationFailed,
+				"Invalid protocol scheme specified. Unable to register wrapper class %s to %s://",
+				ZSTR_VAL(uwrap->ce->name), ZSTR_VAL(protocol));
 	}

 	zend_list_delete(rsrc);
@@ -500,7 +510,9 @@ PHP_FUNCTION(stream_wrapper_unregister)
 	php_stream_wrapper *wrapper = zend_hash_find_ptr(php_stream_get_url_stream_wrappers_hash(), protocol);
 	if (php_unregister_url_stream_wrapper_volatile(protocol) == FAILURE) {
 		/* We failed */
-		php_error_docref(NULL, E_WARNING, "Unable to unregister protocol %s://", ZSTR_VAL(protocol));
+		php_stream_wrapper_warn(wrapper, NULL, REPORT_ERRORS,
+				WrapperUnregistrationFailed,
+				"Unable to unregister protocol %s://", ZSTR_VAL(protocol));
 		RETURN_FALSE;
 	}

@@ -528,13 +540,17 @@ PHP_FUNCTION(stream_wrapper_restore)

 	global_wrapper_hash = php_stream_get_url_stream_wrappers_hash_global();
 	if ((wrapper = zend_hash_find_ptr(global_wrapper_hash, protocol)) == NULL) {
-		php_error_docref(NULL, E_WARNING, "%s:// never existed, nothing to restore", ZSTR_VAL(protocol));
+		php_stream_wrapper_warn_name(user_stream_wops.label, NULL, REPORT_ERRORS,
+				WrapperNotFound,
+				"%s:// never existed, nothing to restore", ZSTR_VAL(protocol));
 		RETURN_FALSE;
 	}

 	wrapper_hash = php_stream_get_url_stream_wrappers_hash();
 	if (wrapper_hash == global_wrapper_hash || zend_hash_find_ptr(wrapper_hash, protocol) == wrapper) {
-		php_error_docref(NULL, E_NOTICE, "%s:// was never changed, nothing to restore", ZSTR_VAL(protocol));
+		php_stream_wrapper_notice(wrapper, NULL, REPORT_ERRORS,
+				WrapperRestorationFailed,
+				"%s:// was never changed, nothing to restore", ZSTR_VAL(protocol));
 		RETURN_TRUE;
 	}

@@ -542,7 +558,9 @@ PHP_FUNCTION(stream_wrapper_restore)
 	php_unregister_url_stream_wrapper_volatile(protocol);

 	if (php_register_url_stream_wrapper_volatile(protocol, wrapper) == FAILURE) {
-		php_error_docref(NULL, E_WARNING, "Unable to restore original %s:// wrapper", ZSTR_VAL(protocol));
+		php_stream_wrapper_warn(wrapper, NULL, REPORT_ERRORS,
+			WrapperRestorationFailed,
+			"Unable to restore original %s:// wrapper", ZSTR_VAL(protocol));
 		RETURN_FALSE;
 	}

@@ -570,8 +588,8 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_
 	zval_ptr_dtor(&args[0]);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!",
-				ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_warn(stream, NotImplemented,
+				"%s::" USERSTREAM_WRITE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
 	}

 	stream->flags &= ~PHP_STREAM_FLAG_NO_FCLOSE;
@@ -591,7 +609,9 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_

 	/* don't allow strange buffer overruns due to bogus return */
 	if (didwrite > 0 && didwrite > count) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " wrote " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " written, " ZEND_LONG_FMT " max)",
+		php_stream_warn_nt(stream, UserspaceInvalidReturn,
+				"%s::" USERSTREAM_WRITE " wrote " ZEND_LONG_FMT " bytes more data than requested ("
+						ZEND_LONG_FMT " written, " ZEND_LONG_FMT " max)",
 				ZSTR_VAL(us->wrapper->ce->name),
 				(zend_long)(didwrite - count), (zend_long)didwrite, (zend_long)count);
 		didwrite = count;
@@ -622,8 +642,8 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
 	}

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!",
-				ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_warn(stream, NotImplemented,
+				"%s::" USERSTREAM_READ " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
 		goto err;
 	}

@@ -639,8 +659,12 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
 	didread = Z_STRLEN(retval);
 	if (didread > 0) {
 		if (didread > count) {
-			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " - read " ZEND_LONG_FMT " bytes more data than requested (" ZEND_LONG_FMT " read, " ZEND_LONG_FMT " max) - excess data will be lost",
-					ZSTR_VAL(us->wrapper->ce->name), (zend_long)(didread - count), (zend_long)didread, (zend_long)count);
+			php_stream_warn_nt(stream, UserspaceInvalidReturn,
+					"%s::" USERSTREAM_READ " - read " ZEND_LONG_FMT
+							" bytes more data than requested (" ZEND_LONG_FMT " read, "
+							ZEND_LONG_FMT " max) - excess data will be lost",
+					ZSTR_VAL(us->wrapper->ce->name), (zend_long)(didread - count),
+					(zend_long)didread, (zend_long)count);
 			didread = count;
 		}
 		memcpy(buf, Z_STRVAL(retval), didread);
@@ -656,7 +680,7 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
 	zend_string_release_ex(func_name, false);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, NotImplemented,
 				"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
 				ZSTR_VAL(us->wrapper->ce->name));
 		stream->eof = 1;
@@ -772,7 +796,8 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
 		*newoffs = Z_LVAL(retval);
 		ret = 0;
 	} else if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_warn(stream, NotImplemented,
+				"%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
 		ret = -1;
 	} else {
 		ret = -1;
@@ -836,8 +861,8 @@ static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
 	zend_string_release_ex(func_name, false);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!",
-				ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_warn(stream, NotImplemented,
+				"%s::" USERSTREAM_STAT " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
 		return -1;
 	}
 	if (UNEXPECTED(Z_ISUNDEF(retval))) {
@@ -855,7 +880,7 @@ static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
 	return ret;
 }

-static int user_stream_set_check_liveliness(const php_userstream_data_t *us)
+static int user_stream_set_check_liveliness(php_stream *stream, const php_userstream_data_t *us)
 {
 	zval retval;

@@ -864,7 +889,7 @@ static int user_stream_set_check_liveliness(const php_userstream_data_t *us)
 	zend_string_release_ex(func_name, false);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, NotImplemented,
 				"%s::" USERSTREAM_EOF " is not implemented! Assuming EOF",
 				ZSTR_VAL(us->wrapper->ce->name));
 		return PHP_STREAM_OPTION_RETURN_ERR;
@@ -875,15 +900,15 @@ static int user_stream_set_check_liveliness(const php_userstream_data_t *us)
 	if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
 		return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
 	} else {
-		php_error_docref(NULL, E_WARNING,
-			"%s::" USERSTREAM_EOF " value must be of type bool, %s given",
+		php_stream_warn(stream, UserspaceInvalidReturn,
+				"%s::" USERSTREAM_EOF " value must be of type bool, %s given",
 				ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval));
 		zval_ptr_dtor(&retval);
 		return PHP_STREAM_OPTION_RETURN_ERR;
 	}
 }

-static int user_stream_set_locking(const php_userstream_data_t *us, int value)
+static int user_stream_set_locking(php_stream *stream, const php_userstream_data_t *us, int value)
 {
 	zval retval;
 	zval zlock;
@@ -918,9 +943,8 @@ static int user_stream_set_locking(const php_userstream_data_t *us, int value)
 			/* lock support test (TODO: more check) */
 			return PHP_STREAM_OPTION_RETURN_OK;
 		}
-		php_error_docref(NULL, E_WARNING,
-				"%s::" USERSTREAM_LOCK " is not implemented!",
-				ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_warn(stream, NotImplemented,
+				"%s::" USERSTREAM_LOCK " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
 		return PHP_STREAM_OPTION_RETURN_ERR;
 	}
 	if (UNEXPECTED(Z_ISUNDEF(retval))) {
@@ -932,14 +956,15 @@ static int user_stream_set_locking(const php_userstream_data_t *us, int value)
 	}
 	// TODO: ext/standard/tests/file/userstreams_004.phpt returns null implicitly for function
 	// Should this warn or not? And should this be considered an error?
-	//php_error_docref(NULL, E_WARNING,
-	//	"%s::" USERSTREAM_LOCK " value must be of type bool, %s given",
+	//php_stream_warn(stream, UserspaceInvalidReturn,
+	//		"%s::" USERSTREAM_LOCK " value must be of type bool, %s given",
 	//		ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval));
 	zval_ptr_dtor(&retval);
 	return PHP_STREAM_OPTION_RETURN_NOTIMPL;
 }

-static int user_stream_set_truncation(const php_userstream_data_t *us, int value, void *ptrparam) {
+static int user_stream_set_truncation(php_stream *stream, const php_userstream_data_t *us,
+		int value, void *ptrparam) {
 	zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_TRUNCATE, false);

 	if (value == PHP_STREAM_TRUNCATE_SUPPORTED) {
@@ -967,9 +992,8 @@ static int user_stream_set_truncation(const php_userstream_data_t *us, int value
 	zend_string_release_ex(func_name, false);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING,
-				"%s::" USERSTREAM_TRUNCATE " is not implemented!",
-				ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_warn(stream, NotImplemented,
+				"%s::" USERSTREAM_TRUNCATE " is not implemented!", ZSTR_VAL(us->wrapper->ce->name));
 		return PHP_STREAM_OPTION_RETURN_ERR;
 	}
 	if (UNEXPECTED(Z_ISUNDEF(retval))) {
@@ -978,15 +1002,16 @@ static int user_stream_set_truncation(const php_userstream_data_t *us, int value
 	if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) {
 		return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
 	} else {
-		php_error_docref(NULL, E_WARNING,
-			"%s::" USERSTREAM_TRUNCATE " value must be of type bool, %s given",
+		php_stream_warn(stream, UserspaceInvalidReturn,
+				"%s::" USERSTREAM_TRUNCATE " value must be of type bool, %s given",
 				ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval));
 		zval_ptr_dtor(&retval);
 		return PHP_STREAM_OPTION_RETURN_ERR;
 	}
 }

-static int user_stream_set_option(const php_userstream_data_t *us, int option, int value, void *ptrparam)
+static int user_stream_set_option(php_stream *stream, const php_userstream_data_t *us, int option,
+		int value, void *ptrparam)
 {
 	zval args[3];
 	ZVAL_LONG(&args[0], option);
@@ -1011,7 +1036,7 @@ static int user_stream_set_option(const php_userstream_data_t *us, int option, i
 	zend_string_release_ex(func_name, false);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING,
+		php_stream_warn(stream, NotImplemented,
 				"%s::" USERSTREAM_SET_OPTION " is not implemented!",
 				ZSTR_VAL(us->wrapper->ce->name));
 		return PHP_STREAM_OPTION_RETURN_ERR;
@@ -1036,19 +1061,19 @@ static int php_userstreamop_set_option(php_stream *stream, int option, int value

 	switch (option) {
 		case PHP_STREAM_OPTION_CHECK_LIVENESS:
-			return user_stream_set_check_liveliness(us);
+			return user_stream_set_check_liveliness(stream, us);

 		case PHP_STREAM_OPTION_LOCKING:
-			return user_stream_set_locking(us, value);
+			return user_stream_set_locking(stream, us, value);

 		case PHP_STREAM_OPTION_TRUNCATE_API:
-			return user_stream_set_truncation(us, value, ptrparam);
+			return user_stream_set_truncation(stream, us, value, ptrparam);

 		case PHP_STREAM_OPTION_READ_BUFFER:
 		case PHP_STREAM_OPTION_WRITE_BUFFER:
 		case PHP_STREAM_OPTION_READ_TIMEOUT:
 		case PHP_STREAM_OPTION_BLOCKING:
-			return user_stream_set_option(us, option, value, ptrparam);
+			return user_stream_set_option(stream, us, option, value, ptrparam);

 		default:
 			return PHP_STREAM_OPTION_RETURN_NOTIMPL;
@@ -1081,7 +1106,8 @@ static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
 	zval_ptr_dtor(&object);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_UNLINK " is not implemented!", ZSTR_VAL(uwrap->ce->name));
+		php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
+				"%s::" USERSTREAM_UNLINK " is not implemented!", ZSTR_VAL(uwrap->ce->name));
 	} else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
 		ret = Z_TYPE(zretval) == IS_TRUE;
 	}
@@ -1119,7 +1145,8 @@ static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 	zval_ptr_dtor(&object);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RENAME " is not implemented!", ZSTR_VAL(uwrap->ce->name));
+		php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
+				"%s::" USERSTREAM_RENAME " is not implemented!", ZSTR_VAL(uwrap->ce->name));
 	} else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
 		ret = Z_TYPE(zretval) == IS_TRUE;
 	}
@@ -1157,7 +1184,8 @@ static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int
 	zval_ptr_dtor(&object);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_MKDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
+		php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
+				"%s::" USERSTREAM_MKDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
 	} else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
 		ret = Z_TYPE(zretval) == IS_TRUE;
 	}
@@ -1194,7 +1222,8 @@ static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url,
 	zval_ptr_dtor(&object);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RMDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
+		php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
+				"%s::" USERSTREAM_RMDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name));
 	} else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
 		ret = Z_TYPE(zretval) == IS_TRUE;
 	}
@@ -1233,7 +1262,9 @@ static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, i
 			ZVAL_STRING(&args[2], value);
 			break;
 		default:
-			php_error_docref(NULL, E_WARNING, "Unknown option %d for " USERSTREAM_METADATA, option);
+			php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS,
+					InvalidMeta,
+					"Unknown option %d for " USERSTREAM_METADATA, option);
 			return ret;
 	}

@@ -1256,7 +1287,8 @@ static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, i
 	zval_ptr_dtor(&object);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_METADATA " is not implemented!", ZSTR_VAL(uwrap->ce->name));
+		php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
+				"%s::" USERSTREAM_METADATA " is not implemented!", ZSTR_VAL(uwrap->ce->name));
 	} else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) {
 		ret = Z_TYPE(zretval) == IS_TRUE;
 	}
@@ -1294,8 +1326,8 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i
 	zval_ptr_dtor(&object);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!",
-			ZSTR_VAL(uwrap->ce->name));
+		php_stream_wrapper_warn(wrapper, context, REPORT_ERRORS, NotImplemented,
+			"%s::" USERSTREAM_STATURL " is not implemented!", ZSTR_VAL(uwrap->ce->name));
 		return -1;
 	}
 	if (UNEXPECTED(Z_ISUNDEF(zretval))) {
@@ -1330,8 +1362,9 @@ static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t co
 	zend_string_release_ex(func_name, false);

 	if (UNEXPECTED(call_result == FAILURE)) {
-		php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!",
-				ZSTR_VAL(us->wrapper->ce->name));
+		php_stream_warn(stream, NotImplemented,
+			"%s::" USERSTREAM_DIR_READ " is not implemented!",
+			ZSTR_VAL(us->wrapper->ce->name));
 		return -1;
 	}
 	if (UNEXPECTED(Z_ISUNDEF(retval))) {
@@ -1416,7 +1449,8 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)

 	if (UNEXPECTED(call_result == FAILURE)) {
 		if (report_errors) {
-			php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!",
+			php_stream_warn(stream, NotImplemented,
+					"%s::" USERSTREAM_CAST " is not implemented!",
 					ZSTR_VAL(us->wrapper->ce->name));
 		}
 		goto out;
@@ -1430,14 +1464,16 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
 		php_stream_from_zval_no_verify(intstream, &retval);
 		if (!intstream) {
 			if (report_errors) {
-				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must return a stream resource",
+				php_stream_warn(stream, UserspaceInvalidReturn,
+						"%s::" USERSTREAM_CAST " must return a stream resource",
 						ZSTR_VAL(us->wrapper->ce->name));
 			}
 			break;
 		}
 		if (intstream == stream) {
 			if (report_errors) {
-				php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " must not return itself",
+				php_stream_warn(stream, UserspaceInvalidReturn,
+						"%s::" USERSTREAM_CAST " must not return itself",
 						ZSTR_VAL(us->wrapper->ce->name));
 			}
 			intstream = NULL;
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index f2b2bb54ec6..d18b0e90195 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -114,9 +114,8 @@ static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t coun

 		if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) {
 			estr = php_socket_strerror(err, NULL, 0);
-			php_error_docref(NULL, E_NOTICE,
-				"Send of %zu bytes failed with errno=%d %s",
-				count, err, estr);
+			php_stream_warn(stream, NetworkSendFailed,
+					"Send of %zu bytes failed with errno=%d %s", count, err, estr);
 			efree(estr);
 		}
 	}
@@ -452,8 +451,7 @@ static int php_sockop_set_option(php_stream *stream, int option, int value, void
 							xparam->inputs.addrlen);
 					if (xparam->outputs.returncode == -1) {
 						char *err = php_socket_strerror(php_socket_errno(), NULL, 0);
-						php_error_docref(NULL, E_WARNING,
-						   	"%s\n", err);
+						php_stream_warn(stream, NetworkSendFailed, "%s", err);
 						efree(err);
 					}
 					return PHP_STREAM_OPTION_RETURN_OK;
@@ -593,7 +591,8 @@ static const php_stream_ops php_stream_unixdg_socket_ops = {
 /* network socket operations */

 #ifdef AF_UNIX
-static inline int parse_unix_address(php_stream_xport_param *xparam, struct sockaddr_un *unix_addr)
+static inline int parse_unix_address(php_stream *stream, php_stream_xport_param *xparam,
+		struct sockaddr_un *unix_addr)
 {
 	memset(unix_addr, 0, sizeof(*unix_addr));
 	unix_addr->sun_family = AF_UNIX;
@@ -612,9 +611,9 @@ static inline int parse_unix_address(php_stream_xport_param *xparam, struct sock
 		 * BUT, to get into this branch of code, the name is too long,
 		 * so we don't care. */
 		xparam->inputs.namelen = max_length;
-		php_error_docref(NULL, E_NOTICE,
-			"socket path exceeded the maximum allowed length of %lu bytes "
-			"and was truncated", max_length);
+		php_stream_notice(stream, InvalidPath,
+				"socket path exceeded the maximum allowed length of %lu bytes and was truncated",
+				max_length);
 	}

 	memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen);
@@ -695,7 +694,7 @@ static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *
 			return -1;
 		}

-		parse_unix_address(xparam, &unix_addr);
+		parse_unix_address(stream, xparam, &unix_addr);

 		int result = bind(sock->socket, (const struct sockaddr *)&unix_addr,
 			(socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen);
@@ -831,7 +830,7 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
 			return -1;
 		}

-		parse_unix_address(xparam, &unix_addr);
+		parse_unix_address(stream, xparam, &unix_addr);

 		ret = php_network_connect_socket(sock->socket,
 				(const struct sockaddr *)&unix_addr, (socklen_t) offsetof(struct sockaddr_un, sun_path) + xparam->inputs.namelen,
diff --git a/win32/build/config.w32 b/win32/build/config.w32
index aefcfb5f824..6cd6907f282 100644
--- a/win32/build/config.w32
+++ b/win32/build/config.w32
@@ -298,7 +298,7 @@ AC_DEFINE('HAVE_STRNLEN', 1);

 AC_DEFINE('ZEND_CHECK_STACK_LIMIT', 1)

-ADD_SOURCES("main/streams", "streams.c cast.c memory.c filter.c plain_wrapper.c \
+ADD_SOURCES("main/streams", "streams.c stream_errors.c cast.c memory.c filter.c plain_wrapper.c \
 	userspace.c transports.c xp_socket.c mmap.c glob_wrapper.c");
 ADD_FLAG("CFLAGS_BD_MAIN_STREAMS", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1");