├── .gitignore ├── phpstan.neon ├── src ├── Exceptions │ └── InvalidOperationException.php └── Eloquent │ └── TypeSafeCollection.php ├── tests ├── Data │ ├── User.php │ ├── Comment.php │ ├── UserCollection.php │ └── CommentCollection.php ├── Feature │ ├── DumpTest.php │ ├── PushTest.php │ ├── MapIntoTest.php │ ├── CollapseTest.php │ ├── ContainsTest.php │ ├── ContainsStrictTest.php │ ├── EachTest.php │ ├── KeysTest.php │ ├── FlipTest.php │ ├── CombineTest.php │ ├── ExceptTest.php │ ├── DiffKeysTest.php │ ├── FilterTest.php │ ├── MapTest.php │ ├── DiffTest.php │ ├── ModeTest.php │ ├── DiffAssocTest.php │ ├── MapToGroupsTest.php │ ├── MapWithKeysTest.php │ ├── CrossJoinTest.php │ ├── AvgTest.php │ ├── ZipTest.php │ ├── PartitionTest.php │ ├── AddTest.php │ ├── DuplicatesTest.php │ ├── PrependTest.php │ ├── DuplicatesStrictTest.php │ ├── MakeTest.php │ ├── WrapTest.php │ ├── CountByTest.php │ ├── MedianTest.php │ ├── OffsetSetTest.php │ ├── TimesTest.php │ ├── DiffKeysUsingTest.php │ ├── MapToDictionaryTest.php │ ├── ConstructorTest.php │ ├── DiffUsingTest.php │ ├── MergeTest.php │ ├── UnionTest.php │ ├── GroupByTest.php │ ├── ChunkTest.php │ └── DiffAssocUsingTest.php └── TestCase.php ├── .editorconfig ├── .php_cs ├── .scrutinizer.yml ├── phpunit.xml ├── LICENSE.md ├── composer.json ├── .travis.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .php_cs.cache 3 | .phpunit.result.cache 4 | composer.lock 5 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - '#Unsafe usage of new static\(\)#' 4 | checkMissingIterableValueType: false 5 | reportUnmatchedIgnoredErrors: false 6 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidOperationException.php: -------------------------------------------------------------------------------- 1 | dump(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Feature/PushTest.php: -------------------------------------------------------------------------------- 1 | push(new User()); 14 | 15 | $this->assertInstanceOf(UserCollection::class, $result); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Feature/MapIntoTest.php: -------------------------------------------------------------------------------- 1 | mapInto(Comment::class); 15 | 16 | $this->assertInstanceOf(Collection::class, $result); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Feature/CollapseTest.php: -------------------------------------------------------------------------------- 1 | new User(), 15 | ]); 16 | 17 | $this->assertInstanceOf(UserCollection::class, $collection->collapse()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Feature/ContainsTest.php: -------------------------------------------------------------------------------- 1 | contains('test'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Feature/ContainsStrictTest.php: -------------------------------------------------------------------------------- 1 | containsStrict('test'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Feature/EachTest.php: -------------------------------------------------------------------------------- 1 | each(function (User $user) { 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/KeysTest.php: -------------------------------------------------------------------------------- 1 | keys(); 18 | 19 | $this->assertInstanceOf(Collection::class, $result); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Feature/FlipTest.php: -------------------------------------------------------------------------------- 1 | flip(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Feature/CombineTest.php: -------------------------------------------------------------------------------- 1 | combine([]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Feature/ExceptTest.php: -------------------------------------------------------------------------------- 1 | new User(), 15 | ]); 16 | 17 | $result = $collection->except([ 18 | 'test', 19 | ]); 20 | 21 | $this->assertInstanceOf(Usercollection::class, $result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/DiffKeysTest.php: -------------------------------------------------------------------------------- 1 | new User(), 15 | ]); 16 | 17 | $result = $collection->diffKeys([ 18 | 'test', 19 | ]); 20 | 21 | $this->assertInstanceOf(UserCollection::class, $result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/FilterTest.php: -------------------------------------------------------------------------------- 1 | new User(), 15 | ]); 16 | 17 | $result = $collection->filter(function (User $user) { 18 | return true; 19 | }); 20 | 21 | $this->assertInstanceOf(Usercollection::class, $result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | setRules([ 5 | '@Symfony' => true, 6 | '@Symfony:risky' => true, 7 | 'array_syntax' => ['syntax' => 'short'], 8 | 'protected_to_private' => false, 9 | 'compact_nullable_typehint' => true, 10 | 'concat_space' => ['spacing' => 'one'], 11 | 'phpdoc_separation' => false, 12 | 'yoda_style' => null, 13 | ]) 14 | ->setRiskyAllowed(true) 15 | ->setFinder( 16 | PhpCsFixer\Finder::create() 17 | ->in([ 18 | __DIR__ . '/src', 19 | __DIR__ . '/tests', 20 | ]) 21 | ->append([__FILE__]) 22 | ); 23 | -------------------------------------------------------------------------------- /tests/Feature/MapTest.php: -------------------------------------------------------------------------------- 1 | map(function (User $user) { 18 | return $user->toArray(); 19 | }); 20 | 21 | $this->assertInstanceOf(Collection::class, $result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/DiffTest.php: -------------------------------------------------------------------------------- 1 | diff([ 19 | new Comment(), 20 | ]); 21 | 22 | $this->assertInstanceOf(UserCollection::class, $result); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Feature/ModeTest.php: -------------------------------------------------------------------------------- 1 | mode('test'); 23 | } catch (\Throwable $e) { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | useDatabasePath(__DIR__ . '/database'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Feature/DiffAssocTest.php: -------------------------------------------------------------------------------- 1 | new User(), 16 | ]); 17 | 18 | $result = $collection->diffAssoc([ 19 | 'test' => new Comment(), 20 | ]); 21 | 22 | $this->assertInstanceOf(UserCollection::class, $result); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Feature/MapToGroupsTest.php: -------------------------------------------------------------------------------- 1 | mapToGroups(function (User $user, $index) { 18 | return ['group-' . $index => $user]; 19 | }); 20 | 21 | $this->assertInstanceOf(Collection::class, $result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/MapWithKeysTest.php: -------------------------------------------------------------------------------- 1 | mapWithKeys(function (User $user, $index) { 18 | return ['key-' . $index => $user]; 19 | }); 20 | 21 | $this->assertInstanceOf(Collection::class, $result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/CrossJoinTest.php: -------------------------------------------------------------------------------- 1 | crossJoin([ 20 | new Comment(), 21 | ]); 22 | 23 | $this->assertInstanceOf(Collection::class, $result); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Feature/AvgTest.php: -------------------------------------------------------------------------------- 1 | avg(function () { 21 | return 1; 22 | }); 23 | 24 | $collection->avg('test'); 25 | 26 | // FIXME: This errors due to invalid types 27 | // $collection->avg(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Feature/ZipTest.php: -------------------------------------------------------------------------------- 1 | zip([ 18 | new User(), 19 | new User(), 20 | ]); 21 | 22 | $this->assertInstanceOf(Collection::class, $result); 23 | $this->assertInstanceOf(UserCollection::class, $result->first()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Feature/PartitionTest.php: -------------------------------------------------------------------------------- 1 | partition(function () { 18 | return true; 19 | }); 20 | 21 | $this->assertInstanceOf(Collection::class, $result); 22 | $this->assertInstanceOf(UserCollection::class, $result->first()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Feature/AddTest.php: -------------------------------------------------------------------------------- 1 | add(new User()); 15 | 16 | $this->assertInstanceOf(UserCollection::class, $result); 17 | } 18 | 19 | /** 20 | * @expectedException \InvalidArgumentException 21 | */ 22 | public function testIncorrect(): void 23 | { 24 | (new UserCollection())->add(new Comment()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Feature/DuplicatesTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 20 | 21 | return; 22 | } 23 | 24 | $result = $collection->duplicates(); 25 | 26 | $this->assertInstanceOf(UserCollection::class, $result); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Feature/PrependTest.php: -------------------------------------------------------------------------------- 1 | prepend(new User()); 15 | 16 | $this->assertInstanceOf(UserCollection::class, $result); 17 | } 18 | 19 | /** 20 | * @expectedException \InvalidArgumentException 21 | */ 22 | public function testIncorrect(): void 23 | { 24 | (new UserCollection())->prepend(new Comment()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Feature/DuplicatesStrictTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 20 | 21 | return; 22 | } 23 | 24 | $result = $collection->duplicatesStrict(); 25 | 26 | $this->assertInstanceOf(UserCollection::class, $result); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Feature/MakeTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(UserCollection::class, $result); 19 | } 20 | 21 | /** 22 | * @expectedException \InvalidArgumentException 23 | */ 24 | public function testIncorrect(): void 25 | { 26 | UserCollection::make([ 27 | new Comment(), 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Feature/WrapTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(UserCollection::class, $result); 19 | } 20 | 21 | /** 22 | * @expectedException \InvalidArgumentException 23 | */ 24 | public function testIncorrect(): void 25 | { 26 | UserCollection::wrap([ 27 | new Comment(), 28 | ]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Feature/CountByTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 20 | 21 | return; 22 | } 23 | 24 | $result = $collection->countBy(function () { 25 | return 'test'; 26 | }); 27 | 28 | $this->assertInstanceOf(Collection::class, $result); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Feature/MedianTest.php: -------------------------------------------------------------------------------- 1 | median('test'); 23 | 24 | // This breaks < 5.7 25 | /* @phpstan-ignore-next-line */ 26 | if (version_compare(Application::VERSION, '5.7', '>=')) { 27 | $collection->median(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Feature/OffsetSetTest.php: -------------------------------------------------------------------------------- 1 | offsetSet(0, new User()); 20 | } 21 | 22 | /** 23 | * @expectedException \InvalidArgumentException 24 | */ 25 | public function testIncorrect(): void 26 | { 27 | (new UserCollection([ 28 | new User(), 29 | ]))->offsetSet(0, new Comment()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Feature/TimesTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(UserCollection::class, $result); 19 | } 20 | 21 | /** 22 | * @expectedException \InvalidArgumentException 23 | */ 24 | public function testIncorrect(): void 25 | { 26 | UserCollection::times(2, function () { 27 | return new Comment(); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Feature/DiffKeysUsingTest.php: -------------------------------------------------------------------------------- 1 | new User(), 15 | ]); 16 | 17 | if (!method_exists($collection, 'diffKeysUsing')) { 18 | $this->assertTrue(true); 19 | 20 | return; 21 | } 22 | 23 | $result = $collection->diffKeysUsing([ 24 | 'test', 25 | ], function ($key1, $key2) { 26 | return true; 27 | }); 28 | 29 | $this->assertInstanceOf(UserCollection::class, $result); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Feature/MapToDictionaryTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 16 | 17 | return; 18 | } 19 | 20 | $result = (new UserCollection([ 21 | new User(), 22 | new User(), 23 | ]))->mapToDictionary(function (User $user, $index) { 24 | return ['index-' . $index => $user]; 25 | }); 26 | 27 | $this->assertInstanceOf(Collection::class, $result); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Feature/ConstructorTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 20 | 21 | return; 22 | } 23 | 24 | $result = $collection->diffUsing([ 25 | new Comment(), 26 | ], function ($original, $other) { 27 | return true; 28 | }); 29 | 30 | $this->assertInstanceOf(UserCollection::class, $result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Feature/MergeTest.php: -------------------------------------------------------------------------------- 1 | merge([ 17 | new User(), 18 | ]); 19 | 20 | $this->assertInstanceOf(UserCollection::class, $result); 21 | } 22 | 23 | /** 24 | * @expectedException \InvalidArgumentException 25 | */ 26 | public function testIncorrect(): void 27 | { 28 | $collection = new UserCollection([]); 29 | 30 | $collection->merge([ 31 | new Comment(), 32 | ]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Feature/UnionTest.php: -------------------------------------------------------------------------------- 1 | union([ 17 | new User(), 18 | ]); 19 | 20 | $this->assertInstanceOf(UserCollection::class, $result); 21 | } 22 | 23 | /** 24 | * @expectedException \InvalidArgumentException 25 | */ 26 | public function testIncorrect(): void 27 | { 28 | $collection = new UserCollection([]); 29 | 30 | $collection->union([ 31 | new Comment(), 32 | ]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Feature/GroupByTest.php: -------------------------------------------------------------------------------- 1 | groupBy(function () { 18 | return 'test'; 19 | }); 20 | 21 | $this->assertInstanceOf(Collection::class, $result); 22 | } 23 | 24 | public function testCorrectWithKey(): void 25 | { 26 | $result = (new UserCollection([ 27 | new User(), 28 | new User(), 29 | ]))->groupBy('test'); 30 | 31 | $this->assertInstanceOf(Collection::class, $result); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Feature/ChunkTest.php: -------------------------------------------------------------------------------- 1 | chunk(1); 18 | 19 | $this->assertInstanceOf(Collection::class, $result); 20 | $this->assertInstanceOf(UserCollection::class, $result->first()); 21 | } 22 | 23 | public function testCorrectEmpty(): void 24 | { 25 | $result = (new UserCollection([ 26 | new User(), 27 | new User(), 28 | ]))->chunk(0); 29 | 30 | $this->assertInstanceOf(Collection::class, $result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Feature/DiffAssocUsingTest.php: -------------------------------------------------------------------------------- 1 | new User(), 16 | ]); 17 | 18 | if (!method_exists($collection, 'diffAssocUsing')) { 19 | $this->assertTrue(true); 20 | 21 | return; 22 | } 23 | 24 | $result = $collection->diffAssocUsing([ 25 | 'test' => new Comment(), 26 | ], function ($original, $other) { 27 | return true; 28 | }); 29 | 30 | $this->assertInstanceOf(UserCollection::class, $result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | 4 | checks: 5 | php: 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | fix_doc_comments: true 19 | code_rating: true 20 | duplication: true 21 | 22 | build: 23 | nodes: 24 | php71: 25 | environment: 26 | php: 27 | version: 7.1.12 28 | services: 29 | mysql: 5.7 30 | tests: 31 | override: 32 | - php-scrutinizer-run 33 | - 34 | command: mysql -e 'CREATE DATABASE db_rebuild;' && vendor/bin/phpunit --coverage-clover=coverage71 35 | coverage: 36 | file: coverage71 37 | format: php-clover 38 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Webparking BV 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webparking/laravel-type-safe-collection", 3 | "description": "This package provides type-safe extension of the laravel collection, forcing a single type of object.", 4 | "license": "MIT", 5 | "keywords": [ 6 | "laravel", 7 | "php", 8 | "database", 9 | "type", 10 | "safe", 11 | "typesafe", 12 | "collection", 13 | "collections" 14 | ], 15 | "authors": [ 16 | { 17 | "name": "Remko Brenters", 18 | "email": "remko.brenters@webparking.nl" 19 | }, 20 | { 21 | "name": "Jeroen van Oort", 22 | "email": "jeroen.vanoort@webparking.nl" 23 | }, 24 | { 25 | "name": "Peter Klooster", 26 | "email": "peter.klooster@webparking.nl" 27 | } 28 | ], 29 | "require": { 30 | "php": ">=7.1.0", 31 | "ext-json": "*", 32 | "illuminate/support": "^5.5|^6.0|^7.0|^8.0", 33 | "illuminate/console": "^5.5|^6.0|^7.0|^8.0", 34 | "illuminate/database": "^5.5|^6.0|^7.0|^8.0", 35 | "webmozart/assert": "^1.0" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Webparking\\TypeSafeCollection\\": "src/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "Webparking\\TypeSafeCollection\\Tests\\": "tests/" 45 | } 46 | }, 47 | "scripts": { 48 | "phpstan": "@php vendor/bin/phpstan analyse src tests/Feature -l max --no-progress --ansi -c phpstan.neon", 49 | "php-cs-fixer": "vendor/bin/php-cs-fixer fix . --config=.php_cs", 50 | "phpunit": "vendor/bin/phpunit", 51 | "test": "composer php-cs-fixer && composer phpstan && composer phpunit" 52 | }, 53 | "require-dev": { 54 | "friendsofphp/php-cs-fixer": "^2.12", 55 | "phpunit/phpunit": "^6.0|^7.0|^8.0", 56 | "orchestra/testbench": "^3.5|^4.0|^5.0|dev-6.x", 57 | "orchestra/database": "^3.5|^4.0|^5.0|dev-6.x", 58 | "phpstan/phpstan": "^0.12" 59 | }, 60 | "minimum-stability": "dev", 61 | "prefer-stable": true 62 | } 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | cache: 4 | directories: 5 | - $HOME/.composer/cache 6 | 7 | services: 8 | - mysql 9 | 10 | matrix: 11 | fast_finish: true 12 | include: 13 | - php: 7.1 14 | env: LARAVEL='5.5.*' TESTBENCH='3.5.*' 15 | - php: 7.1 16 | env: LARAVEL='5.5.*' TESTBENCH='3.5.*' 17 | - php: 7.1 18 | env: LARAVEL='5.6.*' TESTBENCH='3.6.*' 19 | - php: 7.1 20 | env: LARAVEL='5.7.*' TESTBENCH='3.7.*' 21 | - php: 7.1 22 | env: LARAVEL='5.8.*' TESTBENCH='3.8.*' 23 | - php: 7.2 24 | env: LARAVEL='5.5.*' TESTBENCH='3.5.*' 25 | - php: 7.2 26 | env: LARAVEL='5.6.*' TESTBENCH='3.6.*' 27 | - php: 7.2 28 | env: LARAVEL='5.7.*' TESTBENCH='3.7.*' 29 | - php: 7.2 30 | env: LARAVEL='5.8.*' TESTBENCH='3.8.*' 31 | - php: 7.2 32 | env: LARAVEL='^6' TESTBENCH='^4' 33 | - php: 7.2 34 | env: LARAVEL='^7' TESTBENCH='^5' 35 | - php: 7.3 36 | env: LARAVEL='5.5.*' TESTBENCH='3.5.*' 37 | - php: 7.3 38 | env: LARAVEL='5.6.*' TESTBENCH='3.6.*' 39 | - php: 7.3 40 | env: LARAVEL='5.7.*' TESTBENCH='3.7.*' 41 | - php: 7.3 42 | env: LARAVEL='5.8.*' TESTBENCH='3.8.*' 43 | - php: 7.3 44 | env: LARAVEL='^6' TESTBENCH='^4' 45 | - php: 7.3 46 | env: LARAVEL='^7' TESTBENCH='^5' 47 | - php: 7.3 48 | env: LARAVEL='^8' TESTBENCH='6.x-dev' 49 | - php: 7.4 50 | env: LARAVEL='5.5.*' TESTBENCH='3.5.*' 51 | - php: 7.4 52 | env: LARAVEL='5.6.*' TESTBENCH='3.6.*' 53 | - php: 7.4 54 | env: LARAVEL='5.7.*' TESTBENCH='3.7.*' 55 | - php: 7.4 56 | env: LARAVEL='5.8.*' TESTBENCH='3.8.*' 57 | - php: 7.4 58 | env: LARAVEL='^6' TESTBENCH='^4' 59 | - php: 7.4 60 | env: LARAVEL='^7' TESTBENCH='^5' 61 | - php: 7.4 62 | env: LARAVEL='^8' TESTBENCH='6.x-dev' 63 | 64 | 65 | before_install: 66 | - mysql -e 'CREATE DATABASE db_rebuild;' 67 | - travis_retry composer self-update 68 | - composer global require hirak/prestissimo 69 | - COMPOSER_MEMORY_LIMIT=-1 travis_retry composer require --no-update --no-interaction "illuminate/support:${LARAVEL}" "illuminate/console:${LARAVEL}" "illuminate/database:${LARAVEL}" "orchestra/testbench:${TESTBENCH}" "orchestra/database:${TESTBENCH}" 70 | 71 | install: 72 | - COMPOSER_MEMORY_LIMIT=-1 travis_retry composer install --no-interaction 73 | 74 | script: 75 | - composer test 76 | 77 | notifications: 78 | email: false 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Laravel Type-Safe Collection 3 |

4 | 5 |

6 | 7 | Build Status 8 | 9 | 10 | Quality score 11 | 12 | 13 | Code coverage 14 | 15 |

16 | 17 | PHP is getting more mature and allows us to program strong typed in new ways with each new version, however we still lack the option to have generic lists/arrays. This package aims to provide such a thing in the meantime. 18 | 19 | The `TypeSafeCollection` provided by this package will make sure that any object within it is the object you expect. 20 | 21 | ## Installation 22 | Add this package to composer. 23 | 24 | ``` 25 | composer require webparking/laravel-type-safe-collection 26 | ``` 27 | 28 | ## Usage 29 | 30 | ```php 31 | /** 32 | * @method \ArrayIterator|User[] getIterator() 33 | * @method User|null first() 34 | */ 35 | class UserCollection extends TypeSafeCollection 36 | { 37 | protected $type = User::class; 38 | } 39 | ``` 40 | 41 | ```php 42 | class User extends Model 43 | { 44 | public function newCollection(array $models = []): UserCollection 45 | { 46 | return new UserCollection($models); 47 | } 48 | } 49 | ``` 50 | 51 | All queries on User that would result in a Collection will now result in a UserCollection. 52 | 53 | ```php 54 | get_class(User::all()) // UserCollection 55 | get_class(User::where('type', '=', 'admin')->get()) // UserCollection 56 | get_class(User::where('id', '=', 1)->first()) // User 57 | ``` 58 | 59 | ## Licence and Postcardware 60 | 61 | This software is open source and licensed under the [MIT license](LICENSE.md). 62 | 63 | If you use this software in your daily development we would appreciate to receive a postcard of your hometown. 64 | 65 | Please send it to: Webparking BV, Cypresbaan 31a, 2908 LT Capelle aan den IJssel, The Netherlands 66 | -------------------------------------------------------------------------------- /src/Eloquent/TypeSafeCollection.php: -------------------------------------------------------------------------------- 1 | items, $this->type); 22 | } 23 | 24 | /** 25 | * Create a new collection by invoking the callback a given amount of times. 26 | * 27 | * @param int $number 28 | * @param callable $callback 29 | * @return Collection 30 | */ 31 | public static function times($number, callable $callback = null) 32 | { 33 | return new static(Collection::times($number, $callback)); 34 | } 35 | 36 | /** 37 | * Collapse the collection of items into a single array. 38 | * 39 | * @return static 40 | */ 41 | public function collapse() 42 | { 43 | return new static(Arr::collapse($this->items)); 44 | } 45 | 46 | /** 47 | * Cross join with the given lists, returning all possible permutations. 48 | * 49 | * @param mixed ...$lists 50 | * @return Collection 51 | */ 52 | public function crossJoin(...$lists) 53 | { 54 | return new Collection(Arr::crossJoin( 55 | $this->items, ...array_map([$this, 'getArrayableItems'], $lists) 56 | )); 57 | } 58 | 59 | /** 60 | * Push an item onto the beginning of the collection. 61 | * 62 | * @param mixed $value 63 | * @param mixed $key 64 | * @return $this 65 | */ 66 | public function prepend($value, $key = null) 67 | { 68 | Assert::isInstanceOf($value, $this->type); 69 | 70 | parent::prepend($value, $key); 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Chunk the underlying collection array. 77 | * 78 | * @param int $size 79 | * @return EloquentCollection 80 | */ 81 | public function chunk($size) 82 | { 83 | if ($size <= 0) { 84 | return new EloquentCollection(); 85 | } 86 | 87 | $chunks = []; 88 | 89 | foreach (array_chunk($this->items, $size, true) as $chunk) { 90 | $chunks[] = new static($chunk); 91 | } 92 | 93 | return new EloquentCollection($chunks); 94 | } 95 | 96 | /** 97 | * Dump the collection. 98 | * 99 | * @return $this 100 | */ 101 | public function dump() 102 | { 103 | $this->toBase()->dump(); 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * Flip the items in the collection. 110 | * 111 | * @throws InvalidOperationException 112 | */ 113 | public function flip() 114 | { 115 | throw new InvalidOperationException('Flip() can never work on a TypeSafeCollection'); 116 | } 117 | 118 | /** 119 | * Run a map over each of the items. 120 | * 121 | * @return Collection 122 | */ 123 | public function map(callable $callback) 124 | { 125 | return $this->toBase()->map($callback); 126 | } 127 | 128 | /** 129 | * Run a dictionary map over the items. 130 | * 131 | * The callback should return an associative array with a single key/value pair. 132 | * 133 | * @return Collection 134 | */ 135 | public function mapToDictionary(callable $callback) 136 | { 137 | $base = $this->toBase(); 138 | 139 | if (method_exists($base, 'mapToDictionary')) { 140 | return $base->mapToDictionary($callback); 141 | } 142 | 143 | throw new InvalidOperationException('This method is not implemented in the Collection you\'re using'); 144 | } 145 | 146 | /** 147 | * Run a grouping map over the items. 148 | * 149 | * The callback should return an associative array with a single key/value pair. 150 | * 151 | * @return Collection 152 | */ 153 | public function mapToGroups(callable $callback) 154 | { 155 | return $this->toBase()->mapToGroups($callback); 156 | } 157 | 158 | /** 159 | * Run an associative map over each of the items. 160 | * 161 | * The callback should return an associative array with a single key/value pair. 162 | * 163 | * @return Collection 164 | */ 165 | public function mapWithKeys(callable $callback) 166 | { 167 | return $this->toBase()->mapWithKeys($callback); 168 | } 169 | 170 | /** 171 | * Map the values into a new class. 172 | * 173 | * @param string $class 174 | * @return Collection 175 | */ 176 | public function mapInto($class) 177 | { 178 | return $this->toBase()->mapInto($class); 179 | } 180 | 181 | /** 182 | * Create a collection by using this collection for keys and another for its values. 183 | * 184 | * @param mixed $values 185 | * @throws InvalidOperationException 186 | */ 187 | public function combine($values) 188 | { 189 | throw new InvalidOperationException('Flip() can never work on a TypeSafeCollection'); 190 | } 191 | 192 | /** 193 | * Partition the collection into two arrays using the given callback or key. 194 | * 195 | * @param mixed $key 196 | * @param mixed $operator 197 | * @param mixed $value 198 | * @return Collection 199 | */ 200 | public function partition($key, $operator = null, $value = null) 201 | { 202 | $partitions = [new static(), new static()]; 203 | 204 | $callback = \func_num_args() === 1 205 | ? $this->valueRetriever($key) 206 | : $this->operatorForWhere(...\func_get_args()); 207 | 208 | foreach ($this->items as $key => $item) { 209 | $partitions[(int) !$callback($item, $key)][$key] = $item; 210 | } 211 | 212 | return new Collection($partitions); 213 | } 214 | 215 | /** 216 | * Group an associative array by a field or using a callback. 217 | * 218 | * @param callable|string $groupBy 219 | * @param bool $preserveKeys 220 | * @return Collection 221 | */ 222 | public function groupBy($groupBy, $preserveKeys = false) 223 | { 224 | return $this->toBase()->groupBy($groupBy, $preserveKeys); 225 | } 226 | 227 | /** 228 | * Get the keys of the collection items. 229 | * 230 | * @return Collection 231 | */ 232 | public function keys() 233 | { 234 | return $this->toBase()->keys(); 235 | } 236 | 237 | /** 238 | * Set the item at a given offset. 239 | * 240 | * @param mixed $key 241 | * @param mixed $value 242 | */ 243 | public function offsetSet($key, $value): void 244 | { 245 | Assert::isInstanceOf($value, $this->type); 246 | 247 | parent::offsetSet($key, $value); 248 | } 249 | 250 | /** 251 | * Zip the collection together with one or more arrays. 252 | * 253 | * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]); 254 | * => [[1, 4], [2, 5], [3, 6]] 255 | * 256 | * @param mixed ...$items 257 | * @return Collection 258 | */ 259 | public function zip($items) 260 | { 261 | $arrayableItems = array_map(function ($items) { 262 | return $this->getArrayableItems($items); 263 | }, \func_get_args()); 264 | 265 | /** @var mixed[] $params */ 266 | $params = array_merge([function () { 267 | return new static(\func_get_args()); 268 | }, $this->items], $arrayableItems); 269 | 270 | return new Collection(\call_user_func_array('array_map', $params)); 271 | } 272 | 273 | /** 274 | * Count the number of items in the collection using a given truth test. 275 | * 276 | * @param callable|null $callback 277 | * @return Collection 278 | */ 279 | public function countBy($callback = null) 280 | { 281 | $base = $this->toBase(); 282 | 283 | if (method_exists($base, 'countBy')) { 284 | return $base->countBy($callback); 285 | } 286 | 287 | throw new InvalidOperationException('This method is not implemented in the Collection you\'re using'); 288 | } 289 | 290 | /** 291 | * Add an item to the collection. 292 | * 293 | * @param mixed $item 294 | * @return $this 295 | */ 296 | public function add($item) 297 | { 298 | Assert::isInstanceOf($item, $this->type); 299 | 300 | $this->items[] = $item; 301 | 302 | return $this; 303 | } 304 | } 305 | --------------------------------------------------------------------------------