├── .github ├── FUNDING.yml └── workflows │ ├── continuous-integration.yml │ └── release-on-milestone-closed.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build └── .gitignore ├── composer.json ├── composer.lock ├── examples └── simple-instantiator-map.php ├── infection.json.dist ├── phpbench.json ├── phpcs.xml.dist ├── phpunit.xml.dist ├── psalm.xml ├── renovate.json ├── src └── LazyMap │ ├── AbstractLazyMap.php │ └── CallbackLazyMap.php └── test ├── LazyMapPerformance └── LazyMapBench.php ├── LazyMapTest ├── AbstractLazyMapTest.php ├── CallbackLazyMapTest.php └── NullLazyMapTest.php └── LazyMapTestAsset ├── CallableClass.php ├── NullArrayBasedLazyMap.php └── NullLazyMap.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Ocramius] 2 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/laminas/laminas-continuous-integration-action 2 | # Generates a job matrix based on current dependencies and supported version 3 | # ranges, then runs all those jobs 4 | name: "Continuous Integration" 5 | 6 | on: 7 | pull_request: 8 | push: 9 | 10 | jobs: 11 | matrix: 12 | name: Generate job matrix 13 | runs-on: ubuntu-latest 14 | outputs: 15 | matrix: ${{ steps.matrix.outputs.matrix }} 16 | steps: 17 | - name: Gather CI configuration 18 | id: matrix 19 | uses: laminas/laminas-ci-matrix-action@1.22.1 20 | 21 | qa: 22 | name: QA Checks 23 | needs: [ matrix ] 24 | runs-on: ${{ matrix.operatingSystem }} 25 | strategy: 26 | fail-fast: false 27 | matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }} 28 | steps: 29 | - name: ${{ matrix.name }} 30 | uses: laminas/laminas-continuous-integration-action@1.32.0 31 | env: 32 | "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} 33 | "INFECTION_DASHBOARD_API_KEY": ${{ secrets.INFECTION_DASHBOARD_API_KEY }} 34 | "STRYKER_DASHBOARD_API_KEY": ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 35 | with: 36 | job: ${{ matrix.job }} 37 | 38 | demo-scripts: 39 | name: "Check Demo Scripts" 40 | 41 | runs-on: ${{ matrix.operating-system }} 42 | 43 | strategy: 44 | matrix: 45 | dependencies: 46 | - "locked" 47 | php-version: 48 | - "8.1" 49 | - "8.2" 50 | operating-system: 51 | - "ubuntu-latest" 52 | 53 | steps: 54 | - name: "Checkout" 55 | uses: "actions/checkout@v3" 56 | 57 | - name: "Install PHP" 58 | uses: "shivammathur/setup-php@2.24.0" 59 | with: 60 | coverage: "pcov" 61 | php-version: "${{ matrix.php-version }}" 62 | tools: composer:v2, cs2pr 63 | 64 | - name: "Cache dependencies" 65 | uses: "actions/cache@v3" 66 | with: 67 | path: | 68 | ~/.composer/cache 69 | vendor 70 | key: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" 71 | restore-keys: "php-${{ matrix.php-version }}-${{ matrix.dependencies }}" 72 | 73 | - name: "Install lowest dependencies" 74 | if: ${{ matrix.dependencies == 'lowest' }} 75 | run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest" 76 | 77 | - name: "Install highest dependencies" 78 | if: ${{ matrix.dependencies == 'highest' }} 79 | run: "composer update --no-interaction --no-progress --no-suggest" 80 | 81 | - name: "Install locked dependencies" 82 | if: ${{ matrix.dependencies == 'locked' }} 83 | run: "composer install --no-interaction --no-progress --no-suggest" 84 | 85 | - name: "Check Demo Scripts" 86 | run: | 87 | php examples/simple-instantiator-map.php 88 | -------------------------------------------------------------------------------- /.github/workflows/release-on-milestone-closed.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/categories/automating-your-workflow-with-github-actions 2 | 3 | name: "Automatic Releases" 4 | 5 | on: 6 | milestone: 7 | types: 8 | - "closed" 9 | 10 | jobs: 11 | release: 12 | name: "GIT tag, release & create merge-up PR" 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: "Checkout" 17 | uses: "actions/checkout@v3" 18 | 19 | - name: "Release" 20 | uses: "laminas/automatic-releases@v1" 21 | with: 22 | command-name: "laminas:automatic-releases:release" 23 | env: 24 | "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} 25 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 26 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 27 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 28 | 29 | - name: "Create Merge-Up Pull Request" 30 | uses: "laminas/automatic-releases@v1" 31 | with: 32 | command-name: "laminas:automatic-releases:create-merge-up-pull-request" 33 | env: 34 | "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} 35 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 36 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 37 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 38 | 39 | - name: "Create and/or Switch to new Release Branch" 40 | uses: "laminas/automatic-releases@v1" 41 | with: 42 | command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor" 43 | env: 44 | "GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }} 45 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 46 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 47 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 48 | 49 | - name: "Bump Changelog Version On Originating Release Branch" 50 | uses: "laminas/automatic-releases@v1" 51 | with: 52 | command-name: "laminas:automatic-releases:bump-changelog" 53 | env: 54 | "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} 55 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 56 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 57 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 58 | 59 | - name: "Create new milestones" 60 | uses: "laminas/automatic-releases@v1" 61 | with: 62 | command-name: "laminas:automatic-releases:create-milestones" 63 | env: 64 | "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} 65 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 66 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 67 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | infection.log 3 | phpunit.xml 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * Coding standard for the project is [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) 4 | * The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php) 5 | * Any contribution must provide tests for additional introduced conditions 6 | * Any un-confirmed issue needs a failing test case before being accepted 7 | * Pull requests must be sent from a new hotfix/feature branch, not from `master`. 8 | 9 | ## Installation 10 | 11 | To install the project and run the tests, you need to clone it first: 12 | 13 | ```sh 14 | $ git clone git://github.com/Ocramius/LazyMap.git 15 | ``` 16 | 17 | You will then need to run a composer installation: 18 | 19 | ```sh 20 | $ cd LazyMap 21 | $ curl -s https://getcomposer.org/installer | php 22 | $ php composer.phar update 23 | ``` 24 | 25 | ## Testing 26 | 27 | The PHPUnit version to be used is the one installed as a dev- dependency via composer: 28 | 29 | ```sh 30 | $ ./vendor/bin/phpunit 31 | ``` 32 | 33 | Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement 34 | won't be merged. 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Marco Pivetta 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lazy Map 2 | 3 | This small library aims at providing a very simple and efficient map of lazy-instantiating objects. 4 | 5 | [![Total Downloads](https://poser.pugx.org/ocramius/lazy-map/downloads.png)](https://packagist.org/packages/ocramius/lazy-map) 6 | [![Latest Stable Version](https://poser.pugx.org/ocramius/lazy-map/v/stable.png)](https://packagist.org/packages/ocramius/lazy-map) 7 | [![Latest Unstable Version](https://poser.pugx.org/ocramius/lazy-map/v/unstable.png)](https://packagist.org/packages/ocramius/lazy-map) 8 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FOcramius%2FLazyMap%2F2.5.x)](https://dashboard.stryker-mutator.io/reports/github.com/Ocramius/LazyMap/2.5.x) 9 | 10 | ## Abandoned 11 | 12 | Starting with PHP 8.3, dynamic properties are [no longer allowed "out of the box"](https://wiki.php.net/rfc/deprecate_dynamic_properties). 13 | While it is still possible to have dynamic properties via explicit declaration (the `#[\AllowDynamicProperties]` attribute), the approach 14 | of this package is no longer to be considered safe nor efficient long-term. 15 | 16 | Based on that, this package is deprecated and abandoned: please use traditional PHP `array`s instead. 17 | 18 | ## Installation 19 | 20 | The suggested installation method is via [composer](https://getcomposer.org/): 21 | 22 | ```sh 23 | composer require ocramius/lazy-map 24 | ``` 25 | 26 | ## Usage 27 | 28 | The current implementation is very simple and allows to define a map of "services" through a 29 | `LazyMap\CallbackLazyMap`: 30 | 31 | ```php 32 | $map = new \LazyMap\CallbackLazyMap(function ($name) { 33 | $object = new \stdClass(); 34 | 35 | $object->name = $name; 36 | 37 | return $object; 38 | }); 39 | 40 | var_dump($map->foo); 41 | var_dump($map->bar); 42 | var_dump($map->{'something special'}); 43 | ``` 44 | 45 | ## Purpose 46 | 47 | The idea behind the library is to avoid un-efficient lazy-loading operations like following: 48 | 49 | ```php 50 | private function getSomething($name) 51 | { 52 | if (isset($this->initialized[$name]) || array_key_exists($name, $this->initialized)) { 53 | return $this->initialized[$name]; 54 | } 55 | 56 | return $this->initialized[$name] = new Something($name); 57 | } 58 | ``` 59 | 60 | This reduces overhead greatly when you'd otherwise call `getSomething()` thousands of times. 61 | That's especially useful when mapping a lot of different services and iterating over them 62 | over and over again. 63 | 64 | ## Performance 65 | 66 | LazyMap actually performs much better than the "un-efficient" example that I've shown above. 67 | You can look directly at the performance test suite for details on the tested implementations, 68 | but here are some results for you to have an idea of the boost: 69 | 70 | #### Initialized Map Performance: 71 | 72 | |Method Name |Ops/s |Relative| 73 | |--------------------------------|---------------|--------| 74 | |initializedArrayPerformance |2,277,272.90002|100.00% | 75 | |initializedArrayMapPerformance |1,536,988.76108|148.16% | 76 | |initializedLazyMapPerformance |4,446,227.23514|51.22% | 77 | 78 | 79 | #### Un-Initialized Map Performance: 80 | 81 | |Method Name |Ops/s |Relative| 82 | |--------------------------------|---------------|--------| 83 | |unInitializedArrayPerformance : |1,091,720.80627|100.00% | 84 | |unInitializedArrayMapPerformance|688,132.30083 |158.65% | 85 | |unInitializedLazyMapPerformance:|912,191.90744 |119.68% | 86 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !logs 3 | !.gitignore 4 | !coverage-checker.php -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ocramius/lazy-map", 3 | "description": "A library that provides lazy instantiation logic for a map of objects", 4 | "type": "library", 5 | "license": "MIT", 6 | "homepage": "https://github.com/Ocramius/LazyMap", 7 | "keywords": [ 8 | "lazy", 9 | "lazy loading", 10 | "lazy instantiation", 11 | "map", 12 | "service location" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Marco Pivetta", 17 | "email": "ocramius@gmail.com", 18 | "homepage": "http://ocramius.github.io/" 19 | } 20 | ], 21 | "require": { 22 | "php": "~8.1.0 || ~8.2.0" 23 | }, 24 | "require-dev": { 25 | "doctrine/coding-standard": "^11.1.0", 26 | "roave/infection-static-analysis-plugin": "^1.29.0", 27 | "phpbench/phpbench": "^1.2.9", 28 | "phpunit/phpunit": "^9.6.5", 29 | "vimeo/psalm": "^5.7.7" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "LazyMap\\": "src/LazyMap" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "LazyMapPerformance\\": "test/LazyMapPerformance", 39 | "LazyMapTest\\": "test/LazyMapTest", 40 | "LazyMapTestAsset\\": "test/LazyMapTestAsset" 41 | } 42 | }, 43 | "config": { 44 | "allow-plugins": { 45 | "dealerdirect/phpcodesniffer-composer-installer": true, 46 | "infection/extension-installer": false 47 | }, 48 | "platform": { 49 | "php": "8.1.99" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/simple-instantiator-map.php: -------------------------------------------------------------------------------- 1 | name = $name; 13 | 14 | return $object; 15 | }); 16 | 17 | // the map instantiates objects lazily 18 | var_dump($map->foo); 19 | var_dump($map->bar); 20 | var_dump($map->baz); 21 | 22 | // same properties return the same object (shared instance) 23 | var_dump($map->foo === $map->foo); 24 | 25 | // different properties contain different instances 26 | var_dump($map->foo === $map->bar); 27 | var_dump($map->bar === $map->baz); 28 | var_dump($map->baz === $map->foo); 29 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src" 5 | ] 6 | }, 7 | "logs": { 8 | "text": "php://stderr", 9 | "github": true, 10 | "stryker": { 11 | "report": "/^\\d+\\.\\d+\\.x$/" 12 | } 13 | }, 14 | "minMsi": 100, 15 | "minCoveredMsi": 100 16 | } 17 | -------------------------------------------------------------------------------- /phpbench.json: -------------------------------------------------------------------------------- 1 | { 2 | "runner.bootstrap": "vendor/autoload.php", 3 | "runner.path": "test/LazyMapPerformance", 4 | "runner.retry_threshold": 5, 5 | "runner.warmup": 2, 6 | "report.outputs": { 7 | "report": { 8 | "extends": "aggregate" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | examples 17 | src 18 | test 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | ./test/LazyMapTest 11 | 12 | 13 | 14 | ./src 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>Ocramius/.github:renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/LazyMap/AbstractLazyMap.php: -------------------------------------------------------------------------------- 1 | $name = $this->instantiate($name); 24 | 25 | /** @psalm-suppress MixedReturnStatement */ 26 | return $this->$name; 27 | } 28 | 29 | /** 30 | * Instantiate a particular key by the given name 31 | * 32 | * @return mixed 33 | * @psalm-return T 34 | */ 35 | abstract protected function instantiate(string $name); 36 | } 37 | -------------------------------------------------------------------------------- /src/LazyMap/CallbackLazyMap.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[AllowDynamicProperties] 14 | final class CallbackLazyMap extends AbstractLazyMap 15 | { 16 | /** @psalm-param callable(string) : T $callback */ 17 | public function __construct(callable $callback) 18 | { 19 | $this->{self::class . "\0callback"} = $callback; 20 | } 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | protected function instantiate(string $name) 26 | { 27 | /** @psalm-var callable(string) : T $callback */ 28 | $callback = $this->{self::class . "\0callback"}; 29 | 30 | return $callback($name); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/LazyMapPerformance/LazyMapBench.php: -------------------------------------------------------------------------------- 1 | */ 17 | private array $array; 18 | 19 | private NullArrayBasedLazyMap $arrayMap; 20 | 21 | private NullLazyMap $lazyMap; 22 | 23 | public function setUp(): void 24 | { 25 | $this->array = ['existingKey' => 0]; 26 | $this->arrayMap = new NullArrayBasedLazyMap(); 27 | $this->lazyMap = new NullLazyMap(); 28 | 29 | // enforcing key initialization 30 | $this->arrayMap->get('existingKey'); 31 | $this->lazyMap->existingKey; 32 | } 33 | 34 | public function benchInitializedArrayPerformance(): int 35 | { 36 | if (array_key_exists('existingKey', $this->array)) { 37 | return $this->array['existingKey']; 38 | } 39 | 40 | return 0; 41 | } 42 | 43 | public function benchInitializedArrayMapPerformance(): int 44 | { 45 | return $this->arrayMap->get('existingKey'); 46 | } 47 | 48 | /** @return null */ 49 | public function benchInitializedLazyMapPerformance() 50 | { 51 | return $this->lazyMap->existingKey; 52 | } 53 | 54 | public function benchUnInitializedArrayPerformance(): int 55 | { 56 | if (array_key_exists('nonExistingKey', $this->array)) { 57 | return $this->array['nonExistingKey']; 58 | } 59 | 60 | return $this->array['nonExistingKey'] = 0; 61 | } 62 | 63 | public function benchUnInitializedArrayMapPerformance(): int 64 | { 65 | return $this->arrayMap->get('nonExistingKey'); 66 | } 67 | 68 | /** @return null */ 69 | public function benchUnInitializedLazyMapPerformance() 70 | { 71 | return $this->lazyMap->nonExistingKey; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/LazyMapTest/AbstractLazyMapTest.php: -------------------------------------------------------------------------------- 1 | lazyMap = $this->getMockForAbstractClass(AbstractLazyMap::class); 21 | } 22 | 23 | public function testDirectPropertyAccess(): void 24 | { 25 | /** @psalm-var AbstractLazyMap&MockObject $lazyMap */ 26 | $lazyMap = $this->lazyMap; 27 | 28 | $lazyMap 29 | ->expects($this->exactly(3)) 30 | ->method('instantiate') 31 | ->with($this->isType('string')) 32 | ->willReturnCallback(static function (string $key): string { 33 | return $key . ' - initialized value'; 34 | }); 35 | 36 | self::assertSame('foo - initialized value', $lazyMap->foo); 37 | self::assertSame('bar - initialized value', $lazyMap->bar); 38 | self::assertSame('baz\\tab - initialized value', $lazyMap->{'baz\\tab'}); 39 | } 40 | 41 | public function testMultipleDirectPropertyAccessDoesNotTriggerSameInstantiation(): void 42 | { 43 | /** @psalm-var AbstractLazyMap&MockObject $lazyMap */ 44 | $lazyMap = $this->lazyMap; 45 | 46 | $lazyMap 47 | ->expects($this->exactly(2)) 48 | ->method('instantiate') 49 | ->with($this->isType('string')) 50 | ->willReturnCallback(static function (): stdClass { 51 | return new stdClass(); 52 | }); 53 | 54 | $foo = $lazyMap->foo; 55 | 56 | self::assertSame($foo, $lazyMap->foo); 57 | 58 | $bar = $lazyMap->bar; 59 | 60 | self::assertSame($bar, $lazyMap->bar); 61 | 62 | self::assertNotSame($bar, $foo); 63 | } 64 | 65 | public function testUnSettingPropertiesRemovesSharedInstance(): void 66 | { 67 | /** @psalm-var AbstractLazyMap&MockObject $lazyMap */ 68 | $lazyMap = $this->lazyMap; 69 | 70 | $lazyMap 71 | ->expects($this->exactly(2)) 72 | ->method('instantiate') 73 | ->with($this->isType('string')) 74 | ->willReturnCallback(static function (): stdClass { 75 | return new stdClass(); 76 | }); 77 | 78 | $foo = $lazyMap->foo; 79 | 80 | self::assertSame($foo, $lazyMap->foo); 81 | 82 | unset($lazyMap->foo); 83 | 84 | $bar = $lazyMap->foo; 85 | 86 | self::assertSame($bar, $lazyMap->foo); 87 | self::assertNotSame($bar, $foo); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/LazyMapTest/CallbackLazyMapTest.php: -------------------------------------------------------------------------------- 1 | callback = $this->createMock(CallableClass::class); 23 | $this->lazyMap = new CallbackLazyMap(function (string $name): string { 24 | return $this->callback->__invoke($name); 25 | }); 26 | } 27 | 28 | public function testDirectPropertyAccess(): void 29 | { 30 | $count = 0; 31 | $this 32 | ->callback 33 | ->expects($this->exactly(3)) 34 | ->method('__invoke') 35 | ->willReturnCallback(static function (string $name) use (& $count): string { 36 | self::assertIsInt($count); 37 | 38 | $count += 1; 39 | 40 | return $name . ' - ' . $count; 41 | }); 42 | 43 | self::assertEquals('foo - 1', $this->lazyMap->foo); 44 | self::assertEquals('bar - 2', $this->lazyMap->bar); 45 | self::assertEquals('baz\\tab - 3', $this->lazyMap->{'baz\\tab'}); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/LazyMapTest/NullLazyMapTest.php: -------------------------------------------------------------------------------- 1 | lazyMap = new NullLazyMap(); 19 | } 20 | 21 | public function testDirectPropertyAccess(): void 22 | { 23 | self::assertSame(null, $this->lazyMap->foo); 24 | self::assertSame(null, $this->lazyMap->bar); 25 | self::assertSame(null, $this->lazyMap->{'baz\\tab'}); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/LazyMapTestAsset/CallableClass.php: -------------------------------------------------------------------------------- 1 | */ 15 | private array $items = []; 16 | 17 | /** Lazy getter - retrieves or instantiates a key in the map */ 18 | public function & get(string $name): int 19 | { 20 | if (isset($this->items[$name]) || array_key_exists($name, $this->items)) { 21 | return $this->items[$name]; 22 | } 23 | 24 | $this->items[$name] = $this->instantiate($name); 25 | 26 | return $this->items[$name]; 27 | } 28 | 29 | /** 30 | * Null instantiator, emulates same overhead of an {@see \LazyMapTestAsset\NullLazyMap} 31 | */ 32 | protected function instantiate(string $name): int 33 | { 34 | return 0; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/LazyMapTestAsset/NullLazyMap.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | #[AllowDynamicProperties] 16 | class NullLazyMap extends AbstractLazyMap 17 | { 18 | /** @return null */ 19 | protected function instantiate(string $name) 20 | { 21 | return null; 22 | } 23 | } 24 | --------------------------------------------------------------------------------