Skip to content

Commit 4f5675c

Browse files
committed
Detect available memory for maxmemory in buffer
* Detect available memory (in bytes). Then config the buffer stream to stay in memory until reaching 1/4 of that. * Added setBufferMaxMemory to manually override that limit.
1 parent 9499697 commit 4f5675c

File tree

1 file changed

+62
-1
lines changed

1 file changed

+62
-1
lines changed

src/MultipartStreamBuilder.php

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ class MultipartStreamBuilder
3838
*/
3939
private $data = [];
4040

41+
/**
42+
* @var int Bytes of unallocated memory to use for stream buffer.
43+
* To have MultipartStreamBuilder manage it automatically, set to -1.
44+
* Default: -1
45+
*/
46+
private $bufferMaxMemory = -1;
47+
4148
/**
4249
* @param HttplugStreamFactory|StreamFactoryInterface|null $streamFactory
4350
*/
@@ -133,10 +140,18 @@ public function addResource($name, $resource, array $options = [])
133140
*/
134141
public function build()
135142
{
143+
// Assign maximimum 1/4 php's available memory
144+
// to attempt buffering the stream content.
145+
// If the stream content exceed this, will fallback
146+
// to use temporary file.
147+
$maxmemory = ($this->bufferMaxMemory < 0)
148+
? \floor(static::getAvailableMemory() / 4)
149+
: $this->bufferMaxMemory;
150+
136151
// Open a temporary read-write stream as buffer.
137152
// If the size is less than predefined limit, things will stay in memory.
138153
// If the size is more than that, things will be stored in temp file.
139-
$buffer = fopen('php://temp', 'r+');
154+
$buffer = fopen('php://temp/maxmemory:' . $maxmemory, 'r+');
140155
foreach ($this->data as $data) {
141156
// Add start and headers
142157
fwrite($buffer, "--{$this->getBoundary()}\r\n".
@@ -358,4 +373,50 @@ private function createStream($resource)
358373

359374
throw new \InvalidArgumentException(sprintf('First argument to "%s::createStream()" must be a string, resource or StreamInterface.', __CLASS__));
360375
}
376+
377+
/**
378+
* Setup the stream buffer size limit. PHP will allocate buffer
379+
* in memory if the size of the stream is smaller than this size.
380+
* Otherwise, PHP will store the stream data in a temporary file.
381+
*
382+
* @param integer $size
383+
* Size of stream data buffered (in bytes) until using temporary
384+
* file to buffer.
385+
*
386+
* @return MultipartStreamBuilder
387+
*/
388+
public function setBufferMaxMemory(int $size): MultipartStreamBuilder
389+
{
390+
$this->bufferMaxMemory = $size;
391+
392+
return $this;
393+
}
394+
395+
/**
396+
* Get and parse memory_limit into bytes integer.
397+
*
398+
* @return integer
399+
*
400+
* @throws \Exception
401+
* If the ini format does not match expectation.
402+
*/
403+
protected static function getAvailableMemory(): int
404+
{
405+
$memory_limit = ini_get('memory_limit');
406+
if ($memory_limit === '-1') {
407+
// If there is no memory limit, return 100MB by default.
408+
return 100 * 1024 * 1024;
409+
}
410+
if (!preg_match('/^(\d+)(G|M|K|)$/', $memory_limit, $matches)) {
411+
throw new \Exception("Unknown memory_limit format: {$memory_limit}");
412+
}
413+
if ($matches[2] == 'G') {
414+
$memory_limit = $matches[1] * 1024 * 1024 * 1024; // nnnG -> nnn GB
415+
} else if ($matches[2] == 'M') {
416+
$memory_limit = $matches[1] * 1024 * 1024; // nnnM -> nnn MB
417+
} else if ($matches[2] == 'K') {
418+
$memory_limit = $matches[1] * 1024; // nnnK -> nnn KB
419+
}
420+
return (int) $memory_limit - \memory_get_usage();
421+
}
361422
}

0 commit comments

Comments
 (0)