@@ -23,31 +23,107 @@ final class File
2323 */
2424 public static function open (string $ filename , string $ mode )
2525 {
26- $ handle = fopen ($ filename , $ mode );
27- if ($ handle === false ) {
26+ $ stream = fopen ($ filename , $ mode );
27+ if ($ stream === false ) {
2828 throw new FilesystemErrorException (
29- sprintf ('Error opening file : %s ' , $ filename ),
29+ sprintf ('Error opening stream : %s ' , $ filename ),
3030 );
3131 }
32-
33- return $ handle ;
32+ return $ stream ;
3433 }
3534
3635 /**
37- * Close an open file or URL
36+ * Close an open stream
3837 *
3938 * A wrapper around {@see fclose()} that throws an exception on failure.
4039 *
41- * @param resource $handle
40+ * @param resource $stream
41+ */
42+ public static function close ($ stream , ?string $ uri = null ): void
43+ {
44+ $ uri = self ::maybeGetStreamUri ($ stream , $ uri );
45+ $ result = fclose ($ stream );
46+ if ($ result === false ) {
47+ throw new FilesystemErrorException (
48+ sprintf ('Error closing file: %s ' , $ uri ),
49+ );
50+ }
51+ }
52+
53+ /**
54+ * Write to an open stream
55+ *
56+ * A wrapper around {@see fwrite()} that throws an exception on failure and
57+ * when fewer bytes are written than expected.
58+ *
59+ * @param resource $stream
60+ */
61+ public static function write ($ stream , string $ data , ?int $ length = null , ?string $ uri = null ): int
62+ {
63+ $ result = fwrite ($ stream , $ data , $ length );
64+ if ($ result === false ) {
65+ throw new FilesystemErrorException (
66+ sprintf (
67+ 'Error writing to stream: %s ' ,
68+ self ::maybeGetStreamUri ($ stream , $ uri ),
69+ ),
70+ );
71+ }
72+ $ length = $ length === null ? strlen ($ data ) : min ($ length , strlen ($ data ));
73+ if ($ result !== $ length ) {
74+ throw new FilesystemErrorException (
75+ sprintf (
76+ 'Error writing to stream: %d of %d %s written to %s ' ,
77+ $ result ,
78+ $ length ,
79+ Convert::plural ($ length , 'byte ' ),
80+ self ::maybeGetStreamUri ($ stream , $ uri ),
81+ ),
82+ );
83+ }
84+ return $ result ;
85+ }
86+
87+ /**
88+ * Set the file position indicator for a stream
89+ *
90+ * A wrapper around {@see fseek()} that throws an exception on failure.
91+ *
92+ * @param resource $stream
93+ * @param \SEEK_SET|\SEEK_CUR|\SEEK_END $whence
94+ */
95+ public static function seek ($ stream , int $ offset , int $ whence = SEEK_SET , ?string $ uri = null ): void
96+ {
97+ $ result = fseek ($ stream , $ offset , $ whence );
98+ if ($ result === -1 ) {
99+ throw new FilesystemErrorException (
100+ sprintf (
101+ 'Error setting file position indicator for stream: %s ' ,
102+ self ::maybeGetStreamUri ($ stream , $ uri ),
103+ ),
104+ );
105+ }
106+ }
107+
108+ /**
109+ * Get the file position indicator for a stream
110+ *
111+ * A wrapper around {@see ftell()} that throws an exception on failure.
112+ *
113+ * @param resource $stream
42114 */
43- public static function close ( $ handle , string $ filename ): void
115+ public static function tell ( $ stream , ? string $ uri = null ): int
44116 {
45- $ result = fclose ( $ handle );
117+ $ result = ftell ( $ stream );
46118 if ($ result === false ) {
47119 throw new FilesystemErrorException (
48- sprintf ('Error closing file: %s ' , $ filename ),
120+ sprintf (
121+ 'Error getting file position indicator for stream: %s ' ,
122+ self ::maybeGetStreamUri ($ stream , $ uri ),
123+ ),
49124 );
50125 }
126+ return $ result ;
51127 }
52128
53129 /**
@@ -331,21 +407,35 @@ public static function relativeToParent(
331407 }
332408
333409 /**
334- * Get the URI or filename associated with a stream
410+ * Get the URI associated with a stream
335411 *
336- * @param resource $stream A stream opened by `fopen()`, `fsockopen()`,
337- * `pfsockopen()` or `stream_socket_client()`.
338- * @return string|null `null` if `$stream` is not an open stream resource.
412+ * @param resource $stream
413+ * @return string|null `null` if `$stream` is closed or does not have a URI.
339414 */
340415 public static function getStreamUri ($ stream ): ?string
341416 {
342417 if (is_resource ($ stream ) && get_resource_type ($ stream ) === 'stream ' ) {
343- return stream_get_meta_data ($ stream )['uri ' ];
418+ // @phpstan-ignore-next-line
419+ return stream_get_meta_data ($ stream )['uri ' ] ?? null ;
344420 }
345-
346421 return null ;
347422 }
348423
424+ /**
425+ * @param resource $stream
426+ */
427+ private static function maybeGetStreamUri ($ stream , ?string $ uri ): string
428+ {
429+ if ($ uri !== null ) {
430+ return $ uri ;
431+ }
432+ $ uri = self ::getStreamUri ($ stream );
433+ if ($ uri === null ) {
434+ return '<no URI> ' ;
435+ }
436+ return $ uri ;
437+ }
438+
349439 /**
350440 * Write data to a CSV file or stream
351441 *
@@ -392,7 +482,7 @@ public static function writeCsv(
392482 $ handle = self ::open ($ target , 'wb ' );
393483 $ targetName = $ target ;
394484 } else {
395- $ target = 'php://memory ' ;
485+ $ target = 'php://temp ' ;
396486 $ handle = self ::open ($ target , 'w+b ' );
397487 $ targetName = $ target ;
398488 $ target = null ;
@@ -418,7 +508,7 @@ public static function writeCsv(
418508 }
419509
420510 if ($ bom ) {
421- fwrite ($ handle , ' ' );
511+ self :: write ($ handle , ' ' , null , $ targetName );
422512 }
423513
424514 $ count = 0 ;
@@ -436,15 +526,15 @@ public static function writeCsv(
436526 }
437527
438528 if (!$ count && $ headerRow ) {
439- self ::fputcsv ($ handle , array_keys ($ row ), ', ' , '" ' , $ eol );
529+ self ::fputcsv ($ handle , array_keys ($ row ), ', ' , '" ' , $ eol, $ targetName );
440530 }
441531
442532 self ::fputcsv ($ handle , $ row , ', ' , '" ' , $ eol );
443533 $ count ++;
444534 }
445535
446536 if ($ target === null ) {
447- rewind ($ handle );
537+ self :: seek ($ handle, 0 );
448538 $ csv = stream_get_contents ($ handle );
449539 self ::close ($ handle , $ targetName );
450540
@@ -468,7 +558,8 @@ private static function fputcsv(
468558 array $ fields ,
469559 string $ separator = ', ' ,
470560 string $ enclosure = '" ' ,
471- string $ eol = "\n"
561+ string $ eol = "\n" ,
562+ ?string $ uri = null
472563 ): int {
473564 $ special = $ separator . $ enclosure . "\n\r\t " ;
474565 foreach ($ fields as &$ field ) {
@@ -479,11 +570,12 @@ private static function fputcsv(
479570 }
480571 }
481572
482- $ written = fwrite ($ stream , implode ($ separator , $ fields ) . $ eol );
483- if ($ written === false ) {
484- throw new FilesystemErrorException ('Error writing to stream ' );
485- }
486- return $ written ;
573+ return self ::write (
574+ $ stream ,
575+ implode ($ separator , $ fields ) . $ eol ,
576+ null ,
577+ $ uri ,
578+ );
487579 }
488580
489581 /**
0 commit comments