├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── benchmarks ├── BenchObject.php ├── Map │ ├── MapBench.php │ └── MapBuilderBench.php ├── ObjectMapperBench.php ├── Point │ ├── ObjectPointBench.php │ └── PointFactoryBench.php └── Route │ └── RouteBuilderBench.php ├── composer.json ├── doc └── object-point-hierarchy-graph.png ├── phpbench.json.dist ├── phpunit.xml.dist ├── src ├── Exception │ ├── CheckPointSeizingException.php │ ├── Exception.php │ ├── InvalidArgumentException.php │ ├── InvalidOperationException.php │ └── InvalidTargetOperationException.php ├── ImmutableCollection.php ├── Map │ ├── Map.php │ ├── MapBuilder.php │ ├── MapBuilderInterface.php │ └── MapInterface.php ├── ObjectMapper.php ├── ObjectMapperInterface.php ├── ObjectMapperTrait.php ├── PathFinder │ ├── DynamicSourceToStaticTargetPathFinder.php │ ├── PathFinder.php │ ├── PathFinderCollection.php │ ├── PathFinderInterface.php │ ├── StaticPathFinder.php │ └── StaticSourceToDynamicTargetPathFinder.php ├── Point │ ├── CheckPointCollection.php │ ├── CheckPointInterface.php │ ├── DynamicPointInterface.php │ ├── DynamicSourcePointInterface.php │ ├── DynamicTargetPointInterface.php │ ├── IterableRecursionCheckPoint.php │ ├── MethodDynamicSourcePoint.php │ ├── MethodParameterDynamicTargetPoint.php │ ├── MethodParameterStaticTargetPoint.php │ ├── MethodStaticSourcePoint.php │ ├── ObjectPoint.php │ ├── ObjectPointInterface.php │ ├── PointFactory.php │ ├── PointFactoryInterface.php │ ├── PropertyDynamicSourcePoint.php │ ├── PropertyDynamicTargetPoint.php │ ├── PropertyStaticSourcePoint.php │ ├── PropertyStaticTargetPoint.php │ ├── RecursionCheckPoint.php │ ├── SourcePoint.php │ ├── SourcePointInterface.php │ ├── StaticPointInterface.php │ ├── StaticSourcePointInterface.php │ ├── StaticTargetPointInterface.php │ ├── TargetPoint.php │ └── TargetPointInterface.php ├── Route │ ├── Route.php │ ├── RouteBuilder.php │ ├── RouteBuilderInterface.php │ ├── RouteCollection.php │ └── RouteInterface.php ├── Source.php ├── SourceInterface.php ├── Target.php └── TargetInterface.php └── tests ├── Exception ├── CheckPointSeizingExceptionTest.php ├── ExceptionTest.php ├── InvalidArgumentExceptionTest.php ├── InvalidOperationExceptionTest.php └── InvalidTargetOperationExceptionTest.php ├── Map ├── MapBuilderTest.php └── MapTest.php ├── ObjectMapperTest.php ├── PathFinder ├── DynamicSourceToStaticTargetPathFinderTest.php ├── PathFinderCollectionTest.php ├── PathFinderTest.php ├── StaticPathFinderTest.php └── StaticSourceToDynamicTargetPathFinderTest.php ├── Point ├── CheckPointCollectionTest.php ├── IterableRecursionCheckPointTest.php ├── MethodDynamicSourcePointTest.php ├── MethodParameterDynamicTargetPointTest.php ├── MethodParameterStaticTargetPointTest.php ├── MethodStaticSourcePointTest.php ├── PointFactoryTest.php ├── PropertyDynamicSourcePointTest.php ├── PropertyDynamicTargetPointTest.php ├── PropertyStaticSourcePointTest.php ├── PropertyStaticTargetPointTest.php └── RecursionCheckPointTest.php ├── Route ├── RouteBuilderTest.php ├── RouteCollectionTest.php └── RouteTest.php ├── SourceTest.php ├── TargetTest.php ├── Test.php ├── TestDataProviderTrait.php ├── TestInvalidArgumentException.php ├── TestObjectA.php ├── TestObjectB.php └── TestObjectTrait.php /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [clement.cazaud@gmail.com](mailto:clement.cazaud@gmail.com). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First off, thank you for considering contributing to that project. 4 | 5 | This is an open source project and we love to receive contributions from our community — you! There are many ways to contribute, from writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests or writing code which can be incorporated into the project itself. 6 | 7 | Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests. 8 | 9 | --- 10 | 11 | ## Bug Reports & Feature Requests 12 | 13 | Before submitting a bug report or feature request, make sure you're not going to duplicate any existing one by checking the current list of open and closed [issues](https://github.com/opportus/object-mapper/issues). 14 | 15 | While submitting a [bug report](https://github.com/opportus/object-mapper/issues/new?template=bug_report.md) or [feature request](https://github.com/opportus/object-mapper/issues/new?template=feature_request.md), make sure you comply with the instructions in the template. This will help other contributors to address efficiently the issue. 16 | 17 | ## Pull Requests 18 | 19 | Pull requests must address an open bug report or feature request. 20 | 21 | Before implementing your feature or fix, make sure you're not going to duplicate anyone's work by checking the current list of open and closed [pull requests](https://github.com/opportus/object-mapper/pulls). 22 | 23 | Make your code comply with the [PSR-12 Coding Standards](https://www.php-fig.org/psr/psr-12/). 24 | 25 | Leave detailed commit messages. 26 | 27 | While submitting a pull request, make sure you comply with the instructions in the template. This will help other contributors to address efficiently the pull request. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Bug report 4 | about: Create a report to help us improve 5 | 6 | --- 7 | 8 | 9 | 10 | ## Bug Reproduction Steps 11 | 12 | 13 | 14 | ## Expected Result 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Feature request 4 | about: Suggest an idea for this project 5 | 6 | --- 7 | 8 | ## Problem 9 | 10 | 11 | 12 | ## Solutions 13 | 14 | 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Closes #. 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "composer" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | assignees: 8 | - "opportus" 9 | reviewers: 10 | - "opportus" 11 | labels: [ ] -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | 13 | setup: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | php: [ 7.4, 8.0, 8.1 ] 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/cache@v2 21 | id: composer-cache 22 | with: 23 | path: vendor 24 | key: ${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} 25 | restore-keys: | 26 | ${{ runner.os }}-php-${{ matrix.php }}- 27 | - uses: shivammathur/setup-php@v2 28 | with: 29 | php-version: ${{ matrix.php }} 30 | - run: composer validate 31 | - run: composer install --prefer-dist --no-progress --no-suggest 32 | if: steps.composer-cache.outputs.cache-hit != 'true' 33 | 34 | test-suite: 35 | needs: setup 36 | runs-on: ubuntu-latest 37 | env: 38 | XDEBUG_MODE: coverage 39 | strategy: 40 | matrix: 41 | php: [ 7.4, 8.0, 8.1 ] 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: actions/cache@v2 45 | id: composer-cache 46 | with: 47 | path: vendor 48 | key: ${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} 49 | restore-keys: | 50 | ${{ runner.os }}-php-${{ matrix.php }}- 51 | - run: vendor/bin/phpunit --coverage-text --coverage-clover=${{ github.workspace }}/coverage-${{ matrix.php }}.clover.xml 52 | - uses: actions/upload-artifact@v2 53 | with: 54 | path: coverage-${{ matrix.php }}.clover.xml 55 | - uses: codacy/codacy-coverage-reporter-action@v1 56 | with: 57 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 58 | coverage-reports: ${{ github.workspace }}/coverage-${{ matrix.php }}.clover.xml 59 | 60 | benchmark-suite: 61 | needs: setup 62 | runs-on: ubuntu-latest 63 | strategy: 64 | matrix: 65 | php: [ 7.4, 8.0, 8.1 ] 66 | steps: 67 | - uses: actions/checkout@v2 68 | - uses: actions/cache@v2 69 | id: composer-cache 70 | with: 71 | path: vendor 72 | key: ${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} 73 | restore-keys: | 74 | ${{ runner.os }}-php-${{ matrix.php }}- 75 | - run: vendor/bin/phpbench run --retry-threshold=2 --report=aggregate --report=default --report=compare --report=memory --report=env | tee benchmark-${{ matrix.php }}.txt 76 | - uses: actions/upload-artifact@v2 77 | with: 78 | path: benchmark-${{ matrix.php }}.txt 79 | 80 | code-standards-check: 81 | needs: setup 82 | runs-on: ubuntu-latest 83 | strategy: 84 | matrix: 85 | php: [ 8.0 ] 86 | steps: 87 | - uses: actions/checkout@v2 88 | - uses: actions/cache@v2 89 | id: composer-cache 90 | with: 91 | path: vendor 92 | key: ${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} 93 | restore-keys: | 94 | ${{ runner.os }}-php-${{ matrix.php }}- 95 | - run: vendor/bin/php-cs-fixer fix --dry-run --diff --using-cache=no . 96 | 97 | code-analysis: 98 | needs: setup 99 | runs-on: ubuntu-latest 100 | strategy: 101 | matrix: 102 | php: [ 8.0 ] 103 | steps: 104 | - uses: actions/checkout@v2 105 | - uses: actions/cache@v2 106 | id: composer-cache 107 | with: 108 | path: vendor 109 | key: ${{ runner.os }}-php-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} 110 | restore-keys: | 111 | ${{ runner.os }}-php-${{ matrix.php }}- 112 | - uses: codacy/codacy-analysis-cli-action@1.1.0 113 | with: 114 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 115 | upload: true 116 | verbose: true 117 | max-allowed-issues: 2147483647 118 | - uses: codacy/codacy-analysis-cli-action@1.1.0 119 | with: 120 | output: results.sarif 121 | format: sarif 122 | gh-code-scanning-compat: true 123 | max-allowed-issues: 2147483647 124 | - uses: github/codeql-action/upload-sarif@v1 125 | with: 126 | sarif_file: results.sarif 127 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .ideavimrc 3 | .gradle 4 | .vscode 5 | *.code-workspace 6 | .php_cs.cache 7 | .phpunit.result.cache 8 | composer.lock 9 | coverage* 10 | MEMO.md 11 | phpunit*.phar 12 | vendor 13 | tags 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opportus/object-mapper/d308b8811c7f9ce8f54c055d4b732995461bcf30/CHANGELOG.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2020 Clément Cazaud 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do 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 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /benchmarks/BenchObject.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Benchmarks; 13 | 14 | /** 15 | * The bench object. 16 | * 17 | * @package Opportus\ObjectMapper\Benchmarks 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | class BenchObject 22 | { 23 | private $a; 24 | private $b; 25 | 26 | public function __construct(int $a) 27 | { 28 | $this->a = $a; 29 | } 30 | 31 | public function getA(): int 32 | { 33 | return $this->a; 34 | } 35 | 36 | public function getB(): int 37 | { 38 | return $this->b; 39 | } 40 | 41 | public function setB(int $b) 42 | { 43 | $this->b = $b; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /benchmarks/Map/MapBench.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Benchmarks\Map; 13 | 14 | use Opportus\ObjectMapper\Benchmarks\BenchObject; 15 | use Opportus\ObjectMapper\Map\MapBuilder; 16 | use Opportus\ObjectMapper\PathFinder\StaticPathFinder; 17 | use Opportus\ObjectMapper\Point\PointFactory; 18 | use Opportus\ObjectMapper\Route\RouteBuilder; 19 | use Opportus\ObjectMapper\Source; 20 | use Opportus\ObjectMapper\Target; 21 | use PhpBench\Benchmark\Metadata\Annotations\Iterations; 22 | use PhpBench\Benchmark\Metadata\Annotations\Revs; 23 | 24 | /** 25 | * The map bench. 26 | * 27 | * @package Opportus\ObjectMapper\Benchmarks\Map 28 | * @author Clément Cazaud 29 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 30 | */ 31 | class MapBench 32 | { 33 | private $pathFinderMap; 34 | private $pathFinderSource; 35 | private $pathFinderTarget; 36 | 37 | private $noPathFinderMap; 38 | private $noPathFinderSource; 39 | private $noPathFinderTarget; 40 | 41 | public function __construct() 42 | { 43 | $pointFactory = new PointFactory(); 44 | $routeBuilder = new RouteBuilder($pointFactory); 45 | $mapBuilder = new MapBuilder($routeBuilder); 46 | 47 | $source = new BenchObject(1); 48 | $source->setB(11); 49 | 50 | $this->pathFinderMap = $mapBuilder 51 | ->addPathFinder(new StaticPathFinder($routeBuilder)) 52 | ->getMap(); 53 | 54 | $this->pathFinderSource = new Source($source); 55 | $this->pathFinderTarget = new Target(BenchObject::class); 56 | 57 | $this->noPathFinderMap = $mapBuilder 58 | ->getRouteBuilder() 59 | ->setStaticSourcePoint(\sprintf('%s::getA()', BenchObject::class)) 60 | ->setStaticTargetPoint(\sprintf('%s::__construct()::$a', BenchObject::class)) 61 | ->addRouteToMapBuilder() 62 | ->setStaticSourcePoint(\sprintf('%s::getB()', BenchObject::class)) 63 | ->setStaticTargetPoint(\sprintf('%s::setB()::$b', BenchObject::class)) 64 | ->addRouteToMapBuilder() 65 | ->getMapBuilder() 66 | ->getMap(); 67 | 68 | $this->noPathFinderSource = new Source($source); 69 | $this->noPathFinderTarget = new Target(BenchObject::class); 70 | } 71 | 72 | /** 73 | * @Revs(1000) 74 | * @Iterations(10) 75 | */ 76 | public function benchGetRoutesWithPathFinder() 77 | { 78 | $this->pathFinderMap->getRoutes( 79 | $this->pathFinderSource, 80 | $this->pathFinderTarget 81 | ); 82 | } 83 | 84 | /** 85 | * @Revs(1000) 86 | * @Iterations(10) 87 | */ 88 | public function benchGetRoutesWithNoPathFinder() 89 | { 90 | $this->noPathFinderMap->getRoutes( 91 | $this->noPathFinderSource, 92 | $this->noPathFinderTarget 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /benchmarks/Map/MapBuilderBench.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Benchmarks\Map; 13 | 14 | use Opportus\ObjectMapper\Benchmarks\BenchObject; 15 | use Opportus\ObjectMapper\Map\MapBuilder; 16 | use Opportus\ObjectMapper\Point\PointFactory; 17 | use Opportus\ObjectMapper\Route\RouteBuilder; 18 | use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods; 19 | use PhpBench\Benchmark\Metadata\Annotations\Iterations; 20 | use PhpBench\Benchmark\Metadata\Annotations\Revs; 21 | 22 | /** 23 | * The map builder bench. 24 | * 25 | * @package Opportus\ObjectMapper\Benchmarks\Map 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | * 29 | * @BeforeMethods({"buildMapBuilder"}) 30 | */ 31 | class MapBuilderBench 32 | { 33 | private $mapBuilder; 34 | 35 | /** 36 | * @Revs(1000) 37 | * @Iterations(10) 38 | */ 39 | public function benchBuildPathFinderMap() 40 | { 41 | $this->mapBuilder->getMap(true); 42 | } 43 | 44 | /** 45 | * @Revs(1000) 46 | * @Iterations(10) 47 | */ 48 | public function benchBuildNoPathFinderMap() 49 | { 50 | $this->mapBuilder 51 | ->getRouteBuilder() 52 | ->setStaticSourcePoint(\sprintf('%s::getA()', BenchObject::class)) 53 | ->setStaticTargetPoint(\sprintf('%s::__construct()::$a', BenchObject::class)) 54 | ->addRouteToMapBuilder() 55 | ->setStaticSourcePoint(\sprintf('%s::getB()', BenchObject::class)) 56 | ->setStaticTargetPoint(\sprintf('%s::setB()::$b', BenchObject::class)) 57 | ->addRouteToMapBuilder() 58 | ->getMapBuilder() 59 | ->getMap(); 60 | } 61 | 62 | public function buildMapBuilder() 63 | { 64 | $pointFactory = new PointFactory(); 65 | $routeBuilder = new RouteBuilder($pointFactory); 66 | $mapBuilder = new MapBuilder($routeBuilder); 67 | 68 | $this->mapBuilder = $mapBuilder; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /benchmarks/ObjectMapperBench.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Benchmarks; 13 | 14 | use Opportus\ObjectMapper\Map\MapBuilder; 15 | use Opportus\ObjectMapper\ObjectMapper; 16 | use Opportus\ObjectMapper\PathFinder\StaticPathFinder; 17 | use Opportus\ObjectMapper\Point\PointFactory; 18 | use Opportus\ObjectMapper\Route\RouteBuilder; 19 | use PhpBench\Benchmark\Metadata\Annotations\Iterations; 20 | use PhpBench\Benchmark\Metadata\Annotations\Revs; 21 | 22 | /** 23 | * The object mapper bench. 24 | * 25 | * @package Opportus\ObjectMapper\Benchmarks 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class ObjectMapperBench 30 | { 31 | private $objectMapper; 32 | private $noPathFinderMap; 33 | private $source; 34 | 35 | public function __construct() 36 | { 37 | $pointFactory = new PointFactory(); 38 | $routeBuilder = new RouteBuilder($pointFactory); 39 | $mapBuilder = new MapBuilder($routeBuilder); 40 | $objectMapper = new ObjectMapper($mapBuilder); 41 | 42 | $this->objectMapper = $objectMapper; 43 | 44 | $this->noPathFinderMap = $mapBuilder 45 | ->getRouteBuilder() 46 | ->setStaticSourcePoint(\sprintf('%s::getA()', BenchObject::class)) 47 | ->setStaticTargetPoint(\sprintf('%s::__construct()::$a', BenchObject::class)) 48 | ->addRouteToMapBuilder() 49 | ->setStaticSourcePoint(\sprintf('%s::getB()', BenchObject::class)) 50 | ->setStaticTargetPoint(\sprintf('%s::setB()::$b', BenchObject::class)) 51 | ->addRouteToMapBuilder() 52 | ->getMapBuilder() 53 | ->getMap(); 54 | 55 | $this->source = new BenchObject(1); 56 | $this->source->setB(11); 57 | } 58 | 59 | /** 60 | * @Revs(1000) 61 | * @Iterations(10) 62 | */ 63 | public function benchMapWithPathFinder() 64 | { 65 | $this->objectMapper->map( 66 | $this->source, 67 | BenchObject::class 68 | ); 69 | } 70 | 71 | /** 72 | * @Revs(1000) 73 | * @Iterations(10) 74 | */ 75 | public function benchMapWithNoPathFinder() 76 | { 77 | $this->objectMapper->map( 78 | $this->source, 79 | BenchObject::class, 80 | $this->noPathFinderMap 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /benchmarks/Point/ObjectPointBench.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Benchmarks\Point; 13 | 14 | use Opportus\ObjectMapper\Benchmarks\BenchObject; 15 | use Opportus\ObjectMapper\Point\MethodParameterStaticTargetPoint; 16 | use Opportus\ObjectMapper\Point\MethodStaticSourcePoint; 17 | use PhpBench\Benchmark\Metadata\Annotations\Iterations; 18 | use PhpBench\Benchmark\Metadata\Annotations\Revs; 19 | 20 | /** 21 | * The object point bench. 22 | * 23 | * @package Opportus\ObjectMapper\Benchmarks\Point 24 | * @author Clément Cazaud 25 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 26 | */ 27 | class ObjectPointBench 28 | { 29 | /** 30 | * @Revs(1000) 31 | * @Iterations(10) 32 | */ 33 | public function benchConstruct() 34 | { 35 | new MethodStaticSourcePoint(\sprintf('%s::getA()', BenchObject::class)); 36 | new MethodParameterStaticTargetPoint(\sprintf('%s::setB()::$b', BenchObject::class)); 37 | new MethodStaticSourcePoint(\sprintf('%s::getA()', BenchObject::class)); 38 | new MethodParameterStaticTargetPoint(\sprintf('%s::setB()::$b', BenchObject::class)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /benchmarks/Point/PointFactoryBench.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Benchmarks\Point; 13 | 14 | use Opportus\ObjectMapper\Benchmarks\BenchObject; 15 | use Opportus\ObjectMapper\Point\PointFactory; 16 | use PhpBench\Benchmark\Metadata\Annotations\Iterations; 17 | use PhpBench\Benchmark\Metadata\Annotations\Revs; 18 | 19 | /** 20 | * The point factory bench. 21 | * 22 | * @package Opportus\ObjectMapper\Benchmarks\Point 23 | * @author Clément Cazaud 24 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 25 | */ 26 | class PointFactoryBench 27 | { 28 | private $pointFactory; 29 | 30 | public function __construct() 31 | { 32 | $this->pointFactory = new PointFactory(); 33 | } 34 | 35 | /** 36 | * @Revs(1000) 37 | * @Iterations(10) 38 | */ 39 | public function benchCreateObjectPoint() 40 | { 41 | $this->pointFactory 42 | ->createStaticSourcePoint(\sprintf('%s::getA()', BenchObject::class)); 43 | $this->pointFactory 44 | ->createStaticTargetPoint(\sprintf('%s::setB()::$b', BenchObject::class)); 45 | $this->pointFactory 46 | ->createStaticSourcePoint(\sprintf('%s::getA()', BenchObject::class)); 47 | $this->pointFactory 48 | ->createStaticTargetPoint(\sprintf('%s::setB()::$b', BenchObject::class)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /benchmarks/Route/RouteBuilderBench.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Benchmarks\Route; 13 | 14 | use Opportus\ObjectMapper\Benchmarks\BenchObject; 15 | use Opportus\ObjectMapper\Point\PointFactory; 16 | use Opportus\ObjectMapper\Route\RouteBuilder; 17 | use PhpBench\Benchmark\Metadata\Annotations\Iterations; 18 | use PhpBench\Benchmark\Metadata\Annotations\Revs; 19 | 20 | /** 21 | * The route builder bench. 22 | * 23 | * @package Opportus\ObjectMapper\Benchmarks\Map\Route 24 | * @author Clément Cazaud 25 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 26 | */ 27 | class RouteBuilderBench 28 | { 29 | private $routeBuilder; 30 | 31 | public function __construct() 32 | { 33 | $pointFactory = new PointFactory(); 34 | $routeBuilder = new RouteBuilder($pointFactory); 35 | 36 | $this->routeBuilder = $routeBuilder; 37 | } 38 | 39 | /** 40 | * @Revs(1000) 41 | * @Iterations(10) 42 | */ 43 | public function benchBuildRoute() 44 | { 45 | $this->routeBuilder 46 | ->setStaticSourcePoint(\sprintf('%s::getA()', BenchObject::class)) 47 | ->setStaticTargetPoint(\sprintf('%s::__construct()::$a', BenchObject::class)) 48 | ->getRoute(); 49 | 50 | $this->routeBuilder 51 | ->setStaticSourcePoint(\sprintf('%s::getB()', BenchObject::class)) 52 | ->setStaticTargetPoint(\sprintf('%s::setB()::$b', BenchObject::class)) 53 | ->getRoute(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opportus/object-mapper", 3 | "description": "Maps generically source to target objects via extensible strategies and controls.", 4 | "type": "library", 5 | "keywords": ["object mapper", "data mapper", "map", "dto", "pathfinding"], 6 | "homepage": "https://github.com/opportus/object-mapper", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Clément Cazaud", 11 | "email": "clement.cazaud@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.4" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "^9.5", 19 | "phpbench/phpbench": "^1.0", 20 | "friendsofphp/php-cs-fixer": "^3.0" 21 | }, 22 | "autoload": { 23 | "classmap": [ 24 | "src/" 25 | ], 26 | "psr-4": { 27 | "Opportus\\ObjectMapper\\": "src/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "Opportus\\ObjectMapper\\Tests\\": "tests/", 33 | "Opportus\\ObjectMapper\\Benchmarks\\": "benchmarks/" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /doc/object-point-hierarchy-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opportus/object-mapper/d308b8811c7f9ce8f54c055d4b732995461bcf30/doc/object-point-hierarchy-graph.png -------------------------------------------------------------------------------- /phpbench.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "runner.bootstrap": "vendor/autoload.php", 3 | "runner.path": "benchmarks" 4 | } 5 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src 7 | 8 | 9 | 10 | 11 | tests 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Exception/CheckPointSeizingException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Exception; 13 | 14 | /** 15 | * The check point seizing exception. 16 | * 17 | * @package Opportus\ObjectMapper\Exception 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | class CheckPointSeizingException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Exception; 13 | 14 | use Exception as BaseException; 15 | 16 | /** 17 | * The exception. 18 | * 19 | * @package Opportus\ObjectMapper\Exception 20 | * @author Clément Cazaud 21 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 22 | */ 23 | class Exception extends BaseException 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Exception; 13 | 14 | use Throwable; 15 | 16 | /** 17 | * The invalid argument exception. 18 | * 19 | * @package Opportus\ObjectMapper\Exception 20 | * @author Clément Cazaud 21 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 22 | */ 23 | class InvalidArgumentException extends Exception 24 | { 25 | /** 26 | * @var int $argument 27 | */ 28 | private $argument; 29 | 30 | /** 31 | * Constructs the invalid argument exception. 32 | * 33 | * @param int $argument 34 | * @param string $message 35 | * @param int $code 36 | * @param null|Throwable $previous 37 | */ 38 | public function __construct( 39 | int $argument, 40 | string $message = '', 41 | int $code = 0, 42 | Throwable $previous = null 43 | ) { 44 | $this->argument = $argument; 45 | 46 | $message = \sprintf( 47 | 'Argument %d is invalid. %s', 48 | $this->argument, 49 | $message 50 | ); 51 | 52 | parent::__construct($message, $code, $previous); 53 | } 54 | 55 | /** 56 | * Gets the argument. 57 | * 58 | * @return int 59 | */ 60 | public function getArgument(): int 61 | { 62 | return $this->argument; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Exception/InvalidOperationException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Exception; 13 | 14 | use Throwable; 15 | 16 | /** 17 | * The invalid operation exception. 18 | * 19 | * @package Opportus\ObjectMapper\Exception 20 | * @author Clément Cazaud 21 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 22 | */ 23 | class InvalidOperationException extends Exception 24 | { 25 | /** 26 | * Constructs the invalid operation exception. 27 | * 28 | * @param string $message 29 | * @param int $code 30 | * @param null|Throwable $previous 31 | */ 32 | public function __construct( 33 | string $message = '', 34 | int $code = 0, 35 | Throwable $previous = null 36 | ) { 37 | $message = \sprintf( 38 | 'Operation is invalid. %s', 39 | $message 40 | ); 41 | 42 | parent::__construct($message, $code, $previous); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Exception/InvalidTargetOperationException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Exception; 13 | 14 | use Throwable; 15 | 16 | /** 17 | * The invalid object operation exception. 18 | * 19 | * @package Opportus\ObjectMapper\Exception 20 | * @author Clément Cazaud 21 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 22 | */ 23 | class InvalidTargetOperationException extends InvalidOperationException 24 | { 25 | /** 26 | * Constructs the invalid object operation exception. 27 | * 28 | * @param string $message 29 | * @param int $code 30 | * @param null|Throwable $previous 31 | */ 32 | public function __construct( 33 | string $message = '', 34 | int $code = 0, 35 | Throwable $previous = null 36 | ) { 37 | $message = \sprintf( 38 | 'Object operation is invalid. %s', 39 | $message 40 | ); 41 | 42 | parent::__construct($message, $code, $previous); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ImmutableCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper; 13 | 14 | use ArrayAccess; 15 | use ArrayIterator; 16 | use Countable; 17 | use IteratorAggregate; 18 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 19 | 20 | /** 21 | * The immutable collection. 22 | * 23 | * @package Opportus\ObjectMapper 24 | * @author Clément Cazaud 25 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 26 | */ 27 | abstract class ImmutableCollection implements 28 | ArrayAccess, 29 | Countable, 30 | IteratorAggregate 31 | { 32 | /** 33 | * @var array $items 34 | */ 35 | private $items; 36 | 37 | /** 38 | * Constructs the immutable collection. 39 | * 40 | * @param array $items 41 | */ 42 | public function __construct(array $items) 43 | { 44 | $this->items = $items; 45 | } 46 | 47 | /** 48 | * Returns the collection as array. 49 | * 50 | * @return array 51 | */ 52 | public function toArray(): array 53 | { 54 | return $this->items; 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function getIterator() 61 | { 62 | return new ArrayIterator($this->items); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function count() 69 | { 70 | return \count($this->items); 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function offsetExists($offset) 77 | { 78 | return isset($this->items[$offset]); 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function offsetGet($offset) 85 | { 86 | return $this->items[$offset]; 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | * 92 | * @throws InvalidOperationException 93 | */ 94 | public function offsetSet($offset, $value) 95 | { 96 | throw new InvalidOperationException( 97 | 'Attempting to set an element of an immutable array.' 98 | ); 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | * 104 | * @throws InvalidOperationException 105 | */ 106 | public function offsetUnset($offset) 107 | { 108 | throw new InvalidOperationException( 109 | 'Attempting to set an element of an immutable array.' 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Map/Map.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Map; 13 | 14 | use Exception; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\PathFinder\PathFinderCollection; 17 | use Opportus\ObjectMapper\Route\RouteCollection; 18 | use Opportus\ObjectMapper\SourceInterface; 19 | use Opportus\ObjectMapper\TargetInterface; 20 | 21 | /** 22 | * The map. 23 | * 24 | * @package Opportus\ObjectMapper\Map 25 | * @author Clément Cazaud 26 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 27 | */ 28 | class Map implements MapInterface 29 | { 30 | /** 31 | * @var PathFinderCollection $pathFinders 32 | */ 33 | private $pathFinders; 34 | 35 | /** 36 | * @var RouteCollection $routes 37 | */ 38 | private $routes; 39 | 40 | /** 41 | * Constructs the map. 42 | * 43 | * @param null|PathFinderCollection $pathFinders 44 | * @param null|RouteCollection $routes 45 | */ 46 | public function __construct( 47 | ?PathFinderCollection $pathFinders = null, 48 | ?RouteCollection $routes = null 49 | ) { 50 | $this->pathFinders = $pathFinders ?? new PathFinderCollection(); 51 | $this->routes = $routes ?? new RouteCollection(); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getRoutes(SourceInterface $source, TargetInterface $target): RouteCollection 58 | { 59 | $routes = []; 60 | 61 | foreach ($this->pathFinders as $pathFinder) { 62 | try { 63 | $pathFinderRoutes = $pathFinder->getRoutes($source, $target); 64 | } catch (Exception $exception) { 65 | throw new InvalidOperationException('', 0, $exception); 66 | } 67 | 68 | foreach ($pathFinderRoutes as $pathFinderRoute) { 69 | $routes[] = $pathFinderRoute; 70 | } 71 | } 72 | 73 | foreach ($this->routes as $mapRoute) { 74 | $sourceFqn = $mapRoute->getSourcePoint()->getSourceFqn(); 75 | $targetFqn = $mapRoute->getTargetPoint()->getTargetFqn(); 76 | 77 | if ($sourceFqn !== $source->getFqn() || $targetFqn !== $target->getFqn()) { 78 | continue; 79 | } 80 | 81 | $routes[] = $mapRoute; 82 | } 83 | 84 | $routesToKeep = []; 85 | 86 | foreach ($routes as $key => $route) { 87 | $routesToKeep[$route->getTargetPoint()->getFqn()] = $key; 88 | } 89 | 90 | foreach ($routes as $key => $route) { 91 | if (false === \in_array($key, $routesToKeep)) { 92 | unset($routes[$key]); 93 | } 94 | } 95 | 96 | return new RouteCollection($routes); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Map/MapBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Map; 13 | 14 | use Opportus\ObjectMapper\PathFinder\DynamicSourceToStaticTargetPathFinder; 15 | use Opportus\ObjectMapper\PathFinder\StaticSourceToDynamicTargetPathFinder; 16 | use Opportus\ObjectMapper\PathFinder\PathFinderCollection; 17 | use Opportus\ObjectMapper\PathFinder\PathFinderInterface; 18 | use Opportus\ObjectMapper\PathFinder\StaticPathFinder; 19 | use Opportus\ObjectMapper\Route\RouteBuilderInterface; 20 | use Opportus\ObjectMapper\Route\RouteCollection; 21 | use Opportus\ObjectMapper\Route\RouteInterface; 22 | 23 | /** 24 | * The map builder. 25 | * 26 | * @package Opportus\ObjectMapper\Map 27 | * @author Clément Cazaud 28 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 29 | */ 30 | class MapBuilder implements MapBuilderInterface 31 | { 32 | /** 33 | * @var RouteBuilderInterface $routeBuilder 34 | */ 35 | private $routeBuilder; 36 | 37 | /** 38 | * @var RouteCollection $routes 39 | */ 40 | private $routes; 41 | 42 | /** 43 | * @var PathFinderCollection $pathFinders 44 | */ 45 | private $pathFinders; 46 | 47 | /** 48 | * Constructs the map builder. 49 | * 50 | * @param RouteBuilderInterface $routeBuilder 51 | * @param null|RouteCollection $routes 52 | * @param null|PathFinderCollection $pathFinders 53 | */ 54 | public function __construct( 55 | RouteBuilderInterface $routeBuilder, 56 | ?RouteCollection $routes = null, 57 | ?PathFinderCollection $pathFinders = null 58 | ) { 59 | $this->routeBuilder = $routeBuilder; 60 | $this->routes = $routes ?? new RouteCollection(); 61 | $this->pathFinders = $pathFinders ?? new PathFinderCollection(); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function getRouteBuilder(): RouteBuilderInterface 68 | { 69 | return $this->routeBuilder->setMapBuilder($this); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function addRoute(RouteInterface $route): MapBuilderInterface 76 | { 77 | $routes = $this->routes->toArray(); 78 | 79 | $routes[] = $route; 80 | 81 | return new self( 82 | $this->routeBuilder, 83 | new RouteCollection($routes), 84 | $this->pathFinders 85 | ); 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function addRoutes(RouteCollection $routes): MapBuilderInterface 92 | { 93 | $routes = $routes->toArray() + $this->routes->toArray(); 94 | 95 | return new self( 96 | $this->routeBuilder, 97 | new RouteCollection($routes), 98 | $this->pathFinders 99 | ); 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function addPathFinder( 106 | PathFinderInterface $pathFinder, 107 | ?int $priority = null 108 | ): MapBuilderInterface { 109 | $pathFinders = $this->pathFinders->toArray(); 110 | 111 | if (null === $priority) { 112 | $pathFinders[] = $pathFinder; 113 | } else { 114 | $pathFinders[$priority] = $pathFinder; 115 | } 116 | 117 | return new self( 118 | $this->routeBuilder, 119 | $this->routes, 120 | new PathFinderCollection($pathFinders) 121 | ); 122 | } 123 | 124 | /** 125 | * {@inheritdoc} 126 | */ 127 | public function addStaticPathFinder( 128 | ?int $priority = null, 129 | bool $recursiveMode = true 130 | ): MapBuilderInterface { 131 | return $this->addPathFinder( 132 | new StaticPathFinder($this->routeBuilder, $recursiveMode), 133 | $priority 134 | ); 135 | } 136 | 137 | /** 138 | * {@inheritdoc} 139 | */ 140 | public function addStaticSourceToDynamicTargetPathFinder( 141 | ?int $priority = null, 142 | bool $recursiveMode = true 143 | ): MapBuilderInterface { 144 | return $this->addPathFinder( 145 | new StaticSourceToDynamicTargetPathFinder($this->routeBuilder, $recursiveMode), 146 | $priority 147 | ); 148 | } 149 | 150 | /** 151 | * {@inheritdoc} 152 | */ 153 | public function addDynamicSourceToStaticTargetPathFinder( 154 | ?int $priority = null, 155 | bool $recursiveMode = true 156 | ): MapBuilderInterface { 157 | return $this->addPathFinder( 158 | new DynamicSourceToStaticTargetPathFinder($this->routeBuilder, $recursiveMode), 159 | $priority 160 | ); 161 | } 162 | 163 | /** 164 | * {@inheritdoc} 165 | */ 166 | public function getMap(): MapInterface 167 | { 168 | return new Map($this->pathFinders, $this->routes); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Map/MapBuilderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Map; 13 | 14 | use Opportus\ObjectMapper\PathFinder\DynamicSourceToStaticTargetPathFinder; 15 | use Opportus\ObjectMapper\PathFinder\PathFinderInterface; 16 | use Opportus\ObjectMapper\PathFinder\StaticPathFinder; 17 | use Opportus\ObjectMapper\PathFinder\StaticSourceToDynamicTargetPathFinder; 18 | use Opportus\ObjectMapper\Route\RouteBuilderInterface; 19 | use Opportus\ObjectMapper\Route\RouteCollection; 20 | use Opportus\ObjectMapper\Route\RouteInterface; 21 | 22 | /** 23 | * The map builder interface. 24 | * 25 | * @package Opportus\ObjectMapper\Map 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | interface MapBuilderInterface 30 | { 31 | /** 32 | * Gets the route builder. 33 | * 34 | * @return RouteBuilderInterface A `RouteBuilderInterface` instance 35 | */ 36 | public function getRouteBuilder(): RouteBuilderInterface; 37 | 38 | /** 39 | * Adds a route to the map being built. 40 | * 41 | * @param RouteInterface $route A route to add to the map being built 42 | * @return MapBuilderInterface A map builder 43 | */ 44 | public function addRoute(RouteInterface $route): MapBuilderInterface; 45 | 46 | /** 47 | * Adds the routes to the map being built. 48 | * 49 | * @param RouteCollection $routes A collection of routes to add to the 50 | * map being built 51 | * @return MapBuilderInterface A map builder 52 | */ 53 | public function addRoutes(RouteCollection $routes): MapBuilderInterface; 54 | 55 | /** 56 | * Adds a path finder to the map being built. 57 | * 58 | * @param PathFinderInterface $pathFinder A path finder to add to the map 59 | * being built 60 | * @param null|int $priority The priority of the path finder 61 | * on the map being built 62 | * @return MapBuilderInterface A map builder 63 | */ 64 | public function addPathFinder( 65 | PathFinderInterface $pathFinder, 66 | ?int $priority = null 67 | ): MapBuilderInterface; 68 | 69 | /** 70 | * Adds a static path finder to the map being built. 71 | * 72 | * @param null|int $priority The priority of the path 73 | * finder on the map being built 74 | * @param bool $recursiveMode Whether to recurse mapping on 75 | * aggregate's objects 76 | * @return MapBuilderInterface A map builder 77 | * @see StaticPathFinder 78 | */ 79 | public function addStaticPathFinder( 80 | ?int $priority = null, 81 | bool $recursiveMode = true 82 | ): MapBuilderInterface; 83 | 84 | /** 85 | * Adds a static source to dynamic target path finder to the map being 86 | * built. 87 | * 88 | * @param null|int $priority The priority of the path 89 | * finder on the map being built 90 | * @param bool $recursiveMode Whether to recurse mapping on 91 | * aggregate's objects 92 | * @return MapBuilderInterface A map builder 93 | * @see StaticSourceToDynamicTargetPathFinder 94 | */ 95 | public function addStaticSourceToDynamicTargetPathFinder( 96 | ?int $priority = null, 97 | bool $recursiveMode = true 98 | ): MapBuilderInterface; 99 | 100 | /** 101 | * Adds a dynamic source to static target path finder to the map being 102 | * built. 103 | * 104 | * @param null|int $priority The priority of the path 105 | * finder on the map being built 106 | * @param bool $recursiveMode Whether to recurse mapping on 107 | * aggregate's objects 108 | * @return MapBuilderInterface A map builder 109 | * @see DynamicSourceToStaticTargetPathFinder 110 | */ 111 | public function addDynamicSourceToStaticTargetPathFinder( 112 | ?int $priority = null, 113 | bool $recursiveMode = true 114 | ): MapBuilderInterface; 115 | 116 | /** 117 | * Gets the built map. 118 | * 119 | * @return MapInterface The map built 120 | */ 121 | public function getMap(): MapInterface; 122 | } 123 | -------------------------------------------------------------------------------- /src/Map/MapInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Map; 13 | 14 | use Opportus\ObjectMapper\Route\RouteCollection; 15 | use Opportus\ObjectMapper\SourceInterface; 16 | use Opportus\ObjectMapper\TargetInterface; 17 | 18 | /** 19 | * The map interface. 20 | * 21 | * @package Opportus\ObjectMapper\Map 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | interface MapInterface 26 | { 27 | /** 28 | * Gets the routes connecting the source points with the target points. 29 | * 30 | * @param SourceInterface $source A source to map data from 31 | * @param TargetInterface $target A target to map data to 32 | * @return RouteCollection A route collection connecting the 33 | * source points with the target points 34 | * @throws InvalidOperationException If the operation fails for any reason 35 | */ 36 | public function getRoutes(SourceInterface $source, TargetInterface $target): RouteCollection; 37 | } 38 | -------------------------------------------------------------------------------- /src/ObjectMapper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Map\MapBuilderInterface; 17 | use Opportus\ObjectMapper\Map\MapInterface; 18 | 19 | /** 20 | * The object mapper. 21 | * 22 | * @package Opportus\ObjectMapper 23 | * @author Clément Cazaud 24 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 25 | */ 26 | class ObjectMapper implements ObjectMapperInterface 27 | { 28 | use ObjectMapperTrait; 29 | 30 | /** 31 | * @var MapBuilderInterface $mapBuilder 32 | */ 33 | private $mapBuilder; 34 | 35 | /** 36 | * Constructs the object mapper. 37 | * 38 | * @param MapBuilderInterface $mapBuilder 39 | */ 40 | public function __construct(MapBuilderInterface $mapBuilder) 41 | { 42 | $this->mapBuilder = $mapBuilder; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function map( 49 | object $source, 50 | $target, 51 | ?MapInterface $map = null 52 | ): ?object { 53 | if (!$source instanceof SourceInterface) { 54 | $source = new Source($source); 55 | } 56 | 57 | if (!$target instanceof TargetInterface) { 58 | try { 59 | $target = new Target($target); 60 | } catch (InvalidArgumentException $exception) { 61 | throw new InvalidArgumentException(2, '', 0, $exception); 62 | } 63 | } 64 | 65 | $map = $map ?? $this->mapBuilder 66 | ->addStaticPathFinder() 67 | ->getMap(); 68 | 69 | return $this->mapSourceToTarget($source, $target, $map); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ObjectMapperInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Map\MapInterface; 17 | 18 | /** 19 | * The object mapper interface. 20 | * 21 | * @package Opportus\ObjectMapper 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | interface ObjectMapperInterface 26 | { 27 | /** 28 | * Transfers data of the source to the target following routes on the map. 29 | * 30 | * @param object $source The object to map data from 31 | * @param object|string $target The object (or the Fully 32 | * Qualified Name of the class to 33 | * instantiate and) to map data to 34 | * @param null|MapInterface $map An instance of `MapInterface` 35 | * or NULL 36 | * @return null|object The instantiated and/or updated 37 | * target or NULL if the there is 38 | * no route mapping source and 39 | * target 40 | * @throws InvalidArgumentException If source or target arguments 41 | * are invalid 42 | * @throws InvalidOperationException If the operation fails for any 43 | * reason 44 | */ 45 | public function map( 46 | object $source, 47 | $target, 48 | ?MapInterface $map = null 49 | ): ?object; 50 | } 51 | -------------------------------------------------------------------------------- /src/ObjectMapperTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper; 13 | 14 | use Opportus\ObjectMapper\Exception\CheckPointSeizingException; 15 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 16 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 17 | use Opportus\ObjectMapper\Map\MapInterface; 18 | 19 | /** 20 | * The object mapper trait. 21 | * 22 | * @package Opportus\ObjectMapper 23 | * @author Clément Cazaud 24 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 25 | */ 26 | trait ObjectMapperTrait 27 | { 28 | /** 29 | * Transfers data of the source to the target following routes on the map. 30 | * 31 | * @param SourceInterface $source The source to map data from 32 | * @param TargetInterface $target The target to map data to 33 | * @param MapInterface $map An instance of map 34 | * @return null|object The instantiated and/or updated 35 | * target or NULL if the there is 36 | * no route mapping source and 37 | * target 38 | * @throws InvalidOperationException If the operation fails for any 39 | * reason 40 | */ 41 | private function mapSourceToTarget( 42 | SourceInterface $source, 43 | TargetInterface $target, 44 | MapInterface $map 45 | ): ?object { 46 | try { 47 | $routes = $map->getRoutes($source, $target); 48 | } catch (InvalidOperationException $exception) { 49 | throw new InvalidOperationException('', 0, $exception); 50 | } 51 | 52 | if (0 === \count($routes)) { 53 | return null; 54 | } 55 | 56 | foreach ($routes as $route) { 57 | $sourcePoint = $route->getSourcePoint(); 58 | $targetPoint = $route->getTargetPoint(); 59 | $checkPoints = $route->getCheckPoints(); 60 | 61 | try { 62 | $checkPointSubject = $source->getPointValue($sourcePoint); 63 | } catch (InvalidArgumentException | InvalidOperationException $exception) { 64 | throw new InvalidOperationException('', 0, $exception); 65 | } 66 | 67 | foreach ($checkPoints as $checkPoint) { 68 | try { 69 | $checkPointSubject = $checkPoint->control( 70 | $checkPointSubject, 71 | $route, 72 | $map, 73 | $source, 74 | $target 75 | ); 76 | } catch (CheckPointSeizingException $exception) { 77 | continue 2; 78 | } catch (InvalidOperationException $exception) { 79 | throw new InvalidOperationException('', 0, $exception); 80 | } 81 | } 82 | 83 | try { 84 | $target->setPointValue($targetPoint, $checkPointSubject); 85 | } catch (InvalidArgumentException $exception) { 86 | throw new InvalidOperationException('', 0, $exception); 87 | } 88 | } 89 | 90 | $target->operate(); 91 | 92 | return $target->getInstance(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/PathFinder/DynamicSourceToStaticTargetPathFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\PathFinder; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Route\RouteInterface; 16 | use Opportus\ObjectMapper\SourceInterface; 17 | use Opportus\ObjectMapper\TargetInterface; 18 | use ReflectionParameter; 19 | use ReflectionProperty; 20 | 21 | /** 22 | * The default dynamic source to static target path finder. 23 | * 24 | * This behavior consists of guessing the dynamic point of the source to 25 | * connect to each static point of the target following the rules below. 26 | * 27 | * A target point can be: 28 | * 29 | * - A public property (`PropertyStaticTargetPoint`) 30 | * - A parameter of a public setter or a public constructor 31 | * (`ParameterStaticTargetPoint`) 32 | * 33 | * The connectable source point can be: 34 | * 35 | * - A dynamically defined property having for name the same as the target 36 | * point (`PropertyDynamicSourcePoint`) 37 | * 38 | * @package Opportus\ObjectMapper\PathFinder 39 | * @author Clément Cazaud 40 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 41 | */ 42 | class DynamicSourceToStaticTargetPathFinder extends StaticPathFinder 43 | { 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function getReferencePointRoute( 48 | SourceInterface $source, 49 | TargetInterface $target, 50 | $referencePoint 51 | ): ?RouteInterface { 52 | if (false === \is_object($referencePoint) 53 | || ( 54 | !$referencePoint instanceof ReflectionProperty 55 | && !$referencePoint instanceof ReflectionParameter 56 | ) 57 | ) { 58 | $message = \sprintf( 59 | 'The argument must be an object of type % or %s, got an argument of type %s.', 60 | ReflectionProperty::class, 61 | ReflectionParameter::class, 62 | \is_object($referencePoint) ? 63 | \get_class($referencePoint) : \gettype($referencePoint) 64 | ); 65 | 66 | throw new InvalidArgumentException(3, $message); 67 | } 68 | 69 | $targetPointReflection = $referencePoint; 70 | $sourceClassReflection = $source->getClassReflection(); 71 | $sourceObjectReflection = $source->getObjectReflection(); 72 | 73 | if ($sourceClassReflection 74 | ->hasProperty($targetPointReflection->getName()) || 75 | !$sourceObjectReflection 76 | ->hasProperty($targetPointReflection->getName()) 77 | ) { 78 | return null; 79 | } 80 | 81 | $sourcePointFqn = \sprintf( 82 | '%s::$%s', 83 | $sourceClassReflection->getName(), 84 | $targetPointReflection->getName() 85 | ); 86 | 87 | $targetPointFqn = $this 88 | ->getPointFqnFromReflection($targetPointReflection); 89 | 90 | return $this->routeBuilder 91 | ->setDynamicSourcePoint($sourcePointFqn) 92 | ->setStaticTargetPoint($targetPointFqn) 93 | ->getRoute(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/PathFinder/PathFinderCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\PathFinder; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\ImmutableCollection; 16 | 17 | /** 18 | * The path finder collection. 19 | * 20 | * @package Opportus\ObjectMapper\PathFinder 21 | * @author Clément Cazaud 22 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 23 | */ 24 | class PathFinderCollection extends ImmutableCollection 25 | { 26 | /** 27 | * Constructs the path finder collection. 28 | * 29 | * @param PathFinderInterface[] $pathFinders 30 | * @throws InvalidArgumentException 31 | */ 32 | public function __construct(array $pathFinders = []) 33 | { 34 | foreach ($pathFinders as $index => $pathFinder) { 35 | if (!\is_object($pathFinder) || !$pathFinder instanceof PathFinderInterface) { 36 | $message = \sprintf( 37 | 'The array must contain exclusively elements of type %s, got an element of type %s.', 38 | PathFinderInterface::class, 39 | \is_object($pathFinder) ? \get_class($pathFinder) : \gettype($pathFinder) 40 | ); 41 | 42 | throw new InvalidArgumentException(1, $message); 43 | } 44 | 45 | if (false === \is_int($index)) { 46 | $message = \sprintf( 47 | 'The array must contain exclusively indexes of type integer, got an index of type %s.', 48 | \gettype($index) 49 | ); 50 | 51 | throw new InvalidArgumentException(1, $message); 52 | } 53 | } 54 | 55 | \ksort($pathFinders, \SORT_NUMERIC); 56 | 57 | parent::__construct($pathFinders); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/PathFinder/PathFinderInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\PathFinder; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 15 | use Opportus\ObjectMapper\Route\RouteCollection; 16 | use Opportus\ObjectMapper\SourceInterface; 17 | use Opportus\ObjectMapper\TargetInterface; 18 | 19 | /** 20 | * The path finder interface. 21 | * 22 | * @package Opportus\ObjectMapper\PathFinder 23 | * @author Clément Cazaud 24 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 25 | */ 26 | interface PathFinderInterface 27 | { 28 | /** 29 | * Gets the routes connecting the source points with the target points. 30 | * 31 | * @param SourceInterface $source A source to map data from 32 | * @param TargetInterface $target A target to map data to 33 | * @return RouteCollection A route collection connecting the 34 | * source points with the target points 35 | * @throws InvalidOperationException If the operation fails for any reason 36 | */ 37 | public function getRoutes(SourceInterface $source, TargetInterface $target): RouteCollection; 38 | } 39 | -------------------------------------------------------------------------------- /src/PathFinder/StaticSourceToDynamicTargetPathFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\PathFinder; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Route\RouteInterface; 16 | use Opportus\ObjectMapper\SourceInterface; 17 | use Opportus\ObjectMapper\TargetInterface; 18 | use ReflectionMethod; 19 | use ReflectionParameter; 20 | use ReflectionProperty; 21 | 22 | /** 23 | * The default static source to dynamic target path finder. 24 | * 25 | * This behavior consists of guessing the dynamic point of the target to 26 | * connect to each static point of the source following the rules below. 27 | * 28 | * A source point can be: 29 | * 30 | * - A public property (`PropertyStaticSourcePoint`) 31 | * - A public getter (`MethodStaticSourcePoint`) 32 | * 33 | * The connectable target point can be: 34 | * 35 | * - A statically non-existing property having for name the same as the property 36 | * source point or `lcfirst(substr($getterSourcePoint, 3))` 37 | * (`PropertyDynamicSourcePoint`) 38 | * 39 | * @package Opportus\ObjectMapper\PathFinder 40 | * @author Clément Cazaud 41 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 42 | */ 43 | class StaticSourceToDynamicTargetPathFinder extends PathFinder 44 | { 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | protected function getReferencePoints( 49 | SourceInterface $source, 50 | TargetInterface $target 51 | ): array { 52 | $sourceClassReflection = $source->getClassReflection(); 53 | 54 | $propertyBlackList = []; 55 | $sourcePointReflections = []; 56 | 57 | foreach ( 58 | $sourceClassReflection->getMethods(ReflectionMethod::IS_PUBLIC) as 59 | $methodReflection 60 | ) { 61 | if (\strpos($methodReflection->getName(), 'get') !== 0 && 62 | \strpos($methodReflection->getName(), 'is') !== 0 63 | ) { 64 | continue; 65 | } 66 | 67 | if ($methodReflection->getNumberOfRequiredParameters() !== 0) { 68 | continue; 69 | } 70 | 71 | if (\strpos($methodReflection->getName(), 'get') === 0) { 72 | $propertyBlackList[] = \lcfirst(\substr($methodReflection->getName(), 3)); 73 | } elseif (\strpos($methodReflection->getName(), 'is') === 0) { 74 | $propertyBlackList[] = \lcfirst(\substr($methodReflection->getName(), 2)); 75 | } 76 | 77 | $sourcePointReflections[] = $methodReflection; 78 | } 79 | 80 | foreach ( 81 | $sourceClassReflection->getProperties(ReflectionProperty::IS_PUBLIC) as 82 | $propertyReflection 83 | ) { 84 | if (\in_array($propertyReflection->getName(), $propertyBlackList)) { 85 | continue; 86 | } 87 | 88 | $sourcePointReflections[] = $propertyReflection; 89 | } 90 | 91 | return $sourcePointReflections; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | protected function getReferencePointRoute( 98 | SourceInterface $source, 99 | TargetInterface $target, 100 | $referencePoint 101 | ): ?RouteInterface { 102 | if (false === \is_object($referencePoint) 103 | || ( 104 | !$referencePoint instanceof ReflectionProperty 105 | && !$referencePoint instanceof ReflectionMethod 106 | ) 107 | ) { 108 | $message = \sprintf( 109 | 'The argument must be an object of type % or %s, got an argument of type %s.', 110 | ReflectionProperty::class, 111 | ReflectionParameter::class, 112 | \is_object($referencePoint) ? 113 | \get_class($referencePoint) : \gettype($referencePoint) 114 | ); 115 | 116 | throw new InvalidArgumentException(3, $message); 117 | } 118 | 119 | $sourcePointReflection = $referencePoint; 120 | $targetClassReflection = $target->getClassReflection(); 121 | 122 | if ($sourcePointReflection instanceof ReflectionProperty) { 123 | $targetPointName = $sourcePointReflection->getName(); 124 | } elseif ($sourcePointReflection instanceof ReflectionMethod) { 125 | if (\strpos($sourcePointReflection->getName(), 'get') === 0) { 126 | $targetPointName = \lcfirst(\substr( 127 | $sourcePointReflection->getName(), 128 | 3 129 | )); 130 | } elseif (\strpos($sourcePointReflection->getName(), 'is') === 0) { 131 | $targetPointName = \lcfirst(\substr( 132 | $sourcePointReflection->getName(), 133 | 2 134 | )); 135 | } 136 | } 137 | 138 | if ($targetClassReflection->hasProperty($targetPointName)) { 139 | return null; 140 | } 141 | 142 | $sourcePointFqn = $this 143 | ->getPointFqnFromReflection($sourcePointReflection); 144 | 145 | $targetPointFqn = \sprintf( 146 | '%s::$%s', 147 | $targetClassReflection->getName(), 148 | $targetPointName 149 | ); 150 | 151 | return $this->routeBuilder 152 | ->setStaticSourcePoint($sourcePointFqn) 153 | ->setDynamicTargetPoint($targetPointFqn) 154 | ->getRoute(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Point/CheckPointCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\ImmutableCollection; 16 | 17 | /** 18 | * The check point collection. 19 | * 20 | * @package Opportus\ObjectMapper\Point 21 | * @author Clément Cazaud 22 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 23 | */ 24 | class CheckPointCollection extends ImmutableCollection 25 | { 26 | /** 27 | * Constructs the check point collection. 28 | * 29 | * @param CheckPointInterface[] $checkPoints 30 | * @throws InvalidArgumentException 31 | */ 32 | public function __construct(array $checkPoints = []) 33 | { 34 | foreach ($checkPoints as $index=> $checkPoint) { 35 | if ( 36 | !\is_object($checkPoint) || 37 | !$checkPoint instanceof CheckPointInterface 38 | ) { 39 | $message = \sprintf( 40 | 'The array must contain exclusively elements of type %s, got an element of type %s.', 41 | CheckPointInterface::class, 42 | \is_object($checkPoint) ? 43 | \get_class($checkPoint) : \gettype($checkPoint) 44 | ); 45 | 46 | throw new InvalidArgumentException(1, $message); 47 | } 48 | 49 | if (false === \is_int($index)) { 50 | $message = \sprintf( 51 | 'The array must contain exclusively indexes of type integer, got an index of type %s.', 52 | \gettype($index) 53 | ); 54 | 55 | throw new InvalidArgumentException(1, $message); 56 | } 57 | } 58 | 59 | \ksort($checkPoints, \SORT_NUMERIC); 60 | 61 | parent::__construct($checkPoints); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Point/CheckPointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\CheckPointSeizingException; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Map\MapInterface; 17 | use Opportus\ObjectMapper\Route\RouteInterface; 18 | use Opportus\ObjectMapper\SourceInterface; 19 | use Opportus\ObjectMapper\TargetInterface; 20 | 21 | /** 22 | * The check point interface. 23 | * 24 | * @package Opportus\ObjectMapper\Point 25 | * @author Clément Cazaud 26 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 27 | */ 28 | interface CheckPointInterface 29 | { 30 | /** 31 | * Controls the subject. 32 | * 33 | * @param mixed $subject The value going to be 34 | * assigned to the target point 35 | * @param RouteInterface $route The route which the check 36 | * point is currently on 37 | * @param MapInterface $map The map which the route is 38 | * currently on 39 | * @param SourceInterface $source The source which the subject 40 | * comes from 41 | * @param TargetInterface $target The target which the subject 42 | * goes to 43 | * @return mixed The value going to be 44 | * assigned to the target point 45 | * @throws CheckPointSeizingException If the subject must not be 46 | * reach its target point 47 | * @throws InvalidOperationException If the operation fails for 48 | * any reason 49 | */ 50 | public function control( 51 | $subject, 52 | RouteInterface $route, 53 | MapInterface $map, 54 | SourceInterface $source, 55 | TargetInterface $target 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/Point/DynamicPointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The dynamic point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface DynamicPointInterface extends ObjectPointInterface 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Point/DynamicSourcePointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The dynamic source point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface DynamicSourcePointInterface extends SourcePointInterface, DynamicPointInterface 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Point/DynamicTargetPointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The dynamic target point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface DynamicTargetPointInterface extends TargetPointInterface, DynamicPointInterface 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Point/MethodDynamicSourcePoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use ReflectionClass; 16 | use ReflectionException; 17 | 18 | /** 19 | * The method dynamic source point. 20 | * 21 | * @package Opportus\ObjectMapper\Point 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class MethodDynamicSourcePoint extends SourcePoint implements DynamicSourcePointInterface 26 | { 27 | private const FQN_REGEX_PATTERN = '/^~?([A-Za-z0-9\\\_]+)::([A-Za-z0-9_]+)\(\)$/'; 28 | 29 | /** 30 | * Constructs the method dynamic source point. 31 | * 32 | * @param string $fqn 33 | * @throws InvalidArgumentException 34 | */ 35 | public function __construct(string $fqn) 36 | { 37 | if (!\preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 38 | $message = \sprintf( 39 | '%s is not a method dynamic source point as FQN of such is expected to have the following syntax: %s.', 40 | $fqn, 41 | self::FQN_REGEX_PATTERN 42 | ); 43 | 44 | throw new InvalidArgumentException(1, $message); 45 | } 46 | 47 | [$matchedFqn, $matchedSourceFqn, $matchedName] = $matches; 48 | 49 | try { 50 | $sourceClassReflection = new ReflectionClass($matchedSourceFqn); 51 | } catch (ReflectionException $exception) { 52 | $message = \sprintf( 53 | '%s is not a method dynamic source point. %s.', 54 | $fqn, 55 | $exception->getMessage() 56 | ); 57 | 58 | throw new InvalidArgumentException(1, $message); 59 | } 60 | 61 | if ($sourceClassReflection->hasMethod($matchedName)) { 62 | $message = \sprintf( 63 | '%s is not a method dynamic source point since the point is statically defined.', 64 | $fqn 65 | ); 66 | 67 | throw new InvalidArgumentException(1, $message); 68 | } 69 | 70 | $this->fqn = \sprintf('~%s', \ltrim($matchedFqn, '~')); 71 | $this->sourceFqn = $matchedSourceFqn; 72 | $this->name = $matchedName; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public static function getFqnRegexPattern(): string 79 | { 80 | return self::FQN_REGEX_PATTERN; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Point/MethodParameterDynamicTargetPoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use ReflectionClass; 16 | use ReflectionException; 17 | 18 | /** 19 | * The method parameter dynamic target point. 20 | * 21 | * @package Opportus\ObjectMapper\Point 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class MethodParameterDynamicTargetPoint extends TargetPoint implements DynamicTargetPointInterface 26 | { 27 | private const FQN_REGEX_PATTERN = '/^~?([A-Za-z0-9\\\_]+)::([A-Za-z0-9_]+)\(\)::\$([A-Za-z0-9_]+)$/'; 28 | 29 | /** 30 | * @var string $methodName 31 | */ 32 | private $methodName; 33 | 34 | /** 35 | * Constructs the method parameter dynamic target point. 36 | * 37 | * @param string $fqn 38 | * @throws InvalidArgumentException 39 | */ 40 | public function __construct(string $fqn) 41 | { 42 | if (!\preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 43 | $message = \sprintf( 44 | '%s is not a method parameter dynamic target point as FQN of such is expected to have the following syntax: %s.', 45 | $fqn, 46 | self::FQN_REGEX_PATTERN 47 | ); 48 | 49 | throw new InvalidArgumentException(1, $message); 50 | } 51 | 52 | [ 53 | $matchedFqn, 54 | $matchedTargetFqn, 55 | $matchedMethodName, 56 | $matchedName 57 | ] = $matches; 58 | 59 | try { 60 | $targetClassReflection = new ReflectionClass($matchedTargetFqn); 61 | } catch (ReflectionException $exception) { 62 | $message = \sprintf( 63 | '%s is not a method parameter dynamic target point. %s.', 64 | $fqn, 65 | $exception->getMessage() 66 | ); 67 | 68 | throw new InvalidArgumentException(1, $message); 69 | } 70 | 71 | if ($targetClassReflection->hasMethod($matchedMethodName)) { 72 | $message = \sprintf( 73 | '%s is not a method parameter dynamic target point since the point is statically defined.', 74 | $fqn 75 | ); 76 | 77 | throw new InvalidArgumentException(1, $message); 78 | } 79 | 80 | $this->fqn = \sprintf('~%s', \ltrim($matchedFqn, '~')); 81 | $this->targetFqn = $matchedTargetFqn; 82 | $this->name = $matchedName; 83 | $this->methodName = $matchedMethodName; 84 | } 85 | 86 | /** 87 | * Gets the name of the method of the point. 88 | * 89 | * @return string 90 | */ 91 | public function getMethodName(): string 92 | { 93 | return $this->methodName; 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | public static function getFqnRegexPattern(): string 100 | { 101 | return self::FQN_REGEX_PATTERN; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Point/MethodParameterStaticTargetPoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use ReflectionException; 16 | use ReflectionParameter; 17 | 18 | /** 19 | * The method parameter static target point. 20 | * 21 | * @package Opportus\ObjectMapper\Point 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class MethodParameterStaticTargetPoint extends TargetPoint implements StaticTargetPointInterface 26 | { 27 | private const FQN_REGEX_PATTERN = '/^#?([A-Za-z0-9\\\_]+)::([A-Za-z0-9_]+)\(\)::\$([A-Za-z0-9_]+)$/'; 28 | 29 | /** 30 | * @var string $methodName 31 | */ 32 | private $methodName; 33 | 34 | /** 35 | * @var string[] $valuePhpTypes 36 | */ 37 | private $valuePhpTypes; 38 | 39 | /** 40 | * @var string[] $valuePhpDocTypes 41 | */ 42 | private $valuePhpDocTypes; 43 | 44 | /** 45 | * Constructs the method parameter static target point. 46 | * 47 | * @param string $fqn 48 | * @throws InvalidArgumentException 49 | */ 50 | public function __construct(string $fqn) 51 | { 52 | if (!\preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 53 | $message = \sprintf( 54 | '%s is not a method parameter static target point as FQN of such is expected to have the following syntax: %s.', 55 | $fqn, 56 | self::FQN_REGEX_PATTERN 57 | ); 58 | 59 | throw new InvalidArgumentException(1, $message); 60 | } 61 | 62 | [ 63 | $matchedFqn, 64 | $matchedTargetFqn, 65 | $matchedMethodName, 66 | $matchedName 67 | ] = $matches; 68 | 69 | try { 70 | /** @noinspection PhpParamsInspection */ 71 | $reflection = new ReflectionParameter( 72 | [$matchedTargetFqn, $matchedMethodName], 73 | $matchedName 74 | ); 75 | } catch (ReflectionException $exception) { 76 | $message = \sprintf( 77 | '%s is not a method parameter static target point. %s.', 78 | $fqn, 79 | $exception->getMessage() 80 | ); 81 | 82 | throw new InvalidArgumentException(1, $message); 83 | } 84 | 85 | $this->fqn = \sprintf('#%s', \ltrim($matchedFqn, '#')); 86 | $this->targetFqn = $matchedTargetFqn; 87 | $this->name = $matchedName; 88 | $this->methodName = $matchedMethodName; 89 | $this->valuePhpTypes = $this->resolveValuePhpTypes($reflection); 90 | $this->valuePhpDocTypes = $this->resolveValuePhpDocTypes($reflection); 91 | } 92 | 93 | /** 94 | * Gets the name of the method of the point. 95 | * 96 | * @return string 97 | */ 98 | public function getMethodName(): string 99 | { 100 | return $this->methodName; 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public static function getFqnRegexPattern(): string 107 | { 108 | return self::FQN_REGEX_PATTERN; 109 | } 110 | 111 | /** 112 | * {@inheritdoc} 113 | */ 114 | public function getValuePhpTypes(): array 115 | { 116 | return $this->valuePhpTypes; 117 | } 118 | 119 | /** 120 | * {@inheritdoc} 121 | */ 122 | public function getValuePhpDocTypes(): array 123 | { 124 | return $this->valuePhpDocTypes; 125 | } 126 | 127 | /** 128 | * @param ReflectionParameter $reflection 129 | * @return string[] 130 | */ 131 | private function resolveValuePhpTypes(ReflectionParameter $reflection): array 132 | { 133 | if (null === $reflection->getType()) { 134 | return []; 135 | } 136 | 137 | $types = []; 138 | foreach (\explode('|', $reflection->getType()->getName()) as $type) { 139 | if (0 === \strpos($type, '?')) { 140 | $types['null'] = 'null'; 141 | $types[\ltrim($type, '?')] = \ltrim($type, '?'); 142 | 143 | continue; 144 | } 145 | 146 | $types[$type] = $type; 147 | } 148 | 149 | return \array_values($types); 150 | } 151 | 152 | /** 153 | * @param ReflectionParameter $reflection 154 | * @return string[] 155 | */ 156 | private function resolveValuePhpDocTypes(ReflectionParameter $reflection): array 157 | { 158 | \preg_match_all( 159 | '/@param ([^\r\n ]+)/s', 160 | $reflection->getDeclaringFunction()->getDocComment(), 161 | $matches 162 | ); 163 | 164 | if (false === isset($matches[1][$reflection->getPosition()])) { 165 | return []; 166 | } 167 | 168 | return \explode('|', $matches[1][$reflection->getPosition()]); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Point/MethodStaticSourcePoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use ReflectionException; 16 | use ReflectionMethod; 17 | 18 | /** 19 | * The method static source point. 20 | * 21 | * @package Opportus\ObjectMapper\Point 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class MethodStaticSourcePoint extends SourcePoint implements StaticSourcePointInterface 26 | { 27 | private const FQN_REGEX_PATTERN = '/^#?([A-Za-z0-9\\\_]+)::([A-Za-z0-9_]+)\(\)$/'; 28 | 29 | /** 30 | * @var string[] $valuePhpTypes 31 | */ 32 | private $valuePhpTypes; 33 | 34 | /** 35 | * @var string[] $valuePhpDocTypes 36 | */ 37 | private $valuePhpDocTypes; 38 | 39 | /** 40 | * Constructs the method static source point. 41 | * 42 | * @param string $fqn 43 | * @throws InvalidArgumentException 44 | */ 45 | public function __construct(string $fqn) 46 | { 47 | if (!\preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 48 | $message = \sprintf( 49 | '%s is not a method static source point as FQN of such is expected to have the following syntax: %s.', 50 | $fqn, 51 | self::FQN_REGEX_PATTERN 52 | ); 53 | 54 | throw new InvalidArgumentException(1, $message); 55 | } 56 | 57 | [$matchedFqn, $matchedSourceFqn, $matchedName] = $matches; 58 | 59 | try { 60 | $reflection = new ReflectionMethod($matchedSourceFqn, $matchedName); 61 | } catch (ReflectionException $exception) { 62 | $message = \sprintf( 63 | '%s is not a method static source point. %s.', 64 | $fqn, 65 | $exception->getMessage() 66 | ); 67 | 68 | throw new InvalidArgumentException(1, $message); 69 | } 70 | 71 | if (0 !== $reflection->getNumberOfRequiredParameters()) { 72 | $message = \sprintf( 73 | '%s is not a method static source point because such cannot have required parameters.', 74 | $fqn 75 | ); 76 | 77 | throw new InvalidArgumentException(1, $message); 78 | } 79 | 80 | $this->fqn = \sprintf('#%s', \ltrim($matchedFqn, '#')); 81 | $this->sourceFqn = $matchedSourceFqn; 82 | $this->name = $matchedName; 83 | $this->valuePhpTypes = $this->resolveValuePhpTypes($reflection); 84 | $this->valuePhpDocTypes = $this->resolveValuePhpDocTypes($reflection); 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public static function getFqnRegexPattern(): string 91 | { 92 | return self::FQN_REGEX_PATTERN; 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function getValuePhpTypes(): array 99 | { 100 | return $this->valuePhpTypes; 101 | } 102 | 103 | /** 104 | * {@inheritdoc} 105 | */ 106 | public function getValuePhpDocTypes(): array 107 | { 108 | return $this->valuePhpDocTypes; 109 | } 110 | 111 | /** 112 | * @param ReflectionMethod $reflection 113 | * @return string[] 114 | */ 115 | private function resolveValuePhpTypes(ReflectionMethod $reflection): array 116 | { 117 | if (null === $reflection->getReturnType()) { 118 | return []; 119 | } 120 | 121 | $types = []; 122 | foreach (\explode('|', $reflection->getReturnType()->getName()) as $type) { 123 | if (0 === \strpos($type, '?')) { 124 | $types['null'] = 'null'; 125 | $types[\ltrim($type, '?')] = \ltrim($type, '?'); 126 | 127 | continue; 128 | } 129 | 130 | $types[$type] = $type; 131 | } 132 | 133 | return \array_values($types); 134 | } 135 | 136 | /** 137 | * @param ReflectionMethod $reflection 138 | * @return string[] 139 | */ 140 | private function resolveValuePhpDocTypes(ReflectionMethod $reflection): array 141 | { 142 | \preg_match('/@return ([^\r\n]+)/s', $reflection->getDocComment(), $matches); 143 | 144 | if (false === isset($matches[1])) { 145 | return []; 146 | } 147 | 148 | return \explode('|', $matches[1]); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Point/ObjectPoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The object point. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | abstract class ObjectPoint implements ObjectPointInterface 22 | { 23 | /** 24 | * @var string $fqn 25 | */ 26 | protected $fqn; 27 | 28 | /** 29 | * @var string $name 30 | */ 31 | protected $name; 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | abstract public static function getFqnRegexPattern(): string; 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function getFqn(): string 42 | { 43 | return $this->fqn; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getName(): string 50 | { 51 | return $this->name; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Point/ObjectPointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The object point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface ObjectPointInterface 22 | { 23 | /** 24 | * Gets the Fully Qualified Name regex pattern of the point. 25 | * 26 | * @return string The Fully Qualified Name regex pattern of the point 27 | */ 28 | public static function getFqnRegexPattern(): string; 29 | 30 | /** 31 | * Gets the Fully Qualified Name of the point. 32 | * 33 | * @return string The Fully Qualified Name of the point 34 | */ 35 | public function getFqn(): string; 36 | 37 | /** 38 | * Gets the name of the point. 39 | * 40 | * @return string The name of the point 41 | */ 42 | public function getName(): string; 43 | } 44 | -------------------------------------------------------------------------------- /src/Point/PropertyDynamicSourcePoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use ReflectionClass; 16 | use ReflectionException; 17 | 18 | /** 19 | * The property dynamic source point. 20 | * 21 | * @package Opportus\ObjectMapper\Point 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class PropertyDynamicSourcePoint extends SourcePoint implements DynamicSourcePointInterface 26 | { 27 | private const FQN_REGEX_PATTERN = '/^~?([A-Za-z0-9\\\_]+)::\$([A-Za-z0-9_]+)$/'; 28 | 29 | /** 30 | * Constructs the property dynamic source point. 31 | * 32 | * @param string $fqn 33 | * @throws InvalidArgumentException 34 | */ 35 | public function __construct(string $fqn) 36 | { 37 | if (!\preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 38 | $message = \sprintf( 39 | '%s is not a property dynamic source point as FQN of such is expected to have the following syntax: %s.', 40 | $fqn, 41 | self::FQN_REGEX_PATTERN 42 | ); 43 | 44 | throw new InvalidArgumentException(1, $message); 45 | } 46 | 47 | [$matchedFqn, $matchedSourceFqn, $matchedName] = $matches; 48 | 49 | try { 50 | $sourceClassReflection = new ReflectionClass($matchedSourceFqn); 51 | } catch (ReflectionException $exception) { 52 | $message = \sprintf( 53 | '%s is not a property dynamic source point. %s.', 54 | $fqn, 55 | $exception->getMessage() 56 | ); 57 | 58 | throw new InvalidArgumentException(1, $message); 59 | } 60 | 61 | if ($sourceClassReflection->hasProperty($matchedName)) { 62 | $message = \sprintf( 63 | '%s is not a property dynamic source point since the point is statically defined.', 64 | $fqn 65 | ); 66 | 67 | throw new InvalidArgumentException(1, $message); 68 | } 69 | 70 | $this->fqn = \sprintf('~%s', \ltrim($matchedFqn, '~')); 71 | $this->sourceFqn = $matchedSourceFqn; 72 | $this->name = $matchedName; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public static function getFqnRegexPattern(): string 79 | { 80 | return self::FQN_REGEX_PATTERN; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Point/PropertyDynamicTargetPoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use ReflectionClass; 16 | use ReflectionException; 17 | 18 | /** 19 | * The property dynamic target point. 20 | * 21 | * @package Opportus\ObjectMapper\Point 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class PropertyDynamicTargetPoint extends TargetPoint implements DynamicTargetPointInterface 26 | { 27 | private const FQN_REGEX_PATTERN = '/^~?([A-Za-z0-9\\\_]+)::\$([A-Za-z0-9_]+)$/'; 28 | 29 | /** 30 | * Constructs the property dynamic target point. 31 | * 32 | * @param string $fqn 33 | * @throws InvalidArgumentException 34 | */ 35 | public function __construct(string $fqn) 36 | { 37 | if (!\preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 38 | $message = \sprintf( 39 | '%s is not a property dynamic target point as FQN of such is expected to have the following syntax: %s.', 40 | $fqn, 41 | self::FQN_REGEX_PATTERN 42 | ); 43 | 44 | throw new InvalidArgumentException(1, $message); 45 | } 46 | 47 | [$matchedFqn, $matchedTargetFqn, $matchedName] = $matches; 48 | 49 | try { 50 | $targetClassReflection = new ReflectionClass($matchedTargetFqn); 51 | } catch (ReflectionException $exception) { 52 | $message = \sprintf( 53 | '%s is not a property dynamic target point. %s.', 54 | $fqn, 55 | $exception->getMessage() 56 | ); 57 | 58 | throw new InvalidArgumentException(1, $message); 59 | } 60 | 61 | if ($targetClassReflection->hasProperty($matchedName)) { 62 | $message = \sprintf( 63 | '%s is not a property dynamic target point since the point is statically defined.', 64 | $fqn 65 | ); 66 | 67 | throw new InvalidArgumentException(1, $message); 68 | } 69 | 70 | $this->fqn = \sprintf('~%s', \ltrim($matchedFqn, '~')); 71 | $this->targetFqn = $matchedTargetFqn; 72 | $this->name = $matchedName; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public static function getFqnRegexPattern(): string 79 | { 80 | return self::FQN_REGEX_PATTERN; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Point/PropertyStaticSourcePoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use ReflectionException; 16 | use ReflectionProperty; 17 | 18 | /** 19 | * The property static source point. 20 | * 21 | * @package Opportus\ObjectMapper\Point 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class PropertyStaticSourcePoint extends SourcePoint implements StaticSourcePointInterface 26 | { 27 | private const FQN_REGEX_PATTERN = '/^#?([A-Za-z0-9\\\_]+)::\$([A-Za-z0-9_]+)$/'; 28 | 29 | /** 30 | * @var string[] $valuePhpTypes 31 | */ 32 | private $valuePhpTypes; 33 | 34 | /** 35 | * @var string[] $valuePhpDocTypes 36 | */ 37 | private $valuePhpDocTypes; 38 | 39 | /** 40 | * Constructs the property static source point. 41 | * 42 | * @param string $fqn 43 | * @throws InvalidArgumentException 44 | */ 45 | public function __construct(string $fqn) 46 | { 47 | if (!\preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 48 | $message = \sprintf( 49 | '%s is not a property static source point as FQN of such is expected to have the following syntax: %s.', 50 | $fqn, 51 | self::FQN_REGEX_PATTERN 52 | ); 53 | 54 | throw new InvalidArgumentException(1, $message); 55 | } 56 | 57 | [$matchedFqn, $matchedSourceFqn, $matchedName] = $matches; 58 | 59 | try { 60 | $reflection = new ReflectionProperty( 61 | $matchedSourceFqn, 62 | $matchedName 63 | ); 64 | } catch (ReflectionException $exception) { 65 | $message = \sprintf( 66 | '%s is not a property static source point. %s.', 67 | $fqn, 68 | $exception->getMessage() 69 | ); 70 | 71 | throw new InvalidArgumentException(1, $message); 72 | } 73 | 74 | $this->fqn = \sprintf('#%s', \ltrim($matchedFqn, '#')); 75 | $this->sourceFqn = $matchedSourceFqn; 76 | $this->name = $matchedName; 77 | $this->valuePhpTypes = $this->resolveValuePhpTypes($reflection); 78 | $this->valuePhpDocTypes = $this->resolveValuePhpDocTypes($reflection); 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public static function getFqnRegexPattern(): string 85 | { 86 | return self::FQN_REGEX_PATTERN; 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function getValuePhpTypes(): array 93 | { 94 | return $this->valuePhpTypes; 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function getValuePhpDocTypes(): array 101 | { 102 | return $this->valuePhpDocTypes; 103 | } 104 | 105 | /** 106 | * @param ReflectionProperty $reflection 107 | * @return string[] 108 | */ 109 | private function resolveValuePhpTypes(ReflectionProperty $reflection): array 110 | { 111 | if (null === $reflection->getType()) { 112 | return []; 113 | } 114 | 115 | $types = []; 116 | foreach (\explode('|', $reflection->getType()->getName()) as $type) { 117 | if (0 === \strpos($type, '?')) { 118 | $types['null'] = 'null'; 119 | $types[\ltrim($type, '?')] = \ltrim($type, '?'); 120 | 121 | continue; 122 | } 123 | 124 | $types[$type] = $type; 125 | } 126 | 127 | return \array_values($types); 128 | } 129 | 130 | /** 131 | * @param ReflectionProperty $reflection 132 | * @return string[] 133 | */ 134 | private function resolveValuePhpDocTypes(ReflectionProperty $reflection): array 135 | { 136 | \preg_match('/@var ([^\r\n]+)/s', $reflection->getDocComment(), $matches); 137 | 138 | if (false === isset($matches[1])) { 139 | return []; 140 | } 141 | 142 | return \explode('|', $matches[1]); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Point/PropertyStaticTargetPoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use ReflectionException; 16 | use ReflectionProperty; 17 | 18 | /** 19 | * The property static target point. 20 | * 21 | * @package Opportus\ObjectMapper\Point 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class PropertyStaticTargetPoint extends TargetPoint implements StaticTargetPointInterface 26 | { 27 | private const FQN_REGEX_PATTERN = '/^#?([A-Za-z0-9\\\_]+)::\$([A-Za-z0-9_]+)$/'; 28 | 29 | /** 30 | * @var string[] $valuePhpTypes 31 | */ 32 | private $valuePhpTypes; 33 | 34 | /** 35 | * @var string[] $valuePhpDocTypes 36 | */ 37 | private $valuePhpDocTypes; 38 | 39 | /** 40 | * Constructs the property static target point. 41 | * 42 | * @param string $fqn 43 | * @throws InvalidArgumentException 44 | */ 45 | public function __construct(string $fqn) 46 | { 47 | if (!\preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 48 | $message = \sprintf( 49 | '%s is not a property static target point as FQN of such is expected to have the following syntax: %s.', 50 | $fqn, 51 | self::FQN_REGEX_PATTERN 52 | ); 53 | 54 | throw new InvalidArgumentException(1, $message); 55 | } 56 | 57 | [$matchedFqn, $matchedTargetFqn, $matchedName] = $matches; 58 | 59 | try { 60 | $reflection = new ReflectionProperty( 61 | $matchedTargetFqn, 62 | $matchedName 63 | ); 64 | } catch (ReflectionException $exception) { 65 | $message = \sprintf( 66 | '%s is not a property static target point. %s.', 67 | $fqn, 68 | $exception->getMessage() 69 | ); 70 | 71 | throw new InvalidArgumentException(1, $message); 72 | } 73 | 74 | $this->fqn = \sprintf('#%s', \ltrim($matchedFqn, '#')); 75 | $this->targetFqn = $matchedTargetFqn; 76 | $this->name = $matchedName; 77 | $this->valuePhpTypes = $this->resolveValuePhpTypes($reflection); 78 | $this->valuePhpDocTypes = $this->resolveValuePhpDocTypes($reflection); 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public static function getFqnRegexPattern(): string 85 | { 86 | return self::FQN_REGEX_PATTERN; 87 | } 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public function getValuePhpTypes(): array 93 | { 94 | return $this->valuePhpTypes; 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function getValuePhpDocTypes(): array 101 | { 102 | return $this->valuePhpDocTypes; 103 | } 104 | 105 | /** 106 | * @param ReflectionProperty $reflection 107 | * @return string[] 108 | */ 109 | private function resolveValuePhpTypes(ReflectionProperty $reflection): array 110 | { 111 | if (null === $reflection->getType()) { 112 | return []; 113 | } 114 | 115 | $types = []; 116 | foreach (\explode('|', $reflection->getType()->getName()) as $type) { 117 | if (0 === \strpos($type, '?')) { 118 | $types['null'] = 'null'; 119 | $types[\ltrim($type, '?')] = \ltrim($type, '?'); 120 | 121 | continue; 122 | } 123 | 124 | $types[$type] = $type; 125 | } 126 | 127 | return \array_values($types); 128 | } 129 | 130 | /** 131 | * @param ReflectionProperty $reflection 132 | * @return string[] 133 | */ 134 | private function resolveValuePhpDocTypes(ReflectionProperty $reflection): array 135 | { 136 | \preg_match('/@var ([^\r\n]+)/s', $reflection->getDocComment(), $matches); 137 | 138 | if (false === isset($matches[1])) { 139 | return []; 140 | } 141 | 142 | return \explode('|', $matches[1]); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Point/RecursionCheckPoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Map\MapInterface; 17 | use Opportus\ObjectMapper\ObjectMapperTrait; 18 | use Opportus\ObjectMapper\Route\RouteInterface; 19 | use Opportus\ObjectMapper\Source; 20 | use Opportus\ObjectMapper\SourceInterface; 21 | use Opportus\ObjectMapper\Target; 22 | use Opportus\ObjectMapper\TargetInterface; 23 | 24 | /** 25 | * The recursion check point. 26 | * 27 | * @package Opportus\ObjectMapper\Point 28 | * @author Clément Cazaud 29 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 30 | */ 31 | class RecursionCheckPoint implements CheckPointInterface 32 | { 33 | use ObjectMapperTrait; 34 | 35 | /** 36 | * @var string $sourceFqn 37 | */ 38 | private $sourceFqn; 39 | 40 | /** 41 | * @var string $targetFqn 42 | */ 43 | private $targetFqn; 44 | 45 | /** 46 | * @var SourcePointInterface $targetSourcePoint 47 | */ 48 | private $targetSourcePoint; 49 | 50 | /** 51 | * Constructs the recursion check point. 52 | * 53 | * @param string $sourceFqn 54 | * @param string $targetFqn 55 | * @param SourcePointInterface $targetSourcePoint 56 | * @throws InvalidArgumentException 57 | */ 58 | public function __construct( 59 | string $sourceFqn, 60 | string $targetFqn, 61 | SourcePointInterface $targetSourcePoint 62 | ) { 63 | if (false === \class_exists($sourceFqn)) { 64 | $message = \sprintf( 65 | 'Source class %s does not exist.', 66 | $sourceFqn 67 | ); 68 | 69 | throw new InvalidArgumentException(1, $message); 70 | } 71 | 72 | if (false === \class_exists($targetFqn)) { 73 | $message = \sprintf( 74 | 'Target class %s does not exist.', 75 | $targetFqn 76 | ); 77 | 78 | throw new InvalidArgumentException(1, $message); 79 | } 80 | 81 | $this->sourceFqn = $sourceFqn; 82 | $this->targetFqn = $targetFqn; 83 | $this->targetSourcePoint = $targetSourcePoint; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function control( 90 | $subject, 91 | RouteInterface $route, 92 | MapInterface $map, 93 | SourceInterface $source, 94 | TargetInterface $target 95 | ) { 96 | if (null === $subject) { 97 | return $subject; 98 | } 99 | 100 | $recursionSource = new Source($subject); 101 | 102 | if ($recursionSource->getFqn() !== $this->sourceFqn) { 103 | $message = \sprintf( 104 | 'Recursion source %s does not match recursion source %s being mapped on route %s.', 105 | $this->sourceFqn, 106 | $recursionSource->getFqn(), 107 | $route->getFqn() 108 | ); 109 | 110 | throw new InvalidOperationException($message); 111 | } 112 | 113 | if (null === $target->getInstance()) { 114 | $recursionTarget = new Target($this->targetFqn); 115 | } else { 116 | $recursionTarget = (new Source($target->getInstance())) 117 | ->getPointValue($this->targetSourcePoint); 118 | 119 | if (null === $recursionTarget) { 120 | $recursionTarget = new Target($this->targetFqn); 121 | } else { 122 | $recursionTarget = new Target($recursionTarget); 123 | 124 | if ($recursionTarget->getFqn() !== $this->targetFqn) { 125 | $message = \sprintf( 126 | 'Recursion target %s does not match recursion target %s being mapped on route %s.', 127 | $this->targetFqn, 128 | $recursionTarget->getFqn(), 129 | $route->getFqn() 130 | ); 131 | 132 | throw new InvalidOperationException($message); 133 | } 134 | } 135 | } 136 | 137 | $updatedRecursionTarget = $this->mapSourceToTarget( 138 | $recursionSource, 139 | $recursionTarget, 140 | $map 141 | ); 142 | 143 | if (null === $updatedRecursionTarget) { 144 | $message = \sprintf( 145 | 'No route found for recursion source %s and recursion target %s on route %s.', 146 | $recursionSource->getFqn(), 147 | $recursionTarget->getFqn(), 148 | $route->getFqn() 149 | ); 150 | 151 | throw new InvalidOperationException($message); 152 | } 153 | 154 | return $updatedRecursionTarget; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Point/SourcePoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The source point. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | abstract class SourcePoint extends ObjectPoint implements SourcePointInterface 22 | { 23 | /** 24 | * @var string $sourceFqn 25 | */ 26 | protected $sourceFqn; 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function getSourceFqn(): string 32 | { 33 | return $this->sourceFqn; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Point/SourcePointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The source point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface SourcePointInterface extends ObjectPointInterface 22 | { 23 | /** 24 | * Gets the Fully Qualified Name of the source. 25 | * 26 | * @return string The Fully Qualified Name of the source 27 | */ 28 | public function getSourceFqn(): string; 29 | } 30 | -------------------------------------------------------------------------------- /src/Point/StaticPointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The static point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface StaticPointInterface extends ObjectPointInterface 22 | { 23 | /** 24 | * Gets the PHP types of the point's value. 25 | * 26 | * @return string[] An array containing string elements representing the PHP types of the point's value. 27 | * @see https://www.php.net/manual/en/language.types.php 28 | */ 29 | public function getValuePhpTypes(): array; 30 | 31 | /** 32 | * Gets the PHPDoc types (non FQCN resolved) of the point's value. 33 | * 34 | * @return string[] An array containing string elements representing the PHPDoc types of the point's value. 35 | * Each element of the array represents 36 | * @see https://docs.phpdoc.org/guide/guides/types.html 37 | */ 38 | public function getValuePhpDocTypes(): array; 39 | } 40 | -------------------------------------------------------------------------------- /src/Point/StaticSourcePointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The static source point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface StaticSourcePointInterface extends SourcePointInterface, StaticPointInterface 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Point/StaticTargetPointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The static target point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface StaticTargetPointInterface extends TargetPointInterface, StaticPointInterface 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Point/TargetPoint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The target point. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | abstract class TargetPoint extends ObjectPoint implements TargetPointInterface 22 | { 23 | /** 24 | * @var string $targetFqn 25 | */ 26 | protected $targetFqn; 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function getTargetFqn(): string 32 | { 33 | return $this->targetFqn; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Point/TargetPointInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Point; 13 | 14 | /** 15 | * The target point interface. 16 | * 17 | * @package Opportus\ObjectMapper\Point 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | interface TargetPointInterface extends ObjectPointInterface 22 | { 23 | /** 24 | * Gets the Fully Qualified Name of the target. 25 | * 26 | * @return string The Fully Qualified Name of the target 27 | */ 28 | public function getTargetFqn(): string; 29 | } 30 | -------------------------------------------------------------------------------- /src/Route/Route.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Route; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\CheckPointCollection; 16 | use Opportus\ObjectMapper\Point\SourcePointInterface; 17 | use Opportus\ObjectMapper\Point\TargetPointInterface; 18 | 19 | /** 20 | * The route. 21 | * 22 | * @package Opportus\ObjectMapper\Route 23 | * @author Clément Cazaud 24 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 25 | */ 26 | class Route implements RouteInterface 27 | { 28 | /** 29 | * @var string $fqn 30 | */ 31 | private $fqn; 32 | 33 | /** 34 | * @var SourcePointInterface $sourcePoint 35 | */ 36 | private $sourcePoint; 37 | 38 | /** 39 | * @var TargetPointInterface $targetPoint 40 | */ 41 | private $targetPoint; 42 | 43 | /** 44 | * @var CheckPointCollection $checkPoints 45 | */ 46 | private $checkPoints; 47 | 48 | /** 49 | * Constructs the route. 50 | * 51 | * @param SourcePointInterface $sourcePoint 52 | * @param TargetPointInterface $targetPoint 53 | * @param CheckPointCollection $checkPoints 54 | * @throws InvalidArgumentException 55 | */ 56 | public function __construct( 57 | SourcePointInterface $sourcePoint, 58 | TargetPointInterface $targetPoint, 59 | CheckPointCollection $checkPoints 60 | ) { 61 | $this->sourcePoint = $sourcePoint; 62 | $this->targetPoint = $targetPoint; 63 | $this->checkPoints = $checkPoints; 64 | $this->fqn = \sprintf( 65 | '%s=>%s', 66 | $sourcePoint->getFqn(), 67 | $targetPoint->getFqn() 68 | ); 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | public function getFqn(): string 75 | { 76 | return $this->fqn; 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function getSourcePoint(): SourcePointInterface 83 | { 84 | return $this->sourcePoint; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function getTargetPoint(): TargetPointInterface 91 | { 92 | return $this->targetPoint; 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function getCheckPoints(): CheckPointCollection 99 | { 100 | return $this->checkPoints; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Route/RouteCollection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Route; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\ImmutableCollection; 16 | 17 | /** 18 | * The route collection. 19 | * 20 | * @package Opportus\ObjectMapper\Route 21 | * @author Clément Cazaud 22 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 23 | */ 24 | class RouteCollection extends ImmutableCollection 25 | { 26 | /** 27 | * Constructs the route collection. 28 | * 29 | * @param RouteInterface[] $routes 30 | * @throws InvalidArgumentException 31 | */ 32 | public function __construct(array $routes = []) 33 | { 34 | $indexedRoutes = []; 35 | 36 | foreach ($routes as $route) { 37 | if (!\is_object($route) || !$route instanceof RouteInterface) { 38 | $message = \sprintf( 39 | 'The array must contain exclusively elements of type %s, got an element of type %s.', 40 | RouteInterface::class, 41 | \is_object($route) ? \get_class($route) : \gettype($route) 42 | ); 43 | 44 | throw new InvalidArgumentException(1, $message); 45 | } 46 | 47 | $indexedRoutes[$route->getFqn()] = $route; 48 | } 49 | 50 | parent::__construct($indexedRoutes); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Route/RouteInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Route; 13 | 14 | use Opportus\ObjectMapper\Point\CheckPointCollection; 15 | use Opportus\ObjectMapper\Point\SourcePointInterface; 16 | use Opportus\ObjectMapper\Point\TargetPointInterface; 17 | 18 | /** 19 | * The route interface. 20 | * 21 | * @package Opportus\ObjectMapper\Route 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | interface RouteInterface 26 | { 27 | /** 28 | * Gets the Fully Qualified Name of the route. 29 | * 30 | * @return string The Fully Qualified Name of the route: 31 | * SourcePointFqn:TargetPointFqn 32 | */ 33 | public function getFqn(): string; 34 | 35 | /** 36 | * Get the source point of the route. 37 | * 38 | * @return SourcePointInterface The source point of the route to get value 39 | * from 40 | */ 41 | public function getSourcePoint(): SourcePointInterface; 42 | 43 | /** 44 | * Get the target point of the route. 45 | * 46 | * @return TargetPointInterface The target point of the route to assign the 47 | * value to 48 | */ 49 | public function getTargetPoint(): TargetPointInterface; 50 | 51 | /** 52 | * Gets the check points of the route. 53 | * 54 | * @return CheckPointCollection The collection of check points through 55 | * which the value will pass prior to get 56 | * assigned to the target 57 | */ 58 | public function getCheckPoints(): CheckPointCollection; 59 | } 60 | -------------------------------------------------------------------------------- /src/Source.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper; 13 | 14 | use Error; 15 | use Exception; 16 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 17 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 18 | use Opportus\ObjectMapper\Point\MethodDynamicSourcePoint; 19 | use Opportus\ObjectMapper\Point\MethodStaticSourcePoint; 20 | use Opportus\ObjectMapper\Point\PropertyDynamicSourcePoint; 21 | use Opportus\ObjectMapper\Point\PropertyStaticSourcePoint; 22 | use Opportus\ObjectMapper\Point\SourcePointInterface; 23 | use ReflectionClass; 24 | use ReflectionObject; 25 | 26 | /** 27 | * The source. 28 | * 29 | * @package Opportus\ObjectMapper 30 | * @author Clément Cazaud 31 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 32 | */ 33 | class Source implements SourceInterface 34 | { 35 | /** 36 | * @var ReflectionClass $classReflection 37 | */ 38 | private $classReflection; 39 | 40 | /** 41 | * @var ReflectionObject $objectReflection 42 | */ 43 | private $objectReflection; 44 | 45 | /** 46 | * @var object $instance 47 | */ 48 | private $instance; 49 | 50 | /** 51 | * Constructs the source. 52 | * 53 | * @param object $source 54 | */ 55 | public function __construct(object $source) 56 | { 57 | $this->classReflection = new ReflectionClass($source); 58 | $this->objectReflection = new ReflectionObject($source); 59 | $this->instance = $source; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function getFqn(): string 66 | { 67 | return $this->classReflection->getName(); 68 | } 69 | 70 | /** 71 | * {@inheritdoc} 72 | */ 73 | public function getClassReflection(): ReflectionClass 74 | { 75 | return new ReflectionClass($this->classReflection->getName()); 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function getObjectReflection(): ReflectionObject 82 | { 83 | return new ReflectionObject($this->instance); 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function getInstance(): object 90 | { 91 | return $this->instance; 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public function getPointValue(SourcePointInterface $point) 98 | { 99 | if ($this->getFqn() !== $point->getSourceFqn()) { 100 | $message = \sprintf( 101 | '%s is not a point of source %s.', 102 | $point->getFqn(), 103 | $this->getFqn() 104 | ); 105 | 106 | throw new InvalidArgumentException(1, $message); 107 | } 108 | 109 | try { 110 | if ($point instanceof PropertyStaticSourcePoint) { 111 | $propertyReflection = $this->classReflection 112 | ->getProperty($point->getName()); 113 | 114 | $propertyReflection->setAccessible(true); 115 | 116 | return $propertyReflection->getValue($this->instance); 117 | } elseif ($point instanceof MethodStaticSourcePoint) { 118 | $methodReflection = $this->classReflection 119 | ->getMethod($point->getName()); 120 | 121 | $methodReflection->setAccessible(true); 122 | 123 | return $methodReflection->invoke($this->instance); 124 | } elseif ($point instanceof PropertyDynamicSourcePoint) { 125 | return $this->instance->{$point->getName()}; 126 | } elseif ($point instanceof MethodDynamicSourcePoint) { 127 | return $this->instance->{$point->getName()}(); 128 | } 129 | } catch (Error|Exception $exception) { 130 | throw new InvalidOperationException( 131 | $exception->getMessage(), 132 | 0, 133 | $exception 134 | ); 135 | } 136 | 137 | $message = \sprintf( 138 | 'Source point type %s not supported.', 139 | \get_class($point) 140 | ); 141 | 142 | throw new InvalidArgumentException(1, $message); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/SourceInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Point\SourcePointInterface; 17 | use ReflectionClass; 18 | use ReflectionObject; 19 | 20 | /** 21 | * The source interface. 22 | * 23 | * @package Opportus\ObjectMapper 24 | * @author Clément Cazaud 25 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 26 | */ 27 | interface SourceInterface 28 | { 29 | /** 30 | * Gets the source Fully Qualified Name. 31 | * 32 | * @return string The Fully Qualified Class Name of the source 33 | */ 34 | public function getFqn(): string; 35 | 36 | /** 37 | * Gets the source class reflection. 38 | * 39 | * @return ReflectionClass The class reflection of the source 40 | */ 41 | public function getClassReflection(): ReflectionClass; 42 | 43 | /** 44 | * Gets the source object reflection. 45 | * 46 | * @return ReflectionObject The object reflection of the source 47 | */ 48 | public function getObjectReflection(): ReflectionObject; 49 | 50 | /** 51 | * Gets the source instance. 52 | * 53 | * @return object The source instance 54 | */ 55 | public function getInstance(): object; 56 | 57 | /** 58 | * Gets the value of the passed source point. 59 | * 60 | * @param SourcePointInterface $point The source point to get the 61 | * value from 62 | * @return mixed The value of the source point 63 | * @throws InvalidArgumentException If the source FQN of the point 64 | * does not match the source FQN or 65 | * if the point type is not 66 | * supported 67 | * @throws InvalidOperationException If the operation fails for any 68 | * reason 69 | */ 70 | public function getPointValue(SourcePointInterface $point); 71 | } 72 | -------------------------------------------------------------------------------- /src/TargetInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Point\TargetPointInterface; 17 | use ReflectionClass; 18 | use ReflectionObject; 19 | 20 | /** 21 | * The target interface. 22 | * 23 | * @package Opportus\ObjectMapper 24 | * @author Clément Cazaud 25 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 26 | */ 27 | interface TargetInterface 28 | { 29 | /** 30 | * Gets the target Fully Qualified Name. 31 | * 32 | * @return string The Fully Qualified Class Name of the target 33 | */ 34 | public function getFqn(): string; 35 | 36 | /** 37 | * Gets the target class reflection. 38 | * 39 | * @return ReflectionClass The class reflection of the target 40 | */ 41 | public function getClassReflection(): ReflectionClass; 42 | 43 | /** 44 | * Gets the target object reflection. 45 | * 46 | * @return null|ReflectionObject The object reflection of the target 47 | */ 48 | public function getObjectReflection(): ?ReflectionObject; 49 | 50 | /** 51 | * Gets the target instance. 52 | * 53 | * @return null|object The target instance if it has been instantiated 54 | * or null otherwise 55 | */ 56 | public function getInstance(): ?object; 57 | 58 | /** 59 | * Sets the value of the passed target point. 60 | * 61 | * @param TargetPointInterface $point The target point to assign 62 | * the value to 63 | * @param mixed $pointValue The value to assign to the 64 | * target 65 | * @throws InvalidArgumentException If the target FQN of the 66 | * point does not match the 67 | * target FQN or if the point 68 | * type is not supported 69 | */ 70 | public function setPointValue(TargetPointInterface $point, $pointValue); 71 | 72 | /** 73 | * Operates the target, effectively assigning the previously set 74 | * values (with `TargetInterface::setPointValue()`) to their points. 75 | * 76 | * @throws InvalidOperationException If the operation fails for any reason 77 | */ 78 | public function operate(); 79 | } 80 | -------------------------------------------------------------------------------- /tests/Exception/CheckPointSeizingExceptionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Exception; 13 | 14 | use Opportus\ObjectMapper\Exception\CheckPointSeizingException; 15 | use Opportus\ObjectMapper\Exception\Exception; 16 | use Opportus\ObjectMapper\Tests\Test; 17 | 18 | /** 19 | * The check point seizing exception test. 20 | * 21 | * @package Opportus\ObjectMapper\Tests\Exception 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class CheckPointSeizingExceptionTest extends Test 26 | { 27 | public function testConstruct(): void 28 | { 29 | $exception = $this->createCheckPointSeizingException(); 30 | 31 | static::assertInstanceOf(Exception::class, $exception); 32 | } 33 | 34 | private function createCheckPointSeizingException(): CheckPointSeizingException 35 | { 36 | return new CheckPointSeizingException(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Exception/ExceptionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Exception; 13 | 14 | use Exception as BaseException; 15 | use Opportus\ObjectMapper\Exception\Exception; 16 | use Opportus\ObjectMapper\Tests\Test; 17 | 18 | /** 19 | * The exception test. 20 | * 21 | * @package Opportus\ObjectMapper\Tests\Exception 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class ExceptionTest extends Test 26 | { 27 | public function testConstruct(): void 28 | { 29 | $exception = $this->createException(); 30 | 31 | static::assertInstanceOf(BaseException::class, $exception); 32 | } 33 | 34 | private function createException(): Exception 35 | { 36 | return new Exception(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Exception/InvalidArgumentExceptionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Exception; 13 | 14 | use Opportus\ObjectMapper\Exception\Exception; 15 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 16 | use Opportus\ObjectMapper\Tests\Test; 17 | 18 | /** 19 | * The invalid argument exception test. 20 | * 21 | * @package Opportus\ObjectMapper\Tests\Exception 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class InvalidArgumentExceptionTest extends Test 26 | { 27 | public function testConstruct(): void 28 | { 29 | $exception = $this->createInvalidArgumentException(1); 30 | 31 | static::assertInstanceOf(Exception::class, $exception); 32 | } 33 | 34 | public function testGetArgument(): void 35 | { 36 | $exception = $this->createInvalidArgumentException(1); 37 | 38 | static::assertSame(1, $exception->getArgument()); 39 | } 40 | 41 | private function createInvalidArgumentException( 42 | int $argument 43 | ): InvalidArgumentException { 44 | return new InvalidArgumentException($argument); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Exception/InvalidOperationExceptionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Exception; 13 | 14 | use Opportus\ObjectMapper\Exception\Exception; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Tests\Test; 17 | 18 | /** 19 | * The invalid operation exception test. 20 | * 21 | * @package Opportus\ObjectMapper\Tests\Exception 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class InvalidOperationExceptionTest extends Test 26 | { 27 | public function testConstruct(): void 28 | { 29 | $exception = $this->createInvalidOperationException(); 30 | 31 | static::assertInstanceOf(Exception::class, $exception); 32 | } 33 | 34 | private function createInvalidOperationException(): InvalidOperationException 35 | { 36 | return new InvalidOperationException(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Exception/InvalidTargetOperationExceptionTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Exception; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidTargetOperationException; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Tests\Test; 17 | 18 | /** 19 | * The invalid operation exception test. 20 | * 21 | * @package Opportus\ObjectMapper\Tests\Exception 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | class InvalidTargetOperationExceptionTest extends Test 26 | { 27 | public function testConstruct(): void 28 | { 29 | $exception = $this->createInvalidObjectOperationException(); 30 | 31 | static::assertInstanceOf(InvalidOperationException::class, $exception); 32 | } 33 | 34 | private function createInvalidObjectOperationException(): InvalidTargetOperationException 35 | { 36 | return new InvalidTargetOperationException(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Map/MapBuilderTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Route; 13 | 14 | use Opportus\ObjectMapper\Map\Map; 15 | use Opportus\ObjectMapper\Map\MapBuilder; 16 | use Opportus\ObjectMapper\Map\MapBuilderInterface; 17 | use Opportus\ObjectMapper\PathFinder\PathFinderInterface; 18 | use Opportus\ObjectMapper\PathFinder\StaticPathFinder; 19 | use Opportus\ObjectMapper\Route\Route; 20 | use Opportus\ObjectMapper\Route\RouteBuilder; 21 | use Opportus\ObjectMapper\Route\RouteCollection; 22 | use Opportus\ObjectMapper\Route\RouteInterface; 23 | use Opportus\ObjectMapper\Tests\Test; 24 | 25 | /** 26 | * The map builder test. 27 | * 28 | * @package Opportus\ObjectMapper\Tests\Map 29 | * @author Clément Cazaud 30 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 31 | */ 32 | class MapBuilderTest extends Test 33 | { 34 | public function testConstruct(): void 35 | { 36 | $mapBuilder = $this->createMapBuilder(); 37 | 38 | static::assertInstanceOf(MapBuilderInterface::class, $mapBuilder); 39 | } 40 | 41 | public function testGetRouteBuilder(): void 42 | { 43 | $mapBuilder = $this->createMapBuilder(); 44 | 45 | $routeBuilder = $mapBuilder->getRouteBuilder(); 46 | 47 | static::assertInstanceOf(RouteBuilder::class, $routeBuilder); 48 | static::assertEquals($mapBuilder, $routeBuilder->getMapBuilder()); 49 | } 50 | 51 | /** 52 | * @dataProvider provideRoute 53 | */ 54 | public function testAddRoute(RouteInterface $route): void 55 | { 56 | $mapBuilder1 = $this->createMapBuilder(); 57 | $mapBuilder2 = $mapBuilder1->addRoute($route); 58 | 59 | static::assertInstanceOf(MapBuilder::class, $mapBuilder2); 60 | static::assertNotEquals($mapBuilder1, $mapBuilder2); 61 | } 62 | 63 | public function testAddRoutes(): void 64 | { 65 | $mapBuilder1 = $this->createMapBuilder(); 66 | 67 | $routes = []; 68 | 69 | foreach ($this->provideRoute() as $route) { 70 | $routes[] = $route[0]; 71 | } 72 | 73 | $routes = new RouteCollection($routes); 74 | 75 | $mapBuilder2 = $mapBuilder1->addRoutes($routes); 76 | 77 | static::assertInstanceOf(MapBuilder::class, $mapBuilder2); 78 | static::assertNotEquals($mapBuilder1, $mapBuilder2); 79 | } 80 | 81 | public function testAddPathFinder(): void 82 | { 83 | $mapBuilder1 = $this->createMapBuilder(); 84 | $mapBuilder2 = $mapBuilder1->addPathFinder( 85 | $this->createPathFinder() 86 | ); 87 | 88 | static::assertInstanceOf(MapBuilder::class, $mapBuilder2); 89 | static::assertNotEquals($mapBuilder1, $mapBuilder2); 90 | } 91 | 92 | public function testAddStaticPathFinder(): void 93 | { 94 | $mapBuilder1 = $this->createMapBuilder(); 95 | $mapBuilder2 = $mapBuilder1->addStaticPathFinder(); 96 | 97 | static::assertInstanceOf(MapBuilder::class, $mapBuilder2); 98 | static::assertNotEquals($mapBuilder1, $mapBuilder2); 99 | } 100 | 101 | public function testAddStaticSourceToDynamicTargetPathFinder(): void 102 | { 103 | $mapBuilder1 = $this->createMapBuilder(); 104 | $mapBuilder2 = $mapBuilder1->addStaticSourceToDynamicTargetPathFinder(); 105 | 106 | static::assertInstanceOf(MapBuilder::class, $mapBuilder2); 107 | static::assertNotEquals($mapBuilder1, $mapBuilder2); 108 | } 109 | 110 | public function testAddDynamicSourceToStaticTargetPathFinder(): void 111 | { 112 | $mapBuilder1 = $this->createMapBuilder(); 113 | $mapBuilder2 = $mapBuilder1->addDynamicSourceToStaticTargetPathFinder(); 114 | 115 | static::assertInstanceOf(MapBuilder::class, $mapBuilder2); 116 | static::assertNotEquals($mapBuilder1, $mapBuilder2); 117 | } 118 | 119 | public function testGetMap(): void 120 | { 121 | $mapBuilder = $this->createMapBuilder(); 122 | 123 | foreach ($this->provideRoute() as $route) { 124 | $mapBuilder = $mapBuilder->addRoute($route[0]); 125 | } 126 | 127 | $map = $mapBuilder 128 | ->addPathFinder($this->createPathFinder(), 20) 129 | ->addPathFinder($this->createPathFinder(), 30) 130 | ->addPathFinder($this->createPathFinder(), 10) 131 | ->addPathFinder($this->createPathFinder()) 132 | ->addStaticPathFinder(40) 133 | ->addStaticPathFinder() 134 | ->addStaticSourceToDynamicTargetPathFinder(10) 135 | ->addStaticSourceToDynamicTargetPathFinder() 136 | ->addDynamicSourceToStaticTargetPathFinder(50) 137 | ->getMap(); 138 | 139 | static::assertInstanceOf(Map::class, $map); 140 | 141 | $map = $mapBuilder->getMap(); 142 | 143 | static::assertInstanceOf(Map::class, $map); 144 | } 145 | 146 | private function createPathFinder(): PathFinderInterface 147 | { 148 | return new StaticPathFinder($this->createRouteBuilder()); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/Map/MapTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Route; 13 | 14 | use Exception; 15 | use Opportus\ObjectMapper\Exception\InvalidOperationException; 16 | use Opportus\ObjectMapper\Map\Map; 17 | use Opportus\ObjectMapper\Map\MapInterface; 18 | use Opportus\ObjectMapper\PathFinder\DynamicSourceToStaticTargetPathFinder; 19 | use Opportus\ObjectMapper\PathFinder\PathFinderCollection; 20 | use Opportus\ObjectMapper\PathFinder\PathFinderInterface; 21 | use Opportus\ObjectMapper\PathFinder\StaticPathFinder; 22 | use Opportus\ObjectMapper\PathFinder\StaticSourceToDynamicTargetPathFinder; 23 | use Opportus\ObjectMapper\Route\RouteCollection; 24 | use Opportus\ObjectMapper\Source; 25 | use Opportus\ObjectMapper\Target; 26 | use Opportus\ObjectMapper\Tests\Test; 27 | use stdClass; 28 | 29 | /** 30 | * The map test. 31 | * 32 | * @package Opportus\ObjectMapper\Tests\Map 33 | * @author Clément Cazaud 34 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 35 | */ 36 | class MapTest extends Test 37 | { 38 | public function testConstruct(): void 39 | { 40 | $map = $this->createMap(); 41 | 42 | static::assertInstanceOf(MapInterface::class, $map); 43 | } 44 | 45 | /** 46 | * @dataProvider provideObjects 47 | */ 48 | public function testGetRoutes(object $providedSource, $providedTarget): void 49 | { 50 | $source = new Source($providedSource); 51 | $target = new Target($providedTarget); 52 | 53 | $pathFinders = $this->createPathFinders(); 54 | 55 | $mapRoutes = []; 56 | 57 | foreach ($this->provideRoute() as $route) { 58 | $mapRoutes[] = $route[0]; 59 | } 60 | 61 | $mapRoutes = new RouteCollection($mapRoutes); 62 | 63 | $map = $this->createMap($pathFinders, $mapRoutes); 64 | 65 | $expectedRoutes = []; 66 | 67 | foreach ($pathFinders as $pathFinder) { 68 | foreach ($pathFinder->getRoutes($source, $target) as $pathFinderRoute) { 69 | $expectedRoutes[] = $pathFinderRoute; 70 | } 71 | } 72 | 73 | foreach ($mapRoutes as $mapRoute) { 74 | $sourceFqn = $mapRoute->getSourcePoint()->getSourceFqn(); 75 | $targetFqn = $mapRoute->getTargetPoint()->getTargetFqn(); 76 | 77 | if ($sourceFqn !== $source->getFqn() || $targetFqn !== $target->getFqn()) { 78 | continue; 79 | } 80 | 81 | $expectedRoutes[] = $mapRoute; 82 | } 83 | 84 | $expectedRoutesToKeep = []; 85 | 86 | foreach ($expectedRoutes as $key => $route) { 87 | $expectedRoutesToKeep[$route->getTargetPoint()->getFqn()] = $key; 88 | } 89 | 90 | foreach ($expectedRoutes as $key => $route) { 91 | if (false === \in_array($key, $expectedRoutesToKeep)) { 92 | unset($expectedRoutes[$key]); 93 | } 94 | } 95 | 96 | $expectedRoutes = new RouteCollection($expectedRoutes); 97 | 98 | $returnedRoutes = $map->getRoutes($source, $target); 99 | 100 | static::assertEquals($expectedRoutes, $returnedRoutes); 101 | 102 | $returnedRoutes = $map->getRoutes( 103 | new Source(new stdClass()), 104 | new Target(stdClass::class) 105 | ); 106 | 107 | static::assertEmpty($returnedRoutes); 108 | } 109 | 110 | /** 111 | * @dataProvider provideObjects 112 | */ 113 | public function testGetRoutesException( 114 | object $providedSource, 115 | $providedTarget 116 | ): void { 117 | $pathFinder = $this->getMockBuilder(PathFinderInterface::class) 118 | ->getMock(); 119 | 120 | $pathFinder->method('getRoutes') 121 | ->willThrowException(new Exception()); 122 | 123 | $pathFinders = new PathFinderCollection([$pathFinder]); 124 | 125 | $map = $this->createMap($pathFinders); 126 | 127 | $source = new Source($providedSource); 128 | $target = new Target($providedTarget); 129 | 130 | $this->expectException(InvalidOperationException::class); 131 | 132 | $map->getRoutes($source, $target); 133 | } 134 | 135 | private function createPathFinders(): PathFinderCollection 136 | { 137 | return new PathFinderCollection([ 138 | new StaticPathFinder( 139 | $this->createRouteBuilder() 140 | ), 141 | new StaticSourceToDynamicTargetPathFinder( 142 | $this->createRouteBuilder() 143 | ), 144 | new DynamicSourceToStaticTargetPathFinder( 145 | $this->createRouteBuilder() 146 | ), 147 | ]); 148 | } 149 | 150 | private function createMap( 151 | ?PathFinderCollection $pathFinders = null, 152 | ?RouteCollection $routes = null 153 | ): Map { 154 | return new Map($pathFinders, $routes); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tests/PathFinder/DynamicSourceToStaticTargetPathFinderTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\PathFinder; 13 | 14 | use Opportus\ObjectMapper\PathFinder\DynamicSourceToStaticTargetPathFinder; 15 | use Opportus\ObjectMapper\PathFinder\PathFinder; 16 | use Opportus\ObjectMapper\Route\RouteInterface; 17 | use Opportus\ObjectMapper\SourceInterface; 18 | use Opportus\ObjectMapper\TargetInterface; 19 | use ReflectionMethod; 20 | use ReflectionParameter; 21 | use ReflectionProperty; 22 | 23 | /** 24 | * The dynamic source to static target path finder test. 25 | * 26 | * @package Opportus\ObjectMapper\Tests\PathFinder 27 | * @author Clément Cazaud 28 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 29 | */ 30 | class DynamicSourceToStaticTargetPathFinderTest extends StaticPathFinderTest 31 | { 32 | protected function createPathFinder(): PathFinder 33 | { 34 | return new DynamicSourceToStaticTargetPathFinder( 35 | $this->createRouteBuilder() 36 | ); 37 | } 38 | 39 | protected function getReferencePointRoute( 40 | SourceInterface $source, 41 | TargetInterface $target, 42 | $referencePoint 43 | ): ?RouteInterface { 44 | $targetPointReflection = $referencePoint; 45 | $sourceClassReflection = $source->getClassReflection(); 46 | $sourceObjectReflection = $source->getObjectReflection(); 47 | 48 | if ($sourceClassReflection 49 | ->hasProperty($targetPointReflection->getName()) || 50 | !$sourceObjectReflection 51 | ->hasProperty($targetPointReflection->getName()) 52 | ) { 53 | return null; 54 | } 55 | 56 | $sourcePointFqn = \sprintf( 57 | '~%s::$%s', 58 | $sourceClassReflection->getName(), 59 | $targetPointReflection->getName() 60 | ); 61 | 62 | if ($targetPointReflection instanceof ReflectionProperty) { 63 | $targetPointFqn = \sprintf( 64 | '#%s::$%s', 65 | $targetPointReflection->getDeclaringClass()->getName(), 66 | $targetPointReflection->getName() 67 | ); 68 | } elseif ($targetPointReflection instanceof ReflectionParameter) { 69 | $targetPointFqn = \sprintf( 70 | '#%s::%s()::$%s', 71 | $targetPointReflection->getDeclaringClass()->getName(), 72 | $targetPointReflection->getDeclaringFunction()->getName(), 73 | $targetPointReflection->getName() 74 | ); 75 | } 76 | 77 | return $this->createRouteBuilder() 78 | ->setDynamicSourcePoint($sourcePointFqn) 79 | ->setStaticTargetPoint($targetPointFqn) 80 | ->getRoute(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/PathFinder/StaticSourceToDynamicTargetPathFinderTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\PathFinder; 13 | 14 | use Opportus\ObjectMapper\PathFinder\PathFinder; 15 | use Opportus\ObjectMapper\PathFinder\StaticSourceToDynamicTargetPathFinder; 16 | use Opportus\ObjectMapper\Route\RouteInterface; 17 | use Opportus\ObjectMapper\SourceInterface; 18 | use Opportus\ObjectMapper\TargetInterface; 19 | use ReflectionMethod; 20 | use ReflectionProperty; 21 | 22 | /** 23 | * The static source to dynamic target path finder test. 24 | * 25 | * @package Opportus\ObjectMapper\Tests\PathFinder 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class StaticSourceToDynamicTargetPathFinderTest extends PathFinderTest 30 | { 31 | protected function createPathFinder(): PathFinder 32 | { 33 | return new StaticSourceToDynamicTargetPathFinder( 34 | $this->createRouteBuilder() 35 | ); 36 | } 37 | 38 | protected function getReferencePoints( 39 | SourceInterface $source, 40 | TargetInterface $target 41 | ): array { 42 | $sourceClassReflection = $source->getClassReflection(); 43 | 44 | $propertyBlackList = []; 45 | $sourcePointReflections = []; 46 | 47 | foreach ( 48 | $sourceClassReflection->getMethods(ReflectionMethod::IS_PUBLIC) as 49 | $methodReflection 50 | ) { 51 | if (\strpos($methodReflection->getName(), 'get') !== 0 && 52 | \strpos($methodReflection->getName(), 'is') !== 0 53 | ) { 54 | continue; 55 | } 56 | 57 | if ($methodReflection->getNumberOfRequiredParameters() !== 0) { 58 | continue; 59 | } 60 | 61 | if (\strpos($methodReflection->getName(), 'get') === 0) { 62 | $propertyBlackList[] = \lcfirst(\substr($methodReflection->getName(), 3)); 63 | } elseif (\strpos($methodReflection->getName(), 'is') === 0) { 64 | $propertyBlackList[] = \lcfirst(\substr($methodReflection->getName(), 2)); 65 | } 66 | 67 | $sourcePointReflections[] = $methodReflection; 68 | } 69 | 70 | foreach ( 71 | $sourceClassReflection->getProperties(ReflectionProperty::IS_PUBLIC) as 72 | $propertyReflection 73 | ) { 74 | if (\in_array($propertyReflection->getName(), $propertyBlackList)) { 75 | continue; 76 | } 77 | 78 | $sourcePointReflections[] = $propertyReflection; 79 | } 80 | 81 | return $sourcePointReflections; 82 | } 83 | 84 | protected function getReferencePointRoute( 85 | SourceInterface $source, 86 | TargetInterface $target, 87 | $referencePoint 88 | ): ?RouteInterface { 89 | $sourcePointReflection = $referencePoint; 90 | $targetClassReflection = $target->getClassReflection(); 91 | 92 | if ($sourcePointReflection instanceof ReflectionProperty) { 93 | $targetPointName = $sourcePointReflection->getName(); 94 | } elseif ($sourcePointReflection instanceof ReflectionMethod) { 95 | $targetPointName = \lcfirst(\substr( 96 | $sourcePointReflection->getName(), 97 | 3 98 | )); 99 | if (\strpos($sourcePointReflection->getName(), 'get') === 0) { 100 | $targetPointName = \lcfirst(\substr( 101 | $sourcePointReflection->getName(), 102 | 3 103 | )); 104 | } elseif (\strpos($sourcePointReflection->getName(), 'is') === 0) { 105 | $targetPointName = \lcfirst(\substr( 106 | $sourcePointReflection->getName(), 107 | 2 108 | )); 109 | } 110 | } 111 | 112 | if ($targetClassReflection->hasProperty($targetPointName)) { 113 | return null; 114 | } 115 | 116 | if ($sourcePointReflection instanceof ReflectionProperty) { 117 | $sourcePointFqn = \sprintf( 118 | '#%s::$%s', 119 | $sourcePointReflection->getDeclaringClass()->getName(), 120 | $sourcePointReflection->getName() 121 | ); 122 | } elseif ($sourcePointReflection instanceof ReflectionMethod) { 123 | $sourcePointFqn = \sprintf( 124 | '#%s::%s()', 125 | $sourcePointReflection->getDeclaringClass()->getName(), 126 | $sourcePointReflection->getName() 127 | ); 128 | } 129 | 130 | $targetPointFqn = \sprintf( 131 | '~%s::$%s', 132 | $targetClassReflection->getName(), 133 | $targetPointName 134 | ); 135 | 136 | return $this->createRouteBuilder() 137 | ->setStaticSourcePoint($sourcePointFqn) 138 | ->setDynamicTargetPoint($targetPointFqn) 139 | ->getRoute(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/Point/IterableRecursionCheckPointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\CheckPointInterface; 16 | use Opportus\ObjectMapper\Point\IterableRecursionCheckPoint; 17 | use Opportus\ObjectMapper\Point\SourcePointInterface; 18 | use Opportus\ObjectMapper\Tests\Test; 19 | use Opportus\ObjectMapper\Tests\TestObjectA; 20 | use Opportus\ObjectMapper\Tests\TestObjectB; 21 | 22 | /** 23 | * The iterable recursion check point test. 24 | * 25 | * @todo Test Control method 26 | * 27 | * @package Opportus\ObjectMapper\Tests\Point 28 | * @author Clément Cazaud 29 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 30 | */ 31 | class IterableRecursionCheckPointTest extends Test 32 | { 33 | /** 34 | * @dataProvider provideConstructArguments 35 | */ 36 | public function testConstruct( 37 | string $sourceFqn, 38 | string $targetFqn, 39 | SourcePointInterface $targetIterableSourcePoint 40 | ): void { 41 | $point = $this->createIterableRecursionCheckPoint( 42 | $sourceFqn, 43 | $targetFqn, 44 | $targetIterableSourcePoint 45 | ); 46 | 47 | static::assertInstanceOf(CheckPointInterface::class, $point); 48 | } 49 | 50 | /** 51 | * @dataProvider provideConstructInvalidArguments 52 | */ 53 | public function testConstructException( 54 | string $sourceFqn, 55 | string $targetFqn, 56 | SourcePointInterface $targetSourcePoint 57 | ): void { 58 | $this->expectException(InvalidArgumentException::class); 59 | 60 | $this->createIterableRecursionCheckPoint( 61 | $sourceFqn, 62 | $targetFqn, 63 | $targetSourcePoint 64 | ); 65 | } 66 | 67 | public function provideConstructArguments(): array 68 | { 69 | return [ 70 | [ 71 | TestObjectA::class, 72 | TestObjectB::class, 73 | $this->getMockBuilder(SourcePointInterface::class)->getMock() 74 | ], 75 | ]; 76 | } 77 | 78 | public function provideConstructInvalidArguments(): array 79 | { 80 | return [ 81 | [ 82 | TestObjectA::class, 83 | 'NonObject', 84 | $this->getMockBuilder(SourcePointInterface::class)->getMock() 85 | ], 86 | [ 87 | 'NonObject', 88 | TestObjectB::class, 89 | $this->getMockBuilder(SourcePointInterface::class)->getMock() 90 | ], 91 | [ 92 | 'NonObject', 93 | 'NonObject', 94 | $this->getMockBuilder(SourcePointInterface::class)->getMock() 95 | ], 96 | ]; 97 | } 98 | 99 | private function createIterableRecursionCheckPoint( 100 | string $sourceFqn, 101 | string $targetFqn, 102 | SourcePointInterface $targetSourcePoint 103 | ): IterableRecursionCheckPoint { 104 | return new IterableRecursionCheckPoint( 105 | $sourceFqn, 106 | $targetFqn, 107 | $targetSourcePoint 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/Point/MethodDynamicSourcePointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\DynamicSourcePointInterface; 16 | use Opportus\ObjectMapper\Point\MethodDynamicSourcePoint; 17 | use Opportus\ObjectMapper\Point\ObjectPointInterface; 18 | use Opportus\ObjectMapper\Point\SourcePointInterface; 19 | use Opportus\ObjectMapper\Tests\Test; 20 | use Opportus\ObjectMapper\Tests\TestInvalidArgumentException; 21 | 22 | /** 23 | * The method dynamic source point test. 24 | * 25 | * @package Opportus\ObjectMapper\Tests\Point 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class MethodDynamicSourcePointTest extends Test 30 | { 31 | private const FQN_REGEX_PATTERN = '/^~?([A-Za-z0-9\\\_]+)::([A-Za-z0-9_]+)\(\)$/'; 32 | 33 | /** 34 | * @dataProvider provideMethodDynamicSourcePointFqn 35 | */ 36 | public function testConstruct(string $fqn): void 37 | { 38 | $point = $this->createMethodDynamicSourcePoint($fqn); 39 | 40 | static::assertInstanceOf(MethodDynamicSourcePoint::class, $point); 41 | static::assertInstanceOf(DynamicSourcePointInterface::class, $point); 42 | static::assertInstanceOf(SourcePointInterface::class, $point); 43 | static::assertInstanceOf(ObjectPointInterface::class, $point); 44 | } 45 | 46 | /** 47 | * @dataProvider provideInvalidMethodDynamicSourcePointFqn 48 | */ 49 | public function testConstructException(string $fqn): void 50 | { 51 | $this->expectException(InvalidArgumentException::class); 52 | 53 | $this->createMethodDynamicSourcePoint($fqn); 54 | } 55 | 56 | /** 57 | * @dataProvider provideMethodDynamicSourcePointFqn 58 | */ 59 | public function testGetFqn(string $fqn): void 60 | { 61 | $point = $this->createMethodDynamicSourcePoint($fqn); 62 | 63 | static::assertMatchesRegularExpression(self::FQN_REGEX_PATTERN, $point->getFqn()); 64 | 65 | static::assertSame( 66 | \sprintf( 67 | '~%s::%s()', 68 | $this->getPointSourceFqn($fqn), 69 | $this->getPointName($fqn) 70 | ), 71 | $point->getFqn() 72 | ); 73 | } 74 | 75 | /** 76 | * @dataProvider provideMethodDynamicSourcePointFqn 77 | */ 78 | public function testGetTargetFqn(string $fqn): void 79 | { 80 | $point = $this->createMethodDynamicSourcePoint($fqn); 81 | 82 | static::assertSame( 83 | $point->getSourceFqn(), 84 | $this->getPointSourceFqn($fqn) 85 | ); 86 | } 87 | 88 | /** 89 | * @dataProvider provideMethodDynamicSourcePointFqn 90 | */ 91 | public function testGetName(string $fqn): void 92 | { 93 | $point = $this->createMethodDynamicSourcePoint($fqn); 94 | 95 | static::assertSame($point->getName(), $this->getPointName($fqn)); 96 | } 97 | 98 | private function getPointSourceFqn(string $fqn): string 99 | { 100 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 101 | $message = \sprintf( 102 | 'The argument must match method dynamic source point FQN regex pattern %s, got %s.', 103 | self::FQN_REGEX_PATTERN, 104 | $fqn 105 | ); 106 | 107 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 108 | } 109 | 110 | return $matches[1]; 111 | } 112 | 113 | private function getPointName(string $fqn): string 114 | { 115 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 116 | $message = \sprintf( 117 | 'The argument must match method dynamic source point FQN regex pattern %s, got %s.', 118 | self::FQN_REGEX_PATTERN, 119 | $fqn 120 | ); 121 | 122 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 123 | } 124 | 125 | return $matches[2]; 126 | } 127 | 128 | private function createMethodDynamicSourcePoint( 129 | string $fqn 130 | ): MethodDynamicSourcePoint { 131 | return new MethodDynamicSourcePoint($fqn); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/Point/MethodParameterStaticTargetPointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\MethodParameterStaticTargetPoint; 16 | use Opportus\ObjectMapper\Point\ObjectPointInterface; 17 | use Opportus\ObjectMapper\Point\StaticTargetPointInterface; 18 | use Opportus\ObjectMapper\Point\TargetPointInterface; 19 | use Opportus\ObjectMapper\Tests\Test; 20 | use Opportus\ObjectMapper\Tests\TestInvalidArgumentException; 21 | 22 | /** 23 | * The method parameter static target point test. 24 | * 25 | * @package Opportus\ObjectMapper\Tests\Point 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class MethodParameterStaticTargetPointTest extends Test 30 | { 31 | private const FQN_REGEX_PATTERN = '/^#?([A-Za-z0-9\\\_]+)::([A-Za-z0-9_]+)\(\)::\$([A-Za-z0-9_]+)$/'; 32 | 33 | /** 34 | * @dataProvider provideMethodParameterStaticTargetPointFqn 35 | */ 36 | public function testConstruct(string $fqn): void 37 | { 38 | $point = $this->createMethodParameterStaticTargetPoint($fqn); 39 | 40 | static::assertInstanceOf(MethodParameterStaticTargetPoint::class, $point); 41 | static::assertInstanceOf(StaticTargetPointInterface::class, $point); 42 | static::assertInstanceOf(TargetPointInterface::class, $point); 43 | static::assertInstanceOf(ObjectPointInterface::class, $point); 44 | } 45 | 46 | /** 47 | * @dataProvider provideInvalidMethodParameterStaticTargetPointFqn 48 | */ 49 | public function testConstructException(string $fqn): void 50 | { 51 | $this->expectException(InvalidArgumentException::class); 52 | 53 | $this->createMethodParameterStaticTargetPoint($fqn); 54 | } 55 | 56 | /** 57 | * @dataProvider provideMethodParameterStaticTargetPointFqn 58 | */ 59 | public function testGetFqn(string $fqn): void 60 | { 61 | $point = $this->createMethodParameterStaticTargetPoint($fqn); 62 | 63 | static::assertMatchesRegularExpression(self::FQN_REGEX_PATTERN, $point->getFqn()); 64 | 65 | static::assertSame( 66 | \sprintf( 67 | '#%s::%s()::$%s', 68 | $this->getPointTargetFqn($fqn), 69 | $this->getPointMethodName($fqn), 70 | $this->getPointName($fqn) 71 | ), 72 | $point->getFqn() 73 | ); 74 | } 75 | 76 | /** 77 | * @dataProvider provideMethodParameterStaticTargetPointFqn 78 | */ 79 | public function testGetTargetFqn(string $fqn): void 80 | { 81 | $point = $this->createMethodParameterStaticTargetPoint($fqn); 82 | 83 | static::assertSame( 84 | $point->getTargetFqn(), 85 | $this->getPointTargetFqn($fqn) 86 | ); 87 | } 88 | 89 | /** 90 | * @dataProvider provideMethodParameterStaticTargetPointFqn 91 | */ 92 | public function testGetName(string $fqn): void 93 | { 94 | $point = $this->createMethodParameterStaticTargetPoint($fqn); 95 | 96 | static::assertSame($point->getName(), $this->getPointName($fqn)); 97 | } 98 | 99 | /** 100 | * @dataProvider provideMethodParameterStaticTargetPointFqn 101 | */ 102 | public function testGetMethodName(string $fqn): void 103 | { 104 | $point = $this->createMethodParameterStaticTargetPoint($fqn); 105 | 106 | static::assertSame( 107 | $point->getMethodName(), 108 | $this->getPointMethodName($fqn) 109 | ); 110 | } 111 | 112 | private function getPointTargetFqn(string $fqn): string 113 | { 114 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 115 | $message = \sprintf( 116 | 'The argument must match method parameter static target point FQN regex pattern %s, got %s.', 117 | self::FQN_REGEX_PATTERN, 118 | $fqn 119 | ); 120 | 121 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 122 | } 123 | 124 | return $matches[1]; 125 | } 126 | 127 | private function getPointMethodName(string $fqn): string 128 | { 129 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 130 | $message = \sprintf( 131 | 'The argument must match method parameter static target point FQN regex pattern %s, got %s.', 132 | self::FQN_REGEX_PATTERN, 133 | $fqn 134 | ); 135 | 136 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 137 | } 138 | 139 | return $matches[2]; 140 | } 141 | 142 | private function getPointName(string $fqn): string 143 | { 144 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 145 | $message = \sprintf( 146 | 'The argument must match method parameter static target point FQN regex pattern %s, got %s.', 147 | self::FQN_REGEX_PATTERN, 148 | $fqn 149 | ); 150 | 151 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 152 | } 153 | 154 | return $matches[3]; 155 | } 156 | 157 | private function createMethodParameterStaticTargetPoint( 158 | string $fqn 159 | ): MethodParameterStaticTargetPoint { 160 | return new MethodParameterStaticTargetPoint($fqn); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /tests/Point/MethodStaticSourcePointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\MethodStaticSourcePoint; 16 | use Opportus\ObjectMapper\Point\ObjectPointInterface; 17 | use Opportus\ObjectMapper\Point\SourcePointInterface; 18 | use Opportus\ObjectMapper\Point\StaticSourcePointInterface; 19 | use Opportus\ObjectMapper\Tests\Test; 20 | use Opportus\ObjectMapper\Tests\TestInvalidArgumentException; 21 | 22 | /** 23 | * The method static source point test. 24 | * 25 | * @package Opportus\ObjectMapper\Tests\Point 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class MethodStaticSourcePointTest extends Test 30 | { 31 | private const FQN_REGEX_PATTERN = '/^#?([A-Za-z0-9\\\_]+)::([A-Za-z0-9_]+)\(\)$/'; 32 | 33 | /** 34 | * @dataProvider provideMethodStaticSourcePointFqn 35 | */ 36 | public function testConstruct(string $fqn): void 37 | { 38 | $point = $this->createMethodStaticSourcePoint($fqn); 39 | 40 | static::assertInstanceOf(MethodStaticSourcePoint::class, $point); 41 | static::assertInstanceOf(StaticSourcePointInterface::class, $point); 42 | static::assertInstanceOf(SourcePointInterface::class, $point); 43 | static::assertInstanceOf(ObjectPointInterface::class, $point); 44 | } 45 | 46 | /** 47 | * @dataProvider provideInvalidMethodStaticSourcePointFqn 48 | */ 49 | public function testConstructException(string $fqn): void 50 | { 51 | $this->expectException(InvalidArgumentException::class); 52 | 53 | $this->createMethodStaticSourcePoint($fqn); 54 | } 55 | 56 | /** 57 | * @dataProvider provideMethodStaticSourcePointFqn 58 | */ 59 | public function testGetFqn(string $fqn): void 60 | { 61 | $point = $this->createMethodStaticSourcePoint($fqn); 62 | 63 | static::assertMatchesRegularExpression(self::FQN_REGEX_PATTERN, $point->getFqn()); 64 | 65 | static::assertSame( 66 | \sprintf( 67 | '#%s::%s()', 68 | $this->getPointSourceFqn($fqn), 69 | $this->getPointName($fqn) 70 | ), 71 | $point->getFqn() 72 | ); 73 | } 74 | 75 | /** 76 | * @dataProvider provideMethodStaticSourcePointFqn 77 | */ 78 | public function testGetTargetFqn(string $fqn): void 79 | { 80 | $point = $this->createMethodStaticSourcePoint($fqn); 81 | 82 | static::assertSame( 83 | $point->getSourceFqn(), 84 | $this->getPointSourceFqn($fqn) 85 | ); 86 | } 87 | 88 | /** 89 | * @dataProvider provideMethodStaticSourcePointFqn 90 | */ 91 | public function testGetName(string $fqn): void 92 | { 93 | $point = $this->createMethodStaticSourcePoint($fqn); 94 | 95 | static::assertSame($point->getName(), $this->getPointName($fqn)); 96 | } 97 | 98 | private function getPointSourceFqn(string $fqn): string 99 | { 100 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 101 | $message = \sprintf( 102 | 'The argument must match method static source point FQN regex pattern %s, got %s.', 103 | self::FQN_REGEX_PATTERN, 104 | $fqn 105 | ); 106 | 107 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 108 | } 109 | 110 | return $matches[1]; 111 | } 112 | 113 | private function getPointName(string $fqn): string 114 | { 115 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 116 | $message = \sprintf( 117 | 'The argument must match method static source point FQN regex pattern %s, got %s.', 118 | self::FQN_REGEX_PATTERN, 119 | $fqn 120 | ); 121 | 122 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 123 | } 124 | 125 | return $matches[2]; 126 | } 127 | 128 | private function createMethodStaticSourcePoint( 129 | string $fqn 130 | ): MethodStaticSourcePoint { 131 | return new MethodStaticSourcePoint($fqn); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/Point/PropertyDynamicSourcePointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\DynamicSourcePointInterface; 16 | use Opportus\ObjectMapper\Point\PropertyDynamicSourcePoint; 17 | use Opportus\ObjectMapper\Point\ObjectPointInterface; 18 | use Opportus\ObjectMapper\Point\SourcePointInterface; 19 | use Opportus\ObjectMapper\Tests\Test; 20 | use Opportus\ObjectMapper\Tests\TestInvalidArgumentException; 21 | 22 | /** 23 | * The property dynamic source point test. 24 | * 25 | * @package Opportus\ObjectMapper\Tests\Point 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class PropertyDynamicSourcePointTest extends Test 30 | { 31 | private const FQN_REGEX_PATTERN = '/^~?([A-Za-z0-9\\\_]+)::\$([A-Za-z0-9_]+)$/'; 32 | 33 | /** 34 | * @dataProvider providePropertyDynamicSourcePointFqn 35 | */ 36 | public function testConstruct(string $fqn): void 37 | { 38 | $point = $this->createPropertyDynamicSourcePoint($fqn); 39 | 40 | static::assertInstanceOf(PropertyDynamicSourcePoint::class, $point); 41 | static::assertInstanceOf(DynamicSourcePointInterface::class, $point); 42 | static::assertInstanceOf(SourcePointInterface::class, $point); 43 | static::assertInstanceOf(ObjectPointInterface::class, $point); 44 | } 45 | 46 | /** 47 | * @dataProvider provideInvalidPropertyDynamicSourcePointFqn 48 | */ 49 | public function testConstructException(string $fqn): void 50 | { 51 | $this->expectException(InvalidArgumentException::class); 52 | 53 | $this->createPropertyDynamicSourcePoint($fqn); 54 | } 55 | 56 | /** 57 | * @dataProvider providePropertyDynamicSourcePointFqn 58 | */ 59 | public function testGetFqn(string $fqn): void 60 | { 61 | $point = $this->createPropertyDynamicSourcePoint($fqn); 62 | 63 | static::assertMatchesRegularExpression(self::FQN_REGEX_PATTERN, $point->getFqn()); 64 | 65 | static::assertSame( 66 | \sprintf( 67 | '~%s::$%s', 68 | $this->getPointSourceFqn($fqn), 69 | $this->getPointName($fqn) 70 | ), 71 | $point->getFqn() 72 | ); 73 | } 74 | 75 | /** 76 | * @dataProvider providePropertyDynamicSourcePointFqn 77 | */ 78 | public function testGetTargetFqn(string $fqn): void 79 | { 80 | $point = $this->createPropertyDynamicSourcePoint($fqn); 81 | 82 | static::assertSame( 83 | $point->getSourceFqn(), 84 | $this->getPointSourceFqn($fqn) 85 | ); 86 | } 87 | 88 | /** 89 | * @dataProvider providePropertyDynamicSourcePointFqn 90 | */ 91 | public function testGetName(string $fqn): void 92 | { 93 | $point = $this->createPropertyDynamicSourcePoint($fqn); 94 | 95 | static::assertSame($point->getName(), $this->getPointName($fqn)); 96 | } 97 | 98 | private function getPointSourceFqn(string $fqn): string 99 | { 100 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 101 | $message = \sprintf( 102 | 'The argument must match property dynamic source point FQN regex pattern %s, got %s.', 103 | self::FQN_REGEX_PATTERN, 104 | $fqn 105 | ); 106 | 107 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 108 | } 109 | 110 | return $matches[1]; 111 | } 112 | 113 | private function getPointName(string $fqn): string 114 | { 115 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 116 | $message = \sprintf( 117 | 'The argument must match property dynamic source point FQN regex pattern %s, got %s.', 118 | self::FQN_REGEX_PATTERN, 119 | $fqn 120 | ); 121 | 122 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 123 | } 124 | 125 | return $matches[2]; 126 | } 127 | 128 | private function createPropertyDynamicSourcePoint( 129 | string $fqn 130 | ): PropertyDynamicSourcePoint { 131 | return new PropertyDynamicSourcePoint($fqn); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/Point/PropertyDynamicTargetPointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\DynamicTargetPointInterface; 16 | use Opportus\ObjectMapper\Point\PropertyDynamicTargetPoint; 17 | use Opportus\ObjectMapper\Point\ObjectPointInterface; 18 | use Opportus\ObjectMapper\Point\TargetPointInterface; 19 | use Opportus\ObjectMapper\Tests\Test; 20 | use Opportus\ObjectMapper\Tests\TestInvalidArgumentException; 21 | 22 | /** 23 | * The property dynamic target point test. 24 | * 25 | * @package Opportus\ObjectMapper\Tests\Point 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class PropertyDynamicTargetPointTest extends Test 30 | { 31 | private const FQN_REGEX_PATTERN = '/^~?([A-Za-z0-9\\\_]+)::\$([A-Za-z0-9_]+)$/'; 32 | 33 | /** 34 | * @dataProvider providePropertyDynamicTargetPointFqn 35 | */ 36 | public function testConstruct(string $fqn): void 37 | { 38 | $point = $this->createPropertyDynamicTargetPoint($fqn); 39 | 40 | static::assertInstanceOf(PropertyDynamicTargetPoint::class, $point); 41 | static::assertInstanceOf(DynamicTargetPointInterface::class, $point); 42 | static::assertInstanceOf(TargetPointInterface::class, $point); 43 | static::assertInstanceOf(ObjectPointInterface::class, $point); 44 | } 45 | 46 | /** 47 | * @dataProvider provideInvalidPropertyDynamicTargetPointFqn 48 | */ 49 | public function testConstructException(string $fqn): void 50 | { 51 | $this->expectException(InvalidArgumentException::class); 52 | 53 | $this->createPropertyDynamicTargetPoint($fqn); 54 | } 55 | 56 | /** 57 | * @dataProvider providePropertyDynamicTargetPointFqn 58 | */ 59 | public function testGetFqn(string $fqn): void 60 | { 61 | $point = $this->createPropertyDynamicTargetPoint($fqn); 62 | 63 | static::assertMatchesRegularExpression(self::FQN_REGEX_PATTERN, $point->getFqn()); 64 | 65 | static::assertSame( 66 | \sprintf( 67 | '~%s::$%s', 68 | $this->getPointTargetFqn($fqn), 69 | $this->getPointName($fqn) 70 | ), 71 | $point->getFqn() 72 | ); 73 | } 74 | 75 | /** 76 | * @dataProvider providePropertyDynamicTargetPointFqn 77 | */ 78 | public function testGetTargetFqn(string $fqn): void 79 | { 80 | $point = $this->createPropertyDynamicTargetPoint($fqn); 81 | 82 | static::assertSame( 83 | $point->getTargetFqn(), 84 | $this->getPointTargetFqn($fqn) 85 | ); 86 | } 87 | 88 | /** 89 | * @dataProvider providePropertyDynamicTargetPointFqn 90 | */ 91 | public function testGetName(string $fqn): void 92 | { 93 | $point = $this->createPropertyDynamicTargetPoint($fqn); 94 | 95 | static::assertSame($point->getName(), $this->getPointName($fqn)); 96 | } 97 | 98 | private function getPointTargetFqn(string $fqn): string 99 | { 100 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 101 | $message = \sprintf( 102 | 'The argument must match property dynamic target point FQN regex pattern %s, got %s.', 103 | self::FQN_REGEX_PATTERN, 104 | $fqn 105 | ); 106 | 107 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 108 | } 109 | 110 | return $matches[1]; 111 | } 112 | 113 | private function getPointName(string $fqn): string 114 | { 115 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 116 | $message = \sprintf( 117 | 'The argument must match property dynamic target point FQN regex pattern %s, got %s.', 118 | self::FQN_REGEX_PATTERN, 119 | $fqn 120 | ); 121 | 122 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 123 | } 124 | 125 | return $matches[2]; 126 | } 127 | 128 | private function createPropertyDynamicTargetPoint( 129 | string $fqn 130 | ): PropertyDynamicTargetPoint { 131 | return new PropertyDynamicTargetPoint($fqn); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/Point/PropertyStaticSourcePointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\PropertyStaticSourcePoint; 16 | use Opportus\ObjectMapper\Point\ObjectPointInterface; 17 | use Opportus\ObjectMapper\Point\SourcePointInterface; 18 | use Opportus\ObjectMapper\Point\StaticSourcePointInterface; 19 | use Opportus\ObjectMapper\Tests\Test; 20 | use Opportus\ObjectMapper\Tests\TestInvalidArgumentException; 21 | 22 | /** 23 | * The property static source point test. 24 | * 25 | * @package Opportus\ObjectMapper\Tests\Point 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class PropertyStaticSourcePointTest extends Test 30 | { 31 | private const FQN_REGEX_PATTERN = '/^#?([A-Za-z0-9\\\_]+)::\$([A-Za-z0-9_]+)$/'; 32 | 33 | /** 34 | * @dataProvider providePropertyStaticSourcePointFqn 35 | */ 36 | public function testConstruct(string $fqn): void 37 | { 38 | $point = $this->createPropertyStaticSourcePoint($fqn); 39 | 40 | static::assertInstanceOf(PropertyStaticSourcePoint::class, $point); 41 | static::assertInstanceOf(StaticSourcePointInterface::class, $point); 42 | static::assertInstanceOf(SourcePointInterface::class, $point); 43 | static::assertInstanceOf(ObjectPointInterface::class, $point); 44 | } 45 | 46 | /** 47 | * @dataProvider provideInvalidPropertyStaticSourcePointFqn 48 | */ 49 | public function testConstructException(string $fqn): void 50 | { 51 | $this->expectException(InvalidArgumentException::class); 52 | 53 | $this->createPropertyStaticSourcePoint($fqn); 54 | } 55 | 56 | /** 57 | * @dataProvider providePropertyStaticSourcePointFqn 58 | */ 59 | public function testGetFqn(string $fqn): void 60 | { 61 | $point = $this->createPropertyStaticSourcePoint($fqn); 62 | 63 | static::assertMatchesRegularExpression(self::FQN_REGEX_PATTERN, $point->getFqn()); 64 | 65 | static::assertSame( 66 | \sprintf( 67 | '#%s::$%s', 68 | $this->getPointSourceFqn($fqn), 69 | $this->getPointName($fqn) 70 | ), 71 | $point->getFqn() 72 | ); 73 | } 74 | 75 | /** 76 | * @dataProvider providePropertyStaticSourcePointFqn 77 | */ 78 | public function testGetTargetFqn(string $fqn): void 79 | { 80 | $point = $this->createPropertyStaticSourcePoint($fqn); 81 | 82 | static::assertSame( 83 | $point->getSourceFqn(), 84 | $this->getPointSourceFqn($fqn) 85 | ); 86 | } 87 | 88 | /** 89 | * @dataProvider providePropertyStaticSourcePointFqn 90 | */ 91 | public function testGetName(string $fqn): void 92 | { 93 | $point = $this->createPropertyStaticSourcePoint($fqn); 94 | 95 | static::assertSame($point->getName(), $this->getPointName($fqn)); 96 | } 97 | 98 | private function getPointSourceFqn(string $fqn): string 99 | { 100 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 101 | $message = \sprintf( 102 | 'The argument must match property static source point FQN regex pattern %s, got %s.', 103 | self::FQN_REGEX_PATTERN, 104 | $fqn 105 | ); 106 | 107 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 108 | } 109 | 110 | return $matches[1]; 111 | } 112 | 113 | private function getPointName(string $fqn): string 114 | { 115 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 116 | $message = \sprintf( 117 | 'The argument must match property static source point FQN regex pattern %s, got %s.', 118 | self::FQN_REGEX_PATTERN, 119 | $fqn 120 | ); 121 | 122 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 123 | } 124 | 125 | return $matches[2]; 126 | } 127 | 128 | private function createPropertyStaticSourcePoint( 129 | string $fqn 130 | ): PropertyStaticSourcePoint { 131 | return new PropertyStaticSourcePoint($fqn); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/Point/PropertyStaticTargetPointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\PropertyStaticTargetPoint; 16 | use Opportus\ObjectMapper\Point\ObjectPointInterface; 17 | use Opportus\ObjectMapper\Point\StaticTargetPointInterface; 18 | use Opportus\ObjectMapper\Point\TargetPointInterface; 19 | use Opportus\ObjectMapper\Tests\Test; 20 | use Opportus\ObjectMapper\Tests\TestInvalidArgumentException; 21 | 22 | /** 23 | * The property static target point test. 24 | * 25 | * @package Opportus\ObjectMapper\Tests\Point 26 | * @author Clément Cazaud 27 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 28 | */ 29 | class PropertyStaticTargetPointTest extends Test 30 | { 31 | private const FQN_REGEX_PATTERN = '/^#?([A-Za-z0-9\\\_]+)::\$([A-Za-z0-9_]+)$/'; 32 | 33 | /** 34 | * @dataProvider providePropertyStaticTargetPointFqn 35 | */ 36 | public function testConstruct(string $fqn): void 37 | { 38 | $point = $this->createPropertyStaticTargetPoint($fqn); 39 | 40 | static::assertInstanceOf(PropertyStaticTargetPoint::class, $point); 41 | static::assertInstanceOf(StaticTargetPointInterface::class, $point); 42 | static::assertInstanceOf(TargetPointInterface::class, $point); 43 | static::assertInstanceOf(ObjectPointInterface::class, $point); 44 | } 45 | 46 | /** 47 | * @dataProvider provideInvalidPropertyStaticTargetPointFqn 48 | */ 49 | public function testConstructException(string $fqn): void 50 | { 51 | $this->expectException(InvalidArgumentException::class); 52 | 53 | $this->createPropertyStaticTargetPoint($fqn); 54 | } 55 | 56 | /** 57 | * @dataProvider providePropertyStaticTargetPointFqn 58 | */ 59 | public function testGetFqn(string $fqn): void 60 | { 61 | $point = $this->createPropertyStaticTargetPoint($fqn); 62 | 63 | static::assertMatchesRegularExpression(self::FQN_REGEX_PATTERN, $point->getFqn()); 64 | 65 | static::assertSame( 66 | \sprintf( 67 | '#%s::$%s', 68 | $this->getPointTargetFqn($fqn), 69 | $this->getPointName($fqn) 70 | ), 71 | $point->getFqn() 72 | ); 73 | } 74 | 75 | /** 76 | * @dataProvider providePropertyStaticTargetPointFqn 77 | */ 78 | public function testGetTargetFqn(string $fqn): void 79 | { 80 | $point = $this->createPropertyStaticTargetPoint($fqn); 81 | 82 | static::assertSame( 83 | $point->getTargetFqn(), 84 | $this->getPointTargetFqn($fqn) 85 | ); 86 | } 87 | 88 | /** 89 | * @dataProvider providePropertyStaticTargetPointFqn 90 | */ 91 | public function testGetName(string $fqn): void 92 | { 93 | $point = $this->createPropertyStaticTargetPoint($fqn); 94 | 95 | static::assertSame($point->getName(), $this->getPointName($fqn)); 96 | } 97 | 98 | private function getPointTargetFqn(string $fqn): string 99 | { 100 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 101 | $message = \sprintf( 102 | 'The argument must match property static target point FQN regex pattern %s, got %s.', 103 | self::FQN_REGEX_PATTERN, 104 | $fqn 105 | ); 106 | 107 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 108 | } 109 | 110 | return $matches[1]; 111 | } 112 | 113 | private function getPointName(string $fqn): string 114 | { 115 | if (false === \preg_match(self::FQN_REGEX_PATTERN, $fqn, $matches)) { 116 | $message = \sprintf( 117 | 'The argument must match property static target point FQN regex pattern %s, got %s.', 118 | self::FQN_REGEX_PATTERN, 119 | $fqn 120 | ); 121 | 122 | throw new TestInvalidArgumentException(1, __METHOD__, $message); 123 | } 124 | 125 | return $matches[2]; 126 | } 127 | 128 | private function createPropertyStaticTargetPoint( 129 | string $fqn 130 | ): PropertyStaticTargetPoint { 131 | return new PropertyStaticTargetPoint($fqn); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/Point/RecursionCheckPointTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Point; 13 | 14 | use Opportus\ObjectMapper\Exception\InvalidArgumentException; 15 | use Opportus\ObjectMapper\Point\CheckPointInterface; 16 | use Opportus\ObjectMapper\Point\RecursionCheckPoint; 17 | use Opportus\ObjectMapper\Point\SourcePointInterface; 18 | use Opportus\ObjectMapper\Tests\Test; 19 | use Opportus\ObjectMapper\Tests\TestObjectA; 20 | use Opportus\ObjectMapper\Tests\TestObjectB; 21 | 22 | /** 23 | * The recursion check point test. 24 | * 25 | * @todo Test Control method 26 | * 27 | * @package Opportus\ObjectMapper\Tests\Point 28 | * @author Clément Cazaud 29 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 30 | */ 31 | class RecursionCheckPointTest extends Test 32 | { 33 | /** 34 | * @dataProvider provideConstructArguments 35 | */ 36 | public function testConstruct( 37 | string $sourceFqn, 38 | string $targetFqn, 39 | SourcePointInterface $targetSourcePoint 40 | ): void { 41 | $point = $this->createRecursionCheckPoint( 42 | $sourceFqn, 43 | $targetFqn, 44 | $targetSourcePoint 45 | ); 46 | 47 | static::assertInstanceOf(CheckPointInterface::class, $point); 48 | } 49 | 50 | /** 51 | * @dataProvider provideConstructInvalidArguments 52 | */ 53 | public function testConstructException( 54 | string $sourceFqn, 55 | string $targetFqn, 56 | SourcePointInterface $targetSourcePoint 57 | ): void { 58 | $this->expectException(InvalidArgumentException::class); 59 | 60 | $this->createRecursionCheckPoint( 61 | $sourceFqn, 62 | $targetFqn, 63 | $targetSourcePoint 64 | ); 65 | } 66 | 67 | public function provideConstructArguments(): array 68 | { 69 | return [ 70 | [ 71 | TestObjectA::class, 72 | TestObjectB::class, 73 | $this->getMockBuilder(SourcePointInterface::class)->getMock(), 74 | ], 75 | ]; 76 | } 77 | 78 | public function provideConstructInvalidArguments(): array 79 | { 80 | return [ 81 | [ 82 | TestObjectA::class, 83 | 'NonObject', 84 | $this->getMockBuilder(SourcePointInterface::class)->getMock(), 85 | ], 86 | [ 87 | 'NonObject', 88 | TestObjectB::class, 89 | $this->getMockBuilder(SourcePointInterface::class)->getMock(), 90 | ], 91 | [ 92 | 'NonObject', 93 | 'NonObject', 94 | $this->getMockBuilder(SourcePointInterface::class)->getMock(), 95 | ], 96 | ]; 97 | } 98 | 99 | private function createRecursionCheckPoint( 100 | string $sourceFqn, 101 | string $targetFqn, 102 | SourcePointInterface $targetSourcePoint 103 | ): RecursionCheckPoint { 104 | return new RecursionCheckPoint( 105 | $sourceFqn, 106 | $targetFqn, 107 | $targetSourcePoint 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/Route/RouteTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests\Route; 13 | 14 | use Opportus\ObjectMapper\Point\CheckPointCollection; 15 | use Opportus\ObjectMapper\Point\SourcePointInterface; 16 | use Opportus\ObjectMapper\Point\TargetPointInterface; 17 | use Opportus\ObjectMapper\Route\Route; 18 | use Opportus\ObjectMapper\Route\RouteInterface; 19 | use Opportus\ObjectMapper\Tests\Test; 20 | 21 | /** 22 | * The route test. 23 | * 24 | * @package Opportus\ObjectMapper\Tests\Route 25 | * @author Clément Cazaud 26 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 27 | */ 28 | class RouteTest extends Test 29 | { 30 | /** 31 | * @dataProvider provideRoutePoints 32 | */ 33 | public function testConstruct( 34 | SourcePointInterface $sourcePoint, 35 | TargetPointInterface $targetPoint, 36 | CheckPointCollection $checkPoints 37 | ): void { 38 | $route = $this->createRoute($sourcePoint, $targetPoint, $checkPoints); 39 | 40 | static::assertInstanceOf(RouteInterface::class, $route); 41 | } 42 | 43 | /** 44 | * @dataProvider provideRoutePoints 45 | */ 46 | public function testGetFqn( 47 | SourcePointInterface $sourcePoint, 48 | TargetPointInterface $targetPoint, 49 | CheckPointCollection $checkPoints 50 | ): void { 51 | $route = $this->createRoute($sourcePoint, $targetPoint, $checkPoints); 52 | 53 | static::assertSame( 54 | \sprintf('%s=>%s', $sourcePoint->getFqn(), $targetPoint->getFqn()), 55 | $route->getFqn() 56 | ); 57 | } 58 | 59 | /** 60 | * @dataProvider provideRoutePoints 61 | */ 62 | public function testGetSourcePoint( 63 | SourcePointInterface $sourcePoint, 64 | TargetPointInterface $targetPoint, 65 | CheckPointCollection $checkPoints 66 | ): void { 67 | $route = $this->createRoute($sourcePoint, $targetPoint, $checkPoints); 68 | 69 | static::assertInstanceOf( 70 | \get_class($sourcePoint), 71 | $route->getSourcePoint() 72 | ); 73 | 74 | static::assertSame( 75 | $sourcePoint->getFqn(), 76 | $route->getSourcePoint()->getFqn() 77 | ); 78 | } 79 | 80 | /** 81 | * @dataProvider provideRoutePoints 82 | */ 83 | public function testGetTargetPoint( 84 | SourcePointInterface $sourcePoint, 85 | TargetPointInterface $targetPoint, 86 | CheckPointCollection $checkPoints 87 | ): void { 88 | $route = $this->createRoute($sourcePoint, $targetPoint, $checkPoints); 89 | 90 | static::assertInstanceOf( 91 | \get_class($targetPoint), 92 | $route->getTargetPoint() 93 | ); 94 | 95 | static::assertSame( 96 | $targetPoint->getFqn(), 97 | $route->getTargetPoint()->getFqn() 98 | ); 99 | } 100 | 101 | /** 102 | * @dataProvider provideRoutePoints 103 | */ 104 | public function testGetCheckPoints( 105 | SourcePointInterface $sourcePoint, 106 | TargetPointInterface $targetPoint, 107 | CheckPointCollection $checkPoints 108 | ): void { 109 | $route = $this->createRoute($sourcePoint, $targetPoint, $checkPoints); 110 | 111 | static::assertCount(\count($checkPoints), $route->getCheckPoints()); 112 | static::assertSame($checkPoints, $route->getCheckPoints()); 113 | } 114 | 115 | private function createRoute( 116 | SourcePointInterface $sourcePoint, 117 | TargetPointInterface $targetPoint, 118 | CheckPointCollection $checkPoints 119 | ): Route { 120 | return new Route($sourcePoint, $targetPoint, $checkPoints); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/Test.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests; 13 | 14 | use Opportus\ObjectMapper\Map\MapBuilder; 15 | use Opportus\ObjectMapper\ObjectMapper; 16 | use Opportus\ObjectMapper\Point\PointFactory; 17 | use Opportus\ObjectMapper\Route\RouteBuilder; 18 | use PHPUnit\Framework\TestCase; 19 | 20 | /** 21 | * The test. 22 | * 23 | * @package Opportus\ObjectMapper\Tests 24 | * @author Clément Cazaud 25 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 26 | */ 27 | abstract class Test extends TestCase 28 | { 29 | use TestDataProviderTrait; 30 | 31 | protected function createPointFactory(): PointFactory 32 | { 33 | return new PointFactory(); 34 | } 35 | 36 | protected function createRouteBuilder(): RouteBuilder 37 | { 38 | return new RouteBuilder( 39 | $this->createPointFactory() 40 | ); 41 | } 42 | 43 | protected function createMapBuilder(): MapBuilder 44 | { 45 | return new MapBuilder( 46 | $this->createRouteBuilder() 47 | ); 48 | } 49 | 50 | protected function createObjectMapper(): ObjectMapper 51 | { 52 | return new ObjectMapper( 53 | $this->createMapBuilder() 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/TestInvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests; 13 | 14 | use Exception; 15 | use Throwable; 16 | 17 | /** 18 | * The test invalid argument exception. 19 | * 20 | * @package Opportus\ObjectMapper\Tests 21 | * @author Clément Cazaud 22 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 23 | */ 24 | class TestInvalidArgumentException extends Exception 25 | { 26 | /** 27 | * Constructs the invalid argument exception. 28 | * 29 | * @param int $argument 30 | * @param string $function 31 | * @param string $message 32 | * @param int $code 33 | * @param null|Throwable $previous 34 | */ 35 | public function __construct( 36 | int $argument, 37 | string $function, 38 | string $message, 39 | int $code = 0, 40 | Throwable $previous = null 41 | ) { 42 | $message = \sprintf( 43 | 'Argument %d passed to %s is invalid. %s', 44 | $argument, 45 | $function, 46 | $message 47 | ); 48 | 49 | parent::__construct($message, $code, $previous); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/TestObjectA.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests; 13 | 14 | /** 15 | * The test object A. 16 | * 17 | * @package Opportus\ObjectMapper\Tests 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | class TestObjectA 22 | { 23 | use TestObjectTrait; 24 | 25 | private $i; 26 | private $j; 27 | public $k; 28 | public $l; 29 | 30 | public function __construct( 31 | $a = null, 32 | $b = null, 33 | $c = null, 34 | $d = null, 35 | $e = null, 36 | $f = null, 37 | $g = null, 38 | $h = null 39 | ) { 40 | $this->initialize($a, $b, $c, $d, $e, $f, $g, $h); 41 | 42 | $this->m = 0; 43 | $this->n = 0; 44 | $this->o = 0; 45 | $this->p = 0; 46 | } 47 | 48 | public function getI() 49 | { 50 | return $this->i; 51 | } 52 | 53 | public function setI($i) 54 | { 55 | $this->i = $i; 56 | } 57 | 58 | public function getK() 59 | { 60 | return $this->k; 61 | } 62 | 63 | public function setK($k) 64 | { 65 | $this->k = $k; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/TestObjectB.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests; 13 | 14 | /** 15 | * The test object B. 16 | * 17 | * @package Opportus\ObjectMapper\Tests 18 | * @author Clément Cazaud 19 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 20 | */ 21 | class TestObjectB 22 | { 23 | use TestObjectTrait; 24 | 25 | private $m; 26 | private $n; 27 | public $o; 28 | public $p; 29 | 30 | public function __construct( 31 | $a = null, 32 | $b = null, 33 | $c = null, 34 | $d = null, 35 | $e = null, 36 | $f = null, 37 | $g = null, 38 | $h = null 39 | ) { 40 | $this->initialize($a, $b, $c, $d, $e, $f, $g, $h); 41 | 42 | $this->i = 0; 43 | $this->j = 0; 44 | $this->k = 0; 45 | $this->l = 0; 46 | } 47 | 48 | public function getM() 49 | { 50 | return $this->m; 51 | } 52 | 53 | public function setM($m) 54 | { 55 | $this->m = $m; 56 | } 57 | 58 | public function getO() 59 | { 60 | return $this->o; 61 | } 62 | 63 | public function setO($o) 64 | { 65 | $this->o = $o; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/TestObjectTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE file 9 | * that was distributed with this source code. 10 | */ 11 | 12 | namespace Opportus\ObjectMapper\Tests; 13 | 14 | use Exception; 15 | use ReflectionClass; 16 | use stdClass; 17 | 18 | /** 19 | * The test object trait. 20 | * 21 | * @package Opportus\ObjectMapper\Tests 22 | * @author Clément Cazaud 23 | * @license https://github.com/opportus/object-mapper/blob/master/LICENSE MIT 24 | */ 25 | trait TestObjectTrait 26 | { 27 | private $a; 28 | private $b; 29 | private $c; 30 | private $d; 31 | public $e; 32 | public $f; 33 | public $g; 34 | public $h; 35 | 36 | public function getA() 37 | { 38 | return $this->a; 39 | } 40 | 41 | public function setA($a) 42 | { 43 | $this->a = $a; 44 | } 45 | 46 | public function getB() 47 | { 48 | return $this->b; 49 | } 50 | 51 | public function setB($b) 52 | { 53 | $this->b = $b; 54 | } 55 | 56 | public function getC() 57 | { 58 | return $this->c; 59 | } 60 | 61 | public function setC($c) 62 | { 63 | $this->c = $c; 64 | } 65 | 66 | public function getD() 67 | { 68 | return $this->d; 69 | } 70 | 71 | public function setD($d) 72 | { 73 | $this->d = $d; 74 | } 75 | 76 | public function get($arg) 77 | { 78 | } 79 | 80 | public function __call(string $dynamicMethodName, array $arguments) 81 | { 82 | if (\preg_match('/^get([A-Z]1)$/', $dynamicMethodName, $matches)) { 83 | $property = \strtolower($matches[1]); 84 | 85 | return $this->{$property}; 86 | } elseif (\preg_match('/^set([A-Z]1)$/', $dynamicMethodName, $matches)) { 87 | $property = \strtolower($matches[1]); 88 | 89 | $this->{$property} = $arguments[0]; 90 | } 91 | } 92 | 93 | private function initialize( 94 | $a = null, 95 | $b = null, 96 | $c = null, 97 | $d = null, 98 | $e = null, 99 | $f = null, 100 | $g = null, 101 | $h = null 102 | ) { 103 | $class = new ReflectionClass(self::class); 104 | 105 | foreach ($class->getProperties() as $property) { 106 | $staticPropertyName = $property->getName(); 107 | $dynamicPropertyName = \sprintf('%s1', $property->getName()); 108 | 109 | if (isset(${$staticPropertyName})) { 110 | $value = ${$staticPropertyName}; 111 | } else { 112 | $value = 0; 113 | } 114 | 115 | $this->{$staticPropertyName} = $value; 116 | $this->{$dynamicPropertyName} = $value; 117 | } 118 | } 119 | } 120 | --------------------------------------------------------------------------------