├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Container ├── AbstractContainer.php ├── AccessController.php ├── ContainerInterface.php ├── Locked.php └── Movable.php ├── Exception ├── ExceptionInterface.php ├── InvalidArgumentException.php └── RuntimeException.php ├── MemoryManager.php └── Value.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## 2.6.2 - TBD 6 | 7 | ### Added 8 | 9 | - Nothing. 10 | 11 | ### Changed 12 | 13 | - Nothing. 14 | 15 | ### Deprecated 16 | 17 | - Nothing. 18 | 19 | ### Removed 20 | 21 | - Nothing. 22 | 23 | ### Fixed 24 | 25 | - Nothing. 26 | 27 | ## 2.6.1 - 2019-10-16 28 | 29 | ### Added 30 | 31 | - [#19](https://github.com/zendframework/zend-memory/pull/19) adds support for PHP 7.3. 32 | 33 | ### Changed 34 | 35 | - Nothing. 36 | 37 | ### Deprecated 38 | 39 | - Nothing. 40 | 41 | ### Removed 42 | 43 | - Nothing. 44 | 45 | ### Fixed 46 | 47 | - Nothing. 48 | 49 | ## 2.6.0 - 2018-04-30 50 | 51 | ### Added 52 | 53 | - [#18](https://github.com/zendframework/zend-memory/pull/18) adds support for PHP 7.1 and 7.2. 54 | 55 | ### Changed 56 | 57 | - Nothing. 58 | 59 | ### Deprecated 60 | 61 | - Nothing. 62 | 63 | ### Removed 64 | 65 | - [#18](https://github.com/zendframework/zend-memory/pull/18) removes support for PHP 5.5. 66 | 67 | - [#18](https://github.com/zendframework/zend-memory/pull/18) removes support for HHVM. 68 | 69 | ### Fixed 70 | 71 | - [#13](https://github.com/zendframework/zend-memory/pull/13) fixes the `Zend\Memory\Container\Movable::markAsSwapped()` method to correctly set 72 | the SWAPPPED bit instead of the LOADED bit. 73 | 74 | ## 2.5.2 - 2016-05-11 75 | 76 | ### Added 77 | 78 | - [#11](https://github.com/zendframework/zend-memory/pull/11) and 79 | [#12](https://github.com/zendframework/zend-memory/pull/12) add and publish 80 | the documentation to https://zendframework.github.io/zend-memory/ 81 | 82 | ### Deprecated 83 | 84 | - Nothing. 85 | 86 | ### Removed 87 | 88 | - Nothing. 89 | 90 | ### Fixed 91 | 92 | - [#12](https://github.com/zendframework/zend-memory/pull/12) updates the 93 | PHP requirement to allow either 5.5+ or 7.0+, and pins the zend-cache version 94 | for testing to 2.7+. 95 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2019, Zend Technologies USA, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | - Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | - Neither the name of Zend Technologies USA, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zend-memory 2 | 3 | > ## Repository abandoned 2019-12-31 4 | > 5 | > This repository has moved to [laminas/laminas-memory](https://github.com/laminas/laminas-memory). 6 | 7 | [![Build Status](https://secure.travis-ci.org/zendframework/zend-memory.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-memory) 8 | [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-memory/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-memory?branch=master) 9 | 10 | zend-memory manages data in an environment with limited memory. 11 | 12 | Memory objects (memory containers) are generated by the memory manager, and 13 | transparently swapped/loaded when required. 14 | 15 | For example, if creating or loading a managed object would cause the total memory 16 | usage to exceed the limit you specify, some managed objects are copied to cache 17 | storage outside of memory. In this way, the total memory used by managed objects 18 | does not exceed the limit you need to enforce. 19 | 20 | ## Installation 21 | 22 | Run the following to install this library: 23 | 24 | ```bash 25 | $ composer require zendframework/zend-memory 26 | ``` 27 | 28 | ## Documentation 29 | 30 | Browse the documentation online at https://docs.zendframework.com/zend-memory/ 31 | 32 | ## Support 33 | 34 | * [Issues](https://github.com/zendframework/zend-memory/issues/) 35 | * [Chat](https://zendframework-slack.herokuapp.com/) 36 | * [Forum](https://discourse.zendframework.com/) 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zendframework/zend-memory", 3 | "description": "Manage data in an environment with limited memory", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "zf", 7 | "zendframework", 8 | "memory" 9 | ], 10 | "support": { 11 | "docs": "https://docs.zendframework.com/zend-memory/", 12 | "issues": "https://github.com/zendframework/zend-memory/issues", 13 | "source": "https://github.com/zendframework/zend-memory", 14 | "rss": "https://github.com/zendframework/zend-memory/releases.atom", 15 | "chat": "https://zendframework-slack.herokuapp.com", 16 | "forum": "https://discourse.zendframework.com/c/questions/components" 17 | }, 18 | "require": { 19 | "php": "^5.6 || ^7.0" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", 23 | "zendframework/zend-cache": "^2.7", 24 | "zendframework/zend-coding-standard": "~1.0.0" 25 | }, 26 | "suggest": { 27 | "zendframework/zend-cache": "To support swapping memory objects into and out of non-memory cache storage" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Zend\\Memory\\": "src/" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "ZendTest\\Memory\\": "test/" 37 | } 38 | }, 39 | "config": { 40 | "sort-packages": true 41 | }, 42 | "extra": { 43 | "branch-alias": { 44 | "dev-master": "2.6.x-dev", 45 | "dev-develop": "2.7.x-dev" 46 | } 47 | }, 48 | "scripts": { 49 | "check": [ 50 | "@cs-check", 51 | "@test" 52 | ], 53 | "cs-check": "phpcs", 54 | "cs-fix": "phpcbf", 55 | "test": "phpunit --colors=always", 56 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Container/AbstractContainer.php: -------------------------------------------------------------------------------- 1 | memContainer = $memContainer; 39 | } 40 | 41 | /** 42 | * Object destructor 43 | */ 44 | public function __destruct() 45 | { 46 | $this->memContainer->destroy(); 47 | } 48 | 49 | /** 50 | * Get string value reference 51 | * 52 | * _Must_ be used for value access before PHP v 5.2 53 | * or _may_ be used for performance considerations 54 | * 55 | * @return &string 56 | */ 57 | public function &getRef() 58 | { 59 | return $this->memContainer->getRef(); 60 | } 61 | 62 | /** 63 | * Signal, that value is updated by external code. 64 | * 65 | * Should be used together with getRef() 66 | */ 67 | public function touch() 68 | { 69 | $this->memContainer->touch(); 70 | } 71 | 72 | /** 73 | * Lock object in memory. 74 | */ 75 | public function lock() 76 | { 77 | $this->memContainer->lock(); 78 | } 79 | 80 | /** 81 | * Unlock object 82 | */ 83 | public function unlock() 84 | { 85 | $this->memContainer->unlock(); 86 | } 87 | 88 | /** 89 | * Return true if object is locked 90 | * 91 | * @return bool 92 | */ 93 | public function isLocked() 94 | { 95 | return $this->memContainer->isLocked(); 96 | } 97 | 98 | /** 99 | * Get handler 100 | * 101 | * Loads object if necessary and moves it to the top of loaded objects list. 102 | * Swaps objects from the bottom of loaded objects list, if necessary. 103 | * 104 | * @param string $property 105 | * @return string 106 | */ 107 | public function __get($property) 108 | { 109 | return $this->memContainer->$property; 110 | } 111 | 112 | /** 113 | * Set handler 114 | * 115 | * @param string $property 116 | * @param string $value 117 | */ 118 | public function __set($property, $value) 119 | { 120 | $this->memContainer->$property = $value; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Container/ContainerInterface.php: -------------------------------------------------------------------------------- 1 | value = $value; 32 | } 33 | 34 | /** 35 | * Lock object in memory. 36 | */ 37 | public function lock() 38 | { 39 | /* Do nothing */ 40 | } 41 | 42 | /** 43 | * Unlock object 44 | */ 45 | public function unlock() 46 | { 47 | /* Do nothing */ 48 | } 49 | 50 | /** 51 | * Return true if object is locked 52 | * 53 | * @return bool 54 | */ 55 | public function isLocked() 56 | { 57 | return true; 58 | } 59 | 60 | /** 61 | * Get string value reference 62 | * 63 | * _Must_ be used for value access before PHP v 5.2 64 | * or _may_ be used for performance considerations 65 | * 66 | * @return &string 67 | */ 68 | public function &getRef() 69 | { 70 | return $this->value; 71 | } 72 | 73 | /** 74 | * Signal, that value is updated by external code. 75 | * 76 | * Should be used together with getRef() 77 | */ 78 | public function touch() 79 | { 80 | /* Do nothing */ 81 | } 82 | 83 | /** 84 | * Destroy memory container and remove it from memory manager list 85 | */ 86 | public function destroy() 87 | { 88 | /* Do nothing */ 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Container/Movable.php: -------------------------------------------------------------------------------- 1 | memManager = $memoryManager; 63 | $this->id = $id; 64 | $this->state = self::LOADED; 65 | $this->value = new Memory\Value($value, $this); 66 | } 67 | 68 | /** 69 | * Lock object in memory. 70 | */ 71 | public function lock() 72 | { 73 | if (! ($this->state & self::LOADED)) { 74 | $this->memManager->load($this, $this->id); 75 | $this->state |= self::LOADED; 76 | } 77 | 78 | $this->state |= self::LOCKED; 79 | 80 | /** 81 | * @todo 82 | * It's possible to set "value" container attribute to avoid modification tracing, while it's locked 83 | * Check, if it's more effective 84 | */ 85 | } 86 | 87 | /** 88 | * Unlock object 89 | */ 90 | public function unlock() 91 | { 92 | // Clear LOCKED state bit 93 | $this->state &= ~self::LOCKED; 94 | } 95 | 96 | /** 97 | * Return true if object is locked 98 | * 99 | * @return bool 100 | */ 101 | public function isLocked() 102 | { 103 | return (bool) ($this->state & self::LOCKED); 104 | } 105 | 106 | /** 107 | * Get handler 108 | * 109 | * Loads object if necessary and moves it to the top of loaded objects list. 110 | * Swaps objects from the bottom of loaded objects list, if necessary. 111 | * 112 | * @param string $property 113 | * @return string 114 | * @throws Exception\InvalidArgumentException 115 | */ 116 | public function __get($property) 117 | { 118 | if ($property != 'value') { 119 | throw new Exception\InvalidArgumentException(sprintf( 120 | 'Unknown property: %s::$%s', 121 | __CLASS__, 122 | $property 123 | )); 124 | } 125 | 126 | if (! ($this->state & self::LOADED)) { 127 | $this->memManager->load($this, $this->id); 128 | $this->state |= self::LOADED; 129 | } 130 | 131 | return $this->value; 132 | } 133 | 134 | /** 135 | * Set handler 136 | * 137 | * @param string $property 138 | * @param string $value 139 | * @throws Exception\InvalidArgumentException 140 | */ 141 | public function __set($property, $value) 142 | { 143 | if ($property != 'value') { 144 | throw new Exception\InvalidArgumentException(sprintf( 145 | 'Unknown property: %s::$%s', 146 | __CLASS__, 147 | $property 148 | )); 149 | } 150 | 151 | $this->state = self::LOADED; 152 | $this->value = new Memory\Value($value, $this); 153 | 154 | $this->memManager->processUpdate($this, $this->id); 155 | } 156 | 157 | 158 | /** 159 | * Get string value reference 160 | * 161 | * _Must_ be used for value access before PHP v 5.2 162 | * or _may_ be used for performance considerations 163 | * 164 | * @return &string 165 | */ 166 | public function &getRef() 167 | { 168 | if (! ($this->state & self::LOADED)) { 169 | $this->memManager->load($this, $this->id); 170 | $this->state |= self::LOADED; 171 | } 172 | 173 | return $this->value->getRef(); 174 | } 175 | 176 | /** 177 | * Signal, that value is updated by external code. 178 | * 179 | * Should be used together with getRef() 180 | */ 181 | public function touch() 182 | { 183 | $this->memManager->processUpdate($this, $this->id); 184 | } 185 | 186 | /** 187 | * Process container value update. 188 | * Must be called only by value object 189 | * 190 | * @internal 191 | */ 192 | public function processUpdate() 193 | { 194 | // Clear SWAPPED state bit 195 | $this->state &= ~self::SWAPPED; 196 | 197 | $this->memManager->processUpdate($this, $this->id); 198 | } 199 | 200 | /** 201 | * Start modifications trace 202 | * 203 | * @internal 204 | */ 205 | public function startTrace() 206 | { 207 | if (! ($this->state & self::LOADED)) { 208 | $this->memManager->load($this, $this->id); 209 | $this->state |= self::LOADED; 210 | } 211 | 212 | $this->value->startTrace(); 213 | } 214 | 215 | /** 216 | * Set value (used by memory manager when value is loaded) 217 | * 218 | * @internal 219 | */ 220 | public function setValue($value) 221 | { 222 | $this->value = new Memory\Value($value, $this); 223 | } 224 | 225 | /** 226 | * Clear value (used by memory manager when value is swapped) 227 | * 228 | * @internal 229 | */ 230 | public function unloadValue() 231 | { 232 | // Clear LOADED state bit 233 | $this->state &= ~self::LOADED; 234 | 235 | $this->value = null; 236 | } 237 | 238 | /** 239 | * Mark, that object is swapped 240 | * 241 | * @internal 242 | */ 243 | public function markAsSwapped() 244 | { 245 | // Set SWAPPED state bit 246 | $this->state |= self::SWAPPED; 247 | } 248 | 249 | /** 250 | * Check if object is marked as swapped 251 | * 252 | * @internal 253 | * @return bool 254 | */ 255 | public function isSwapped() 256 | { 257 | return $this->state & self::SWAPPED; 258 | } 259 | 260 | /** 261 | * Get object id 262 | * 263 | * @internal 264 | * @return int 265 | */ 266 | public function getId() 267 | { 268 | return $this->id; 269 | } 270 | /** 271 | * Destroy memory container and remove it from memory manager list 272 | * 273 | * @internal 274 | */ 275 | public function destroy() 276 | { 277 | /** 278 | * We don't clean up swap because of performance considerations 279 | * Cleaning is performed by Memory Manager destructor 280 | */ 281 | 282 | $this->memManager->unlink($this, $this->id); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | => , 68 | * ... 69 | * ) 70 | * 71 | * @var array 72 | */ 73 | private $unloadCandidates = []; 74 | 75 | /** 76 | * List of object sizes. 77 | * 78 | * This list is used to calculate modification of object sizes 79 | * 80 | * array( => , ...) 81 | * 82 | * @var array 83 | */ 84 | private $sizes = []; 85 | 86 | /** 87 | * Last modified object 88 | * 89 | * It's used to reduce number of calls necessary to trace objects' modifications 90 | * Modification is not processed by memory manager until we do not switch to another 91 | * object. 92 | * So we have to trace only _first_ object modification and do nothing for others 93 | * 94 | * @var \Zend\Memory\Container\Movable 95 | */ 96 | private $lastModified = null; 97 | 98 | /** 99 | * Unique memory manager id 100 | * 101 | * @var int 102 | */ 103 | private $managerId; 104 | 105 | /** 106 | * This function is intended to generate unique id, used by memory manager 107 | */ 108 | private function generateMemManagerId() 109 | { 110 | /** 111 | * @todo !!! 112 | * uniqid() php function doesn't really guarantee the id to be unique 113 | * it should be changed by something else 114 | * (Ex. backend interface should be extended to provide this functionality) 115 | */ 116 | $this->managerId = str_replace('.', '_', uniqid('ZendMemManager', true)) . '_'; 117 | } 118 | 119 | /** 120 | * Memory manager constructor 121 | * 122 | * If cache is not specified, then memory objects are never swapped 123 | * 124 | * @param CacheStorage $cache 125 | */ 126 | public function __construct(CacheStorage $cache = null) 127 | { 128 | if ($cache === null) { 129 | return; 130 | } 131 | 132 | $this->cache = $cache; 133 | $this->generateMemManagerId(); 134 | 135 | $memoryLimitStr = trim(ini_get('memory_limit')); 136 | if ($memoryLimitStr != '' && $memoryLimitStr != -1) { 137 | $this->memoryLimit = (int) $memoryLimitStr; 138 | switch (strtolower($memoryLimitStr[strlen($memoryLimitStr) - 1])) { 139 | case 'g': 140 | $this->memoryLimit *= 1024; 141 | // no break 142 | case 'm': 143 | $this->memoryLimit *= 1024; 144 | // no break 145 | case 'k': 146 | $this->memoryLimit *= 1024; 147 | break; 148 | default: 149 | break; 150 | } 151 | 152 | $this->memoryLimit = (int) ($this->memoryLimit * 2 / 3); 153 | } // No limit otherwise 154 | } 155 | 156 | /** 157 | * Object destructor 158 | * 159 | * Clean up cache storage 160 | */ 161 | public function __destruct() 162 | { 163 | if ($this->cache !== null) { 164 | if ($this->cache instanceof ClearByNamespaceCacheStorage) { 165 | $this->cache->clearByNamespace($this->cache->getOptions()->getNamespace()); 166 | } elseif ($this->cache instanceof FlushableCacheStorage) { 167 | $this->cache->flush(); 168 | } 169 | } 170 | } 171 | 172 | /** 173 | * Set memory grow limit 174 | * 175 | * @param int $newLimit 176 | */ 177 | public function setMemoryLimit($newLimit) 178 | { 179 | $this->memoryLimit = $newLimit; 180 | 181 | $this->swapCheck(); 182 | } 183 | 184 | /** 185 | * Get memory grow limit 186 | * 187 | * @return int 188 | */ 189 | public function getMemoryLimit() 190 | { 191 | return $this->memoryLimit; 192 | } 193 | 194 | /** 195 | * Set minimum size of values, which may be swapped 196 | * 197 | * @param int $newSize 198 | */ 199 | public function setMinSize($newSize) 200 | { 201 | $this->minSize = $newSize; 202 | } 203 | 204 | /** 205 | * Get minimum size of values, which may be swapped 206 | * 207 | * @return int 208 | */ 209 | public function getMinSize() 210 | { 211 | return $this->minSize; 212 | } 213 | 214 | /** 215 | * Create new Zend\Memory value container 216 | * 217 | * @param string $value 218 | * @return Container\ContainerInterface 219 | * @throws Exception\ExceptionInterface 220 | */ 221 | public function create($value = '') 222 | { 223 | return $this->createContainer($value, false); 224 | } 225 | 226 | /** 227 | * Create new Zend\Memory value container, which has value always 228 | * locked in memory 229 | * 230 | * @param string $value 231 | * @return Container\ContainerInterface 232 | * @throws Exception\ExceptionInterface 233 | */ 234 | public function createLocked($value = '') 235 | { 236 | return $this->createContainer($value, true); 237 | } 238 | 239 | /** 240 | * Create new Zend\Memory container object 241 | * 242 | * @param string $value 243 | * @param bool $locked 244 | * @return Container\ContainerInterface 245 | * @throws Exception\ExceptionInterface 246 | */ 247 | private function createContainer($value, $locked) 248 | { 249 | $id = $this->nextId++; 250 | 251 | // Use only memory locked objects if backend is not specified 252 | if ($locked || ($this->cache === null)) { 253 | return new Container\Locked($value); 254 | } 255 | 256 | // Commit other objects modifications 257 | $this->commit(); 258 | 259 | $valueObject = new Container\Movable($this, $id, $value); 260 | 261 | // Store last object size as 0 262 | $this->sizes[$id] = 0; 263 | 264 | // Prepare object for next modifications 265 | $this->lastModified = $valueObject; 266 | 267 | return new Container\AccessController($valueObject); 268 | } 269 | 270 | /** 271 | * Unlink value container from memory manager 272 | * 273 | * Used by Memory container destroy() method 274 | * 275 | * @internal 276 | * @param Container\Movable $container 277 | * @param int $id 278 | * @return null 279 | */ 280 | public function unlink(Container\Movable $container, $id) 281 | { 282 | if ($this->lastModified === $container) { 283 | // Drop all object modifications 284 | $this->lastModified = null; 285 | unset($this->sizes[$id]); 286 | return; 287 | } 288 | 289 | if (isset($this->unloadCandidates[$id])) { 290 | unset($this->unloadCandidates[$id]); 291 | } 292 | 293 | $this->memorySize -= $this->sizes[$id]; 294 | unset($this->sizes[$id]); 295 | } 296 | 297 | /** 298 | * Process value update 299 | * 300 | * @internal 301 | * @param \Zend\Memory\Container\Movable $container 302 | * @param int $id 303 | */ 304 | public function processUpdate(Container\Movable $container, $id) 305 | { 306 | /** 307 | * This method is automatically invoked by memory container only once per 308 | * "modification session", but user may call memory container touch() method 309 | * several times depending on used algorithm. So we have to use this check 310 | * to optimize this case. 311 | */ 312 | if ($container === $this->lastModified) { 313 | return; 314 | } 315 | 316 | // Remove just updated object from list of candidates to unload 317 | if (isset($this->unloadCandidates[$id])) { 318 | unset($this->unloadCandidates[$id]); 319 | } 320 | 321 | // Reduce used memory mark 322 | $this->memorySize -= $this->sizes[$id]; 323 | 324 | // Commit changes of previously modified object if necessary 325 | $this->commit(); 326 | 327 | $this->lastModified = $container; 328 | } 329 | 330 | /** 331 | * Commit modified object and put it back to the loaded objects list 332 | */ 333 | private function commit() 334 | { 335 | if (($container = $this->lastModified) === null) { 336 | return; 337 | } 338 | 339 | $this->lastModified = null; 340 | 341 | $id = $container->getId(); 342 | 343 | // Calculate new object size and increase used memory size by this value 344 | $this->memorySize += ($this->sizes[$id] = strlen($container->getRef())); 345 | 346 | if ($this->sizes[$id] > $this->minSize) { 347 | // Move object to "unload candidates list" 348 | $this->unloadCandidates[$id] = $container; 349 | } 350 | 351 | $container->startTrace(); 352 | 353 | $this->swapCheck(); 354 | } 355 | 356 | /** 357 | * Check and swap objects if necessary 358 | * 359 | * @throws Exception\RuntimeException 360 | */ 361 | private function swapCheck() 362 | { 363 | if ($this->memoryLimit < 0 || $this->memorySize < $this->memoryLimit) { 364 | // Memory limit is not reached 365 | // Do nothing 366 | return; 367 | } 368 | 369 | // walk through loaded objects in access history order 370 | foreach ($this->unloadCandidates as $id => $container) { 371 | $this->swap($container, $id); 372 | unset($this->unloadCandidates[$id]); 373 | 374 | if ($this->memorySize < $this->memoryLimit) { 375 | // We've swapped enough objects 376 | return; 377 | } 378 | } 379 | 380 | throw new Exception\RuntimeException('Memory manager can\'t get enough space.'); 381 | } 382 | 383 | /** 384 | * Swap object data to disk 385 | * Actually swaps data or only unloads it from memory, 386 | * if object is not changed since last swap 387 | * 388 | * @param \Zend\Memory\Container\Movable $container 389 | * @param int $id 390 | */ 391 | private function swap(Container\Movable $container, $id) 392 | { 393 | if ($container->isLocked()) { 394 | return; 395 | } 396 | 397 | if (! $container->isSwapped()) { 398 | $this->cache->setItem($this->managerId . $id, $container->getRef()); 399 | } 400 | 401 | $this->memorySize -= $this->sizes[$id]; 402 | 403 | $container->markAsSwapped(); 404 | $container->unloadValue(); 405 | } 406 | 407 | /** 408 | * Load value from swap file. 409 | * 410 | * @internal 411 | * @param \Zend\Memory\Container\Movable $container 412 | * @param int $id 413 | */ 414 | public function load(Container\Movable $container, $id) 415 | { 416 | $value = $this->cache->getItem($this->managerId . $id); 417 | 418 | // Try to swap other objects if necessary 419 | // (do not include specified object into check) 420 | $this->memorySize += strlen($value); 421 | $this->swapCheck(); 422 | 423 | // Add loaded object to the end of loaded objects list 424 | $container->setValue($value); 425 | 426 | if ($this->sizes[$id] > $this->minSize) { 427 | // Add object to the end of "unload candidates list" 428 | $this->unloadCandidates[$id] = $container; 429 | } 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /src/Value.php: -------------------------------------------------------------------------------- 1 | container = $container; 52 | 53 | $this->value = (string) $value; 54 | 55 | /** 56 | * Object is marked as just modified by memory manager 57 | * So we don't need to trace followed object modifications and 58 | * object is processed (and marked as traced) when another 59 | * memory object is modified. 60 | * 61 | * It reduces overall number of calls necessary to modification trace 62 | */ 63 | $this->trace = false; 64 | } 65 | 66 | /** 67 | * Countable 68 | * 69 | * @return int 70 | */ 71 | public function count() 72 | { 73 | return strlen($this->value); 74 | } 75 | 76 | /** 77 | * ArrayAccess interface method 78 | * returns true if string offset exists 79 | * 80 | * @param int $offset 81 | * @return bool 82 | */ 83 | public function offsetExists($offset) 84 | { 85 | return $offset >= 0 && $offset < strlen($this->value); 86 | } 87 | 88 | /** 89 | * ArrayAccess interface method 90 | * Get character at $offset position 91 | * 92 | * @param int $offset 93 | * @return string 94 | */ 95 | public function offsetGet($offset) 96 | { 97 | return $this->value[$offset]; 98 | } 99 | 100 | /** 101 | * ArrayAccess interface method 102 | * Set character at $offset position 103 | * 104 | * @param int $offset 105 | * @param string $char 106 | */ 107 | public function offsetSet($offset, $char) 108 | { 109 | $this->value[$offset] = $char; 110 | 111 | if ($this->trace) { 112 | $this->trace = false; 113 | $this->container->processUpdate(); 114 | } 115 | } 116 | 117 | /** 118 | * ArrayAccess interface method 119 | * Unset character at $offset position 120 | * 121 | * @param int $offset 122 | */ 123 | public function offsetUnset($offset) 124 | { 125 | unset($this->value[$offset]); 126 | 127 | if ($this->trace) { 128 | $this->trace = false; 129 | $this->container->processUpdate(); 130 | } 131 | } 132 | 133 | /** 134 | * To string conversion 135 | * 136 | * @return string 137 | */ 138 | public function __toString() 139 | { 140 | return $this->value; 141 | } 142 | 143 | /** 144 | * Get string value reference 145 | * 146 | * _Must_ be used for value access before PHP v 5.2 147 | * or _may_ be used for performance considerations 148 | * 149 | * @internal 150 | * @return string 151 | */ 152 | public function &getRef() 153 | { 154 | return $this->value; 155 | } 156 | 157 | /** 158 | * Start modifications trace 159 | * 160 | * _Must_ be used for value access before PHP v 5.2 161 | * or _may_ be used for performance considerations 162 | * 163 | * @internal 164 | */ 165 | public function startTrace() 166 | { 167 | $this->trace = true; 168 | } 169 | } 170 | --------------------------------------------------------------------------------