From 17877e642aab7a1d31edb9d98ee4614c52af1c08 Mon Sep 17 00:00:00 2001 From: "kouhei.takemoto" Date: Sat, 22 Feb 2020 22:32:19 +0900 Subject: [PATCH] Initial Commit --- .gitignore | 10 ++ .phan/config.php | 67 ++++++++ Makefile | 38 +++++ composer.json | 25 +++ phpinsights.php | 62 ++++++++ phpunit.xml | 24 +++ src/Collection.class.php | 210 +++++++++++++++++++++++++ src/collection/Filter.class.php | 37 +++++ src/collection/Generator.class.php | 74 +++++++++ src/collection/Register.class.php | 38 +++++ src/collection/Scanner.class.php | 34 +++++ tests/CollectionTest.php | 236 +++++++++++++++++++++++++++++ tests/bootstrap.php | 3 + 13 files changed, 858 insertions(+) create mode 100644 .gitignore create mode 100644 .phan/config.php create mode 100644 Makefile create mode 100644 composer.json create mode 100644 phpinsights.php create mode 100644 phpunit.xml create mode 100644 src/Collection.class.php create mode 100644 src/collection/Filter.class.php create mode 100644 src/collection/Generator.class.php create mode 100644 src/collection/Register.class.php create mode 100644 src/collection/Scanner.class.php create mode 100644 tests/CollectionTest.php create mode 100644 tests/bootstrap.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9987130 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# IDE +/.idea + +# composer +/vendor +composer.lock + +# phpunit +.phpunit.result.cache +phpunit.tdx diff --git a/.phan/config.php b/.phan/config.php new file mode 100644 index 0000000..c09fdc3 --- /dev/null +++ b/.phan/config.php @@ -0,0 +1,67 @@ + null, + + // A list of directories that should be parsed for class and + // method information. After excluding the directories + // defined in exclude_analysis_directory_list, the remaining + // files will be statically analyzed for errors. + // + // Thus, both first-party and third-party code being used by + // your application should be included in this list. + 'directory_list' => [ + 'src', + ], + + // A directory list that defines files that will be excluded + // from static analysis, but whose class and method + // information should be included. + // + // Generally, you'll want to include the directories for + // third-party code (such as "vendor/") in this list. + // + // n.b.: If you'd like to parse but not analyze 3rd + // party code, directories containing that code + // should be added to the `directory_list` as + // to `exclude_analysis_directory_list`. + "exclude_analysis_directory_list" => [ + 'vendor/', + ], + + // A list of plugin files to execute. + // Plugins which are bundled with Phan can be added here by providing their name (e.g. 'AlwaysReturnPlugin') + // + // Documentation about available bundled plugins can be found + // at https://github.com/phan/phan/tree/master/.phan/plugins + // + // Alternately, you can pass in the full path to a PHP file + // with the plugin's implementation (e.g. 'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php') + 'plugins' => [ + // checks if a function, closure or method unconditionally returns. + // can also be written as 'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php' + 'AlwaysReturnPlugin', + 'DollarDollarPlugin', + 'DuplicateArrayKeyPlugin', + 'DuplicateExpressionPlugin', + 'PregRegexCheckerPlugin', + 'PrintfCheckerPlugin', + 'SleepCheckerPlugin', + // Checks for syntactically unreachable statements in + // the global scope or function bodies. + 'UnreachableCodePlugin', + 'UseReturnValuePlugin', +// 'EmptyStatementListPlugin', +// 'LoopVariableReusePlugin', + ], +]; diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..daffade --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +DATE=`date +%Y-%m-%d` +DATETIME = `date +%Y-%m-%d_%H-%M-%S` + +define highlight + @echo "\033[1;32m$1\033[0m" +endef + +.PHONY: test_all +test_all: + @./vendor/bin/phpunit + +.PHONY: composer_reload +composer_reload: + @composer clear-cache + @composer dump-autoload + +.PHONY: composer_develop +composer_develop: + @composer install -vvv --dev --prefer-dist --optimize-autoloader + +.PHONY: composer_public +composer_public: + @composer install -vvv --no-dev --prefer-dist --optimize-autoloader + +.PHONY: composer_check +composer_check: + $(call highlight,#### ---- composer diag ---- ####) + @composer diag + +.PHONY: phan +phan: + @mkdir -p ./phan/${DATE} + @./vendor/bin/phan --no-progress-bar --output ./.phan/${DATE}/${DATETIME}.txt + +.PHONY: insights +insights: + @./vendor/bin/phpinsights analyse ./src + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d01c293 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "take64/citrus-collection", + "description": "php collection library", + "keywords": ["citrus", "php", "library", "collection"], + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "take64", + "email": "take64@citrus.tk" + } + ], + "require": {}, + "require-dev": { + "phpunit/phpunit": "^8.4", + "nunomaduro/phpinsights": "^v1.13", + "phan/phan": "^2.4" + }, + "autoload": { + "psr-4": { + "Citrus\\": "src/", + "Test\\": "tests/" + } + } +} diff --git a/phpinsights.php b/phpinsights.php new file mode 100644 index 0000000..8541027 --- /dev/null +++ b/phpinsights.php @@ -0,0 +1,62 @@ + 'default', + 'exclude' => [ + // 'path/to/directory-or-file' + ], + 'add' => [ + // ExampleMetric::class => [ + // ExampleInsight::class, + // ] + NunoMaduro\PhpInsights\Domain\Metrics\Code\Code::class => [ + SlevomatCodingStandard\Sniffs\ControlStructures\RequireYodaComparisonSniff::class, + ], + ], + 'remove' => [ + // ExampleInsight::class, + SlevomatCodingStandard\Sniffs\ControlStructures\DisallowShortTernaryOperatorSniff::class, + SlevomatCodingStandard\Sniffs\ControlStructures\DisallowYodaComparisonSniff::class, + SlevomatCodingStandard\Sniffs\PHP\UselessParenthesesSniff::class, + SlevomatCodingStandard\Sniffs\TypeHints\DisallowArrayTypeHintSyntaxSniff::class, + SlevomatCodingStandard\Sniffs\TypeHints\UselessConstantTypeHintSniff::class, + PhpCsFixer\Fixer\ControlStructure\NoUnneededControlParenthesesFixer::class, + PhpCsFixer\Fixer\Operator\TernaryOperatorSpacesFixer::class, + PhpCsFixer\Fixer\Phpdoc\PhpdocInlineTagFixer::class, + PhpCsFixer\Fixer\Phpdoc\PhpdocSeparationFixer::class, + PhpCsFixer\Fixer\Phpdoc\AlignMultilineCommentFixer::class, + PHP_CodeSniffer\Standards\PSR2\Sniffs\ControlStructures\ElseIfDeclarationSniff::class, + PHP_CodeSniffer\Standards\Generic\Sniffs\Formatting\SpaceAfterCastSniff::class, + ], + 'config' => [ + // ExampleInsight::class => [ + // 'key' => 'value', + // ], + ObjectCalisthenics\Sniffs\NamingConventions\ElementNameMinimalLengthSniff::class => [ + 'minLength' => 3, + 'allowedShortNames' => ['i', 'id', 'to', 'up', 'ky', 'vl', 'e'], + ], + PhpCsFixer\Fixer\CastNotation\CastSpacesFixer::class => [ + 'space' => 'none', + ], + PhpCsFixer\Fixer\Basic\BracesFixer::class => [ + 'allow_single_line_closure' => false, + 'position_after_anonymous_constructs' => 'next', + 'position_after_control_structures' => 'next', + 'position_after_functions_and_oop_constructs' => 'next', + ], + SlevomatCodingStandard\Sniffs\Commenting\DocCommentSpacingSniff::class => [ + 'linesCountBeforeFirstContent' => 0, + 'linesCountBetweenDescriptionAndAnnotations' => 1, + 'linesCountBetweenDifferentAnnotationsTypes' => 0, + 'linesCountBetweenAnnotationsGroups' => 0, + 'linesCountAfterLastContent' => 0, + 'annotationsGroups' => [], + ], + PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer::class => [ + 'tokens' => [], // possibles values ['break', 'case', 'continue', 'curly_brace_block', 'default', 'extra', 'parenthesis_brace_block', 'return', 'square_brace_block', 'switch', 'throw', 'use', 'use_trait'] + ] + ], +]; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..431919e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,24 @@ + + + + + tests/ + + + + + + + + \ No newline at end of file diff --git a/src/Collection.class.php b/src/Collection.class.php new file mode 100644 index 0000000..9d31fbf --- /dev/null +++ b/src/Collection.class.php @@ -0,0 +1,210 @@ + + * @license http://www.citrus.tk/ + */ + +namespace Citrus; + +use Citrus\Collection\Filter; +use Citrus\Collection\Generator; +use Citrus\Collection\Register; +use Citrus\Collection\Scanner; + +/** + * コレクションクラス + */ +class Collection +{ + /** @var iterable データソース */ + protected $source; + + + + /** + * constructor. + * + * @param array $source + */ + public function __construct(array $source) + { + $this->source = $source; + } + + + + /************************************************************************** + * Register + **************************************************************************/ + + /** + * callable関数がnull以外の値を返した場合、値を積んで返却する + * + * @param callable $callable + * @return self + */ + public function append(callable $callable): self + { + $this->source = Register::append($this->source, $callable); + return $this; + } + + + + /************************************************************************** + * Scanner + **************************************************************************/ + + /** + * callable関数を適用した内容を積んで返却する + * + * @param callable $callable + * @return self + */ + public function map(callable $callable): self + { + $this->source = Scanner::map($this->source, $callable); + return $this; + } + + + + /************************************************************************** + * Filter + **************************************************************************/ + + /** + * callable関数の返却値がtrueの場合に積んで返却する + * + * @param callable $callable + * @return self + */ + public function filter(callable $callable): self + { + $this->source = Filter::filter($this->source, $callable); + return $this; + } + + + + /** + * callable関数がの返却値がtrueの場合に削除して返却する + * + * @param callable $callable + * @return self + */ + public function remove(callable $callable): self + { + // false の場合に残せば良いので filter の逆 + return $this->filter(function ($vl, $ky) use ($callable) { + return (false === $callable($vl, $ky)); + }); + } + + + + /************************************************************************** + * Generator + **************************************************************************/ + + /** + * 配列設定して、コレクションを生成 + * + * @param array $source + * @return self + */ + public static function stream(array $source): self + { + return Generator::stream($source); + } + + + + /** + * 指定した範囲でcallable関数を実行し、コレクションを生成 + * + * @param int $start 開始 + * @param int $end 終了 + * @param callable $callable + * @return self + */ + public static function range(int $start, int $end, callable $callable): self + { + return Generator::range($start, $end, $callable); + } + + + + /** + * 指定した回数でcallable関数を実行し、コレクションを生成 + * + * @param int $count 回数 + * @param callable $callable + * @return self + */ + public static function repeat(int $count, callable $callable): self + { + // 範囲は1から$count + return Generator::range(1, $count, $callable); + } + + + + /** + * 両方の要素を残したいい感じの配列マージ + * + * 同じ要素がある場合はあとが優先 + * + * @param array $values + * @return self + */ + public function betterMerge(array $values): self + { + $this->source = Generator::betterMergeRecursive($this->source, $values); + return $this; + } + + + + /************************************************************************** + * Exporter + **************************************************************************/ + + /** + * 出力 + * + * @return array + */ + public function toList(): array + { + return $this->source; + } + + + + /** + * 出力(値だけ) + * + * @return array + */ + public function toValues(): array + { + return array_values($this->source); + } + + + + /** + * 出力(キーだけ) + * + * @return array + */ + public function toKeys(): array + { + return array_keys($this->source); + } +} diff --git a/src/collection/Filter.class.php b/src/collection/Filter.class.php new file mode 100644 index 0000000..91055f2 --- /dev/null +++ b/src/collection/Filter.class.php @@ -0,0 +1,37 @@ + + * @license http://www.citrus.tk/ + */ + +namespace Citrus\Collection; + +/** + * コレクションメソッド(フィルタ系) + */ +class Filter +{ + /** + * callable関数の返却値がtrueの場合に積んで返却する + * + * @param iterable $source + * @param callable $callable function($key, $value) + * @return iterable + */ + public static function filter(iterable $source, callable $callable): iterable + { + $results = []; + foreach ($source as $ky => $vl) + { + if (true === $callable($vl, $ky)) + { + $results[$ky] = $vl; + } + } + return $results; + } +} diff --git a/src/collection/Generator.class.php b/src/collection/Generator.class.php new file mode 100644 index 0000000..0e89c21 --- /dev/null +++ b/src/collection/Generator.class.php @@ -0,0 +1,74 @@ + + * @license http://www.citrus.tk/ + */ + +namespace Citrus\Collection; + +use Citrus\Collection; + +/** + * コレクションメソッド(生成系) + */ +class Generator +{ + /** + * 配列設定して、コレクションを生成 + * + * @param array $source + * @return Collection + */ + public static function stream(array $source): Collection + { + return new Collection($source); + } + + + + /** + * 指定した範囲でcallable関数を実行し、コレクションを生成 + * + * @param int $start 開始 + * @param int $end 終了 + * @param callable $callable + * @return Collection + */ + public static function range(int $start, int $end, callable $callable): Collection + { + $results = []; + for ($i = $start; $i <= $end; $i++) + { + $results[] = $callable($i); + } + return new Collection($results); + } + + + + /** + * 両方の要素を残したいい感じの配列マージ + * + * 同じ要素がある場合はあとが優先 + * + * @param array $array1 + * @param array $array2 + * @return array + */ + public static function betterMergeRecursive(array $array1, array $array2): array + { + foreach ($array2 as $ky => $vl) + { + $array1[$ky] = (true === is_array($vl) + ? self::betterMergeRecursive(($array1[$ky] ?? []), $array2[$ky]) // 配列の場合 + : $array2[$ky] // 配列以外の場合 + ); + } + + return $array1; + } +} diff --git a/src/collection/Register.class.php b/src/collection/Register.class.php new file mode 100644 index 0000000..a374c6d --- /dev/null +++ b/src/collection/Register.class.php @@ -0,0 +1,38 @@ + + * @license http://www.citrus.tk/ + */ + +namespace Citrus\Collection; + +/** + * コレクションメソッド(追加系) + */ +class Register +{ + /** + * callable関数がnull以外の値を返した場合、値を積んで返却する + * + * @param iterable $source + * @param callable $callable function($key, $value) + * @return iterable + */ + public static function append(iterable $source, callable $callable): iterable + { + $results = []; + foreach ($source as $ky => $vl) + { + $append = $callable($vl, $ky); + if (false === is_null($append)) + { + $results[$ky] = $append; + } + } + return $results; + } +} diff --git a/src/collection/Scanner.class.php b/src/collection/Scanner.class.php new file mode 100644 index 0000000..63cb934 --- /dev/null +++ b/src/collection/Scanner.class.php @@ -0,0 +1,34 @@ + + * @license http://www.citrus.tk/ + */ + +namespace Citrus\Collection; + +/** + * コレクションメソッド(スキャン系) + */ +class Scanner +{ + /** + * callable関数を適用した内容を積んで返却する + * + * @param iterable $source + * @param callable $callable function($key, $value) + * @return iterable + */ + public static function map(iterable $source, callable $callable): iterable + { + $results = []; + foreach ($source as $ky => $vl) + { + $results[] = $callable($vl, $ky); + } + return $results; + } +} diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php new file mode 100644 index 0000000..5218d46 --- /dev/null +++ b/tests/CollectionTest.php @@ -0,0 +1,236 @@ + + * @license http://www.citrus.tk/ + */ + +namespace Test; + +use Citrus\Collection; +use PHPUnit\Framework\TestCase; + +/** + * コレクションクラスのテスト + */ +class CollectionTest extends TestCase +{ + /** + * @test + */ + public function betterMerge_両方の要素を残したいい感じの配列マージ() + { + $array1 = [ + 'a' => 1, + 'b' => 2, + 'c' => [ + 'd' => 3, + 'e' => 4, + ], + 'f' => 5, + ]; + $array2 = [ + 'a' => 5, + 'c' => [ + 'g' => 6, + ], + 'h' => 7, + ]; + + $expected = [ + 'a' => 5, + 'b' => 2, + 'c' => [ + 'd' => 3, + 'e' => 4, + 'g' => 6, + ], + 'f' => 5, + 'h' => 7, + ]; + + // いい感じのマージ + $actual = Collection::stream($array1)->betterMerge($array2)->toList(); + + // 検算 + $this->assertSame($expected, $actual); + } + + + + /** + * @test + */ + public function filter_指定データのみ残した配列生成() + { + $values = [ + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => 4, + ]; + + // キー「c」以外を残す + $expected1 = [ + 'a' => 1, + 'b' => 2, + 'd' => 4, + ]; + // 検算 + $this->assertSame($expected1, Collection::stream($values)->filter(function ($vl, $ky) { + return ('c' !== $ky); + })->toList()); + + // 値「2」を超えるものだけ残す + $expected2 = [ + 'c' => 3, + 'd' => 4, + ]; + // 検算 + $this->assertSame($expected2, Collection::stream($values)->filter(function ($vl) { + return (2 < $vl); + })->toList()); + } + + + + /** + * @test + */ + public function remove_指定データのみ削除した配列生成() + { + $values = [ + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => 4, + ]; + + // キー「c」以外を削除 + $expected1 = [ + 'c' => 3, + ]; + // 検算 + $this->assertSame($expected1, Collection::stream($values)->remove(function ($vl, $ky) { + return (3 !== $vl); + })->toList()); + + // 値「2」を超えるものだけ削除 + $expected2 = [ + 'a' => 1, + 'b' => 2, + ]; + // 検算 + $this->assertSame($expected2, Collection::stream($values)->remove(function ($vl) { + return (2 < $vl); + })->toList()); + } + + + + /** + * @test + */ + public function append_データ生成できたものだけで配列生成() + { + $values = [ + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => 4, + ]; + + // キー「c」以外を残して、全部1を足す + $expected = [ + 'a' => (1 + 1), + 'b' => (2 + 1), + 'd' => (4 + 1), + ]; + // 検算 + $this->assertSame($expected, Collection::stream($values)->append(function ($vl, $ky) { + if ('c' !== $ky) + { + return ($vl + 1); + } + return null; + })->toList()); + } + + + + /** + * @test + */ + public function map_データ編集して配列生成() + { + $values = [ + 1, + 2, + 3, + 4, + ]; + + // 全部1を足す + $expected = [ + (1 + 1), + (2 + 1), + (3 + 1), + (4 + 1), + ]; + // 検算 + $this->assertSame($expected, Collection::stream($values)->map(function ($vl) { + return ($vl + 1); + })->toList()); + } + + + + /** + * @test + */ + public function range_指定範囲で配列生成() + { + $start = 5; + $end = 10; + + // 全部二乗 + $expected = [ + (5 * 5), + (6 * 6), + (7 * 7), + (8 * 8), + (9 * 9), + (10 * 10), + ]; + // 検算 + $this->assertSame($expected, Collection::range($start, $end, function ($index) { + return ($index * $index); + })->toList()); + } + + + + /** + * @test + */ + public function repeat_指定回数で配列生成() + { + $count = 5; + + // 全部インデックス + $expected = [ + 1, + 2, + 3, + 4, + 5, + ]; + // 検算 + $this->assertSame($expected, Collection::repeat($count, function ($index) { + return $index; + })->toList()); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..d21c14d --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,3 @@ +