├── .codeclimate.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── coverage.yml │ ├── php-cs-fixer.yml │ ├── phpunit.yml │ └── update-changelog.yml ├── .gitignore ├── .idea ├── .gitignore ├── blade.xml ├── inspectionProfiles │ └── Project_Default.xml ├── laravel-dumper.iml ├── laravel-idea-personal.xml ├── laravel-idea.xml ├── modules.xml ├── php-test-framework.xml ├── php.xml ├── phpunit.xml └── vcs.xml ├── .php-cs-fixer.dist.php ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── pre-commit.sh ├── composer.json ├── config.php ├── diffs ├── carbon-date.diff ├── container-nested.diff ├── container.diff ├── custom-caster-2.diff ├── custom-caster.diff ├── eloquent-builder.diff ├── eloquent-model.diff ├── eloquent-relation.diff ├── query-builder.diff ├── request.diff └── response.diff ├── phpunit.xml ├── src ├── Casters │ ├── BuilderCaster.php │ ├── CarbonCaster.php │ ├── Caster.php │ ├── ContainerCaster.php │ ├── CustomCaster.php │ ├── DatabaseConnectionCaster.php │ ├── HeaderBagCaster.php │ ├── ModelCaster.php │ ├── ParameterBagCaster.php │ ├── RequestCaster.php │ ├── ResponseCaster.php │ └── manifest.php ├── LaravelDumperServiceProvider.php ├── Support │ ├── Key.php │ └── Properties.php ├── autoload.php └── helpers.php └── tests ├── CasterTest.php ├── CustomCasterTest.php ├── DatabaseCasterTest.php ├── PropertiesTest.php ├── TestCase.php └── migrations └── 2022_01_18_131057_create_test_tables.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_patterns: 2 | - ".github/" 3 | - ".idea/" 4 | - "stubs/" 5 | - "tests/" 6 | - "**/vendor/" 7 | - "**/node_modules/" 8 | - "bin/" 9 | - "diffs/" 10 | - "*.md" 11 | - ".*.yml" 12 | - "LICENSE" 13 | - "composer.json" 14 | - "phpunit.xml" 15 | - "src/helpers.php" 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **What version does this affect?** 14 | - Laravel Version: [e.g. 5.8.0] 15 | - Package Version: [e.g. 1.5.0] 16 | 17 | **To Reproduce** 18 | Steps to reproduce the behavior: 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature idea or improvement 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | coverage: 10 | runs-on: ubuntu-latest 11 | 12 | name: Publish code coverage 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: 8.2 22 | extensions: dom, curl, libxml, mbstring, zip, pcntl, bcmath, intl, iconv 23 | coverage: pcov 24 | 25 | - name: Cache dependencies 26 | uses: actions/cache@v2 27 | with: 28 | path: | 29 | vendor 30 | ${{ steps.composer-cache-files-dir.outputs.dir }} 31 | key: ${{ runner.os }}-composer-${{ hashFiles('composer.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-composer- 34 | 35 | - name: Install dependencies 36 | env: 37 | COMPOSER_DISCARD_CHANGES: true 38 | run: composer require --no-suggest --no-progress --no-interaction --prefer-dist --update-with-all-dependencies "laravel/framework:^10.0" "orchestra/testbench:^8.0" 39 | 40 | - name: Run and publish code coverage 41 | uses: paambaati/codeclimate-action@v2.4.0 42 | env: 43 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 44 | with: 45 | coverageCommand: vendor/bin/phpunit --coverage-clover ${{ github.workspace }}/clover.xml 46 | debug: true 47 | coverageLocations: 48 | "${{github.workspace}}/clover.xml:clover" 49 | -------------------------------------------------------------------------------- /.github/workflows/php-cs-fixer.yml: -------------------------------------------------------------------------------- 1 | name: Code Style 2 | 3 | on: [ pull_request, push ] 4 | 5 | jobs: 6 | coverage: 7 | runs-on: ubuntu-latest 8 | 9 | name: Run code style checks 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup PHP 16 | uses: shivammathur/setup-php@v2 17 | with: 18 | php-version: 8.1 19 | extensions: dom, curl, libxml, mbstring, zip, pcntl, bcmath, intl, iconv 20 | 21 | - name: Cache dependencies 22 | uses: actions/cache@v2 23 | with: 24 | path: | 25 | vendor 26 | ${{ steps.composer-cache-files-dir.outputs.dir }} 27 | key: ${{ runner.os }}-composer-${{ hashFiles('composer.json') }} 28 | restore-keys: | 29 | ${{ runner.os }}-composer- 30 | 31 | - name: Install dependencies 32 | env: 33 | COMPOSER_DISCARD_CHANGES: true 34 | run: composer require --no-suggest --no-progress --no-interaction --prefer-dist --update-with-all-dependencies "laravel/framework:^10.0" "orchestra/testbench:^8.0" 35 | 36 | - name: Run PHP CS Fixer 37 | run: ./vendor/bin/php-cs-fixer fix --diff --dry-run 38 | -------------------------------------------------------------------------------- /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 14 * * 3' # Run Wednesdays at 2pm EST 8 | 9 | jobs: 10 | php-tests: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | dependency-version: [ stable, lowest ] 16 | laravel: [ ^9.50.2, 10.* ] 17 | php: [ 8.1, 8.2 ] 18 | include: 19 | - laravel: ^9.50.2 20 | testbench: ^7.22 21 | - laravel: 10.* 22 | testbench: 8.* 23 | 24 | timeout-minutes: 10 25 | name: "${{ matrix.php }} / ${{ matrix.laravel }} (${{ matrix.dependency-version }})" 26 | 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v3 30 | 31 | - name: Setup PHP 32 | uses: shivammathur/setup-php@v2 33 | with: 34 | php-version: ${{ matrix.php }} 35 | extensions: dom, curl, libxml, mbstring, zip, pcntl, bcmath, intl, iconv 36 | tools: composer:v2 37 | 38 | - name: Register composer cache directory 39 | id: composer-cache-files-dir 40 | run: | 41 | echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 42 | 43 | - name: Cache dependencies 44 | uses: actions/cache@v3 45 | with: 46 | path: | 47 | vendor 48 | ${{ steps.composer-cache-files-dir.outputs.dir }} 49 | key: ${{ runner.os }}-composer-${{ hashFiles('composer.json') }} 50 | restore-keys: | 51 | ${{ runner.os }}-composer- 52 | 53 | - name: Install dependencies 54 | run: composer require --no-interaction --prefer-dist --no-update "illuminate/support:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" 55 | 56 | - name: Set dependency version 57 | run: composer update --no-interaction --prefer-dist --with-all-dependencies --prefer-${{ matrix.dependency-version }} 58 | 59 | - name: Execute tests 60 | run: vendor/bin/phpunit -v 61 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: Update Changelog 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | update-publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | repository: ${{ github.event.repository.full_name }} 14 | ref: 'main' 15 | 16 | - name: Update changelog 17 | uses: thomaseizinger/keep-a-changelog-new-release@v1 18 | with: 19 | version: ${{ github.event.release.tag_name }} 20 | 21 | - name: Commit changelog back to repo 22 | uses: EndBug/add-and-commit@v8 23 | with: 24 | add: 'CHANGELOG.md' 25 | message: ${{ github.event.release.tag_name }} 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.phar 3 | composer.lock 4 | .phpunit.result.cache 5 | .php-cs-fixer.cache 6 | 7 | .DS_Store 8 | .phpstorm.meta.php 9 | _ide_helper.php 10 | 11 | node_modules 12 | mix-manifest.json 13 | yarn-error.log 14 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/blade.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 136 | -------------------------------------------------------------------------------- /.idea/laravel-dumper.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /.idea/laravel-idea-personal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/laravel-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/php-test-framework.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 159 | 160 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 170 | -------------------------------------------------------------------------------- /.idea/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | setRiskyAllowed(true) 7 | ->setIndent("\t") 8 | ->setLineEnding("\n") 9 | ->setRules([ 10 | '@PSR2' => true, 11 | 'function_declaration' => [ 12 | 'closure_function_spacing' => 'none', 13 | 'closure_fn_spacing' => 'none', 14 | ], 15 | 'ordered_imports' => [ 16 | 'sort_algorithm' => 'alpha', 17 | ], 18 | 'array_indentation' => true, 19 | 'braces' => [ 20 | 'allow_single_line_closure' => true, 21 | ], 22 | 'no_break_comment' => false, 23 | 'return_type_declaration' => [ 24 | 'space_before' => 'none', 25 | ], 26 | 'blank_line_after_opening_tag' => true, 27 | 'compact_nullable_typehint' => true, 28 | 'cast_spaces' => true, 29 | 'concat_space' => [ 30 | 'spacing' => 'none', 31 | ], 32 | 'declare_equal_normalize' => [ 33 | 'space' => 'none', 34 | ], 35 | 'function_typehint_space' => true, 36 | 'new_with_braces' => true, 37 | 'method_argument_space' => true, 38 | 'no_empty_statement' => true, 39 | 'no_empty_comment' => true, 40 | 'no_empty_phpdoc' => true, 41 | 'no_extra_blank_lines' => [ 42 | 'tokens' => [ 43 | 'extra', 44 | 'use', 45 | 'use_trait', 46 | 'return', 47 | ], 48 | ], 49 | 'no_leading_import_slash' => true, 50 | 'no_leading_namespace_whitespace' => true, 51 | 'no_blank_lines_after_class_opening' => true, 52 | 'no_blank_lines_after_phpdoc' => true, 53 | 'no_whitespace_in_blank_line' => false, 54 | 'no_whitespace_before_comma_in_array' => true, 55 | 'no_useless_else' => true, 56 | 'no_useless_return' => true, 57 | 'single_trait_insert_per_statement' => true, 58 | 'psr_autoloading' => true, 59 | 'dir_constant' => true, 60 | 'single_line_comment_style' => [ 61 | 'comment_types' => ['hash'], 62 | ], 63 | 'include' => true, 64 | 'is_null' => true, 65 | 'linebreak_after_opening_tag' => true, 66 | 'lowercase_cast' => true, 67 | 'lowercase_static_reference' => true, 68 | 'magic_constant_casing' => true, 69 | 'magic_method_casing' => true, 70 | 'class_attributes_separation' => [ 71 | // TODO: This can be reverted when https://github.com/FriendsOfPHP/PHP-CS-Fixer/pull/5869 is merged 72 | 'elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one'], 73 | ], 74 | 'modernize_types_casting' => true, 75 | 'native_function_casing' => true, 76 | 'native_function_type_declaration_casing' => true, 77 | 'no_alias_functions' => true, 78 | 'no_multiline_whitespace_around_double_arrow' => true, 79 | 'multiline_whitespace_before_semicolons' => true, 80 | 'no_short_bool_cast' => true, 81 | 'no_unused_imports' => true, 82 | 'no_php4_constructor' => true, 83 | 'no_singleline_whitespace_before_semicolons' => true, 84 | 'no_spaces_around_offset' => true, 85 | 'no_trailing_comma_in_list_call' => true, 86 | 'no_trailing_comma_in_singleline_array' => true, 87 | 'normalize_index_brace' => true, 88 | 'object_operator_without_whitespace' => true, 89 | 'phpdoc_annotation_without_dot' => true, 90 | 'phpdoc_indent' => true, 91 | 'phpdoc_no_package' => true, 92 | 'phpdoc_no_access' => true, 93 | 'phpdoc_no_useless_inheritdoc' => true, 94 | 'phpdoc_single_line_var_spacing' => true, 95 | 'phpdoc_trim' => true, 96 | 'phpdoc_types' => true, 97 | 'semicolon_after_instruction' => true, 98 | 'array_syntax' => [ 99 | 'syntax' => 'short', 100 | ], 101 | 'list_syntax' => [ 102 | 'syntax' => 'short', 103 | ], 104 | 'short_scalar_cast' => true, 105 | 'single_blank_line_before_namespace' => true, 106 | 'single_quote' => true, 107 | 'standardize_not_equals' => true, 108 | 'ternary_operator_spaces' => true, 109 | 'whitespace_after_comma_in_array' => true, 110 | 'not_operator_with_successor_space' => true, 111 | 'trailing_comma_in_multiline' => true, 112 | 'trim_array_spaces' => true, 113 | 'binary_operator_spaces' => true, 114 | 'unary_operator_spaces' => true, 115 | 'php_unit_method_casing' => [ 116 | 'case' => 'snake_case', 117 | ], 118 | 'php_unit_test_annotation' => [ 119 | 'style' => 'prefix', 120 | ], 121 | ]) 122 | ->setFinder( 123 | PhpCsFixer\Finder::create() 124 | ->exclude('.circleci') 125 | ->exclude('bin') 126 | ->exclude('node_modules') 127 | ->exclude('vendor') 128 | ->notPath('.phpstorm.meta.php') 129 | ->notPath('_ide_helper.php') 130 | ->notPath('artisan') 131 | ->in(__DIR__) 132 | ); 133 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes will be documented in this file following the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 4 | format. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | ## [Unreleased] 7 | 8 | ## [2.0.0] - 2023-12-16 9 | 10 | ## [1.1.0] - 2023-02-17 11 | 12 | ### Added 13 | 14 | - Added Laravel 10 support 15 | 16 | ## [1.0.1] - 2022-02-10 17 | 18 | ### Fixed 19 | 20 | - Fixed how bindings are merged into SQL queries when dumping a query builder 21 | 22 | ## [1.0.0] - 2022-01-24 23 | 24 | ## [0.5.0] - 2022-01-23 25 | 26 | ### Changed 27 | 28 | - Removed the `LaravelDumper` facade in favor of registering custom casters directly 29 | 30 | ## [0.4.0] 31 | 32 | ### Added 33 | 34 | - Added Laravel 9 support 35 | 36 | ## [0.3.0] 37 | 38 | ### Added 39 | 40 | - Added support for dynamic custom casters 41 | - Added `LaravelDumper` facade 42 | - Added custom `Properties` collection for easier manipulation of dumped properties 43 | 44 | ### Changed 45 | 46 | - Changed `Caster` interface to use `Properties` collection 47 | - Updated all casters to use new `Properties` collection 48 | 49 | ## [0.2.0] 50 | 51 | ### Added 52 | 53 | - Added `ddf()` and `dumpf()` for access to original `dd()` and `dump()` behavior 54 | 55 | ## [0.1.0] 56 | 57 | ### Added 58 | 59 | - Added support for Requests and Responses 60 | - Added support for ParameterBags 61 | - Added support for HeaderBags 62 | 63 | ### Changed 64 | 65 | - Improved tests 66 | 67 | ## [0.0.1] 68 | 69 | ### Added 70 | 71 | - Initial release 72 | 73 | # Keep a Changelog Syntax 74 | 75 | - `Added` for new features. 76 | - `Changed` for changes in existing functionality. 77 | - `Deprecated` for soon-to-be removed features. 78 | - `Removed` for now removed features. 79 | - `Fixed` for any bug fixes. 80 | - `Security` in case of vulnerabilities. 81 | 82 | [Unreleased]: https://github.com/glhd/laravel-dumper/compare/2.0.0...HEAD 83 | 84 | [2.0.0]: https://github.com/glhd/laravel-dumper/compare/1.1.0...2.0.0 85 | 86 | [1.1.0]: https://github.com/glhd/laravel-dumper/compare/1.0.1...1.1.0 87 | 88 | [1.0.1]: https://github.com/glhd/laravel-dumper/compare/1.0.0...1.0.1 89 | 90 | [1.0.0]: https://github.com/glhd/laravel-dumper/compare/0.5.0...1.0.0 91 | 92 | [0.5.0]: https://github.com/glhd/laravel-dumper/compare/0.4.0...0.5.0 93 | 94 | [0.4.0]: https://github.com/glhd/laravel-dumper/compare/0.3.0...0.4.0 95 | 96 | [0.3.0]: https://github.com/glhd/laravel-dumper/compare/0.2.0...0.3.0 97 | 98 | [0.2.0]: https://github.com/glhd/laravel-dumper/compare/0.1.0...0.2.0 99 | 100 | [0.1.0]: https://github.com/glhd/laravel-dumper/compare/0.0.1...0.1.0 101 | 102 | [0.0.1]: https://github.com/glhd/laravel-dumper 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Galahad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Build Status 7 | 8 | 9 | Coverage Status 13 | 14 | 15 | Latest Stable Release 19 | 20 | 21 | MIT Licensed 25 | 26 | 27 | Follow @inxilpro on Twitter 31 | 32 |
33 | 34 | # Laravel Dumper 35 | 36 | Improve the default output of `dump()` and `dd()` in Laravel projects. Improves the default 37 | dump behavior for many core Laravel objects, including: 38 | 39 | - Models 40 | - Query Builders 41 | - Service Container 42 | - Database Connections 43 | - Carbon Instances 44 | - Requests and Responses 45 | 46 | https://user-images.githubusercontent.com/21592/150163719-547ecd90-b029-4588-9648-34891e5e0886.mp4 47 | 48 | ## Installation 49 | 50 | Install as a dev dependency: 51 | 52 | ```shell 53 | # composer require glhd/laravel-dumper --dev 54 | ``` 55 | 56 | ## Usage 57 | 58 | Just use `dd()` as you would normally, and enjoy the newly curated output! 59 | 60 | ## Original Dump Output 61 | If, for some reason, you really need the full debug output for an object that `laravel-dumper` customizes, you can 62 | do a "full" dump with `ddf()` and `dumpf()`. 63 | 64 | ## Comparison to Default Output 65 | 66 | You can see comparisons between the default `dd()` output and the `laravel-dumper` output 67 | in the [diffs directory of this repository](./diffs/). 68 | 69 | ## Custom Casters 70 | 71 | Due to [changes in how Laravel registers the var dumper](https://github.com/laravel/framework/pull/44211) it 72 | is no longer possible to register custom casters. 73 | -------------------------------------------------------------------------------- /bin/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Move into project root 4 | BIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 5 | cd "$BIN_DIR" 6 | cd .. 7 | 8 | # Exit on errors 9 | set -e 10 | 11 | CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM -- '***.php') 12 | 13 | if [[ -z "$CHANGED_FILES" ]]; then 14 | echo 'No changed files' 15 | exit 0 16 | fi 17 | 18 | if [[ -x vendor/bin/php-cs-fixer ]]; then 19 | vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php $CHANGED_FILES 20 | git add $CHANGED_FILES 21 | else 22 | echo 'PHP-CS-Fixer is not installed' 23 | exit 1 24 | fi 25 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glhd/laravel-dumper", 3 | "description": "", 4 | "keywords": [ 5 | "laravel" 6 | ], 7 | "authors": [ 8 | { 9 | "name": "Chris Morrell", 10 | "homepage": "http://www.cmorrell.com" 11 | } 12 | ], 13 | "type": "library", 14 | "license": "MIT", 15 | "require": { 16 | "php": ">=8.1", 17 | "illuminate/support": "^9|^10|11.x-dev|dev-master", 18 | "ext-json": "*", 19 | "jdorn/sql-formatter": "^1.2" 20 | }, 21 | "require-dev": { 22 | "orchestra/testbench": "^7.10|^8|9.x-dev|10.x-dev|dev-master", 23 | "friendsofphp/php-cs-fixer": "^3.0", 24 | "mockery/mockery": "^1.3.2", 25 | "phpunit/phpunit": "^9.5" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Glhd\\LaravelDumper\\": "src/" 30 | }, 31 | "files": [ 32 | "src/autoload.php" 33 | ] 34 | }, 35 | "autoload-dev": { 36 | "classmap": [ 37 | "tests/TestCase.php" 38 | ], 39 | "psr-4": { 40 | "Glhd\\LaravelDumper\\Tests\\": "tests/" 41 | } 42 | }, 43 | "scripts": { 44 | "write-diffs": "WRITE_DIFFS=1 vendor/bin/phpunit", 45 | "fix-style": "vendor/bin/php-cs-fixer fix", 46 | "check-style": "vendor/bin/php-cs-fixer fix --diff --dry-run" 47 | }, 48 | "extra": { 49 | "laravel": { 50 | "providers": [ 51 | "Glhd\\LaravelDumper\\LaravelDumperServiceProvider" 52 | ] 53 | } 54 | }, 55 | "minimum-stability": "dev", 56 | "prefer-stable": true 57 | } 58 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | ['local', 'testing'], 16 | ]; 17 | -------------------------------------------------------------------------------- /diffs/carbon-date.diff: -------------------------------------------------------------------------------- 1 | --- Before 2022-01-24 09:44:04.000000000 -0500 2 | +++ After 2022-01-24 09:44:04.000000000 -0500 3 | @@ -1,23 +1,12 @@ 4 | Carbon\Carbon @1642553042 { 5 | + date: 2022-01-18 19:44:02.572622 America/New_York (-05:00) 6 | #endOfTime: false 7 | #startOfTime: false 8 | #constructedObjectId: "00000000186315cd0000000058605208" 9 | - #localMonthsOverflow: null 10 | - #localYearsOverflow: null 11 | - #localStrictModeEnabled: null 12 | - #localHumanDiffOptions: null 13 | - #localToStringFormat: null 14 | - #localSerializer: null 15 | - #localMacros: null 16 | - #localGenericMacros: null 17 | - #localFormatFunction: null 18 | - #localTranslator: null 19 | #dumpProperties: array:3 [ 20 | 0 => "date" 21 | 1 => "timezone_type" 22 | 2 => "timezone" 23 | ] 24 | - #dumpLocale: null 25 | - #dumpDateProperties: null 26 | - date: 2022-01-18 19:44:02.572622 America/New_York (-05:00) 27 | + …12 28 | } 29 | -------------------------------------------------------------------------------- /diffs/container-nested.diff: -------------------------------------------------------------------------------- 1 | --- Before 2022-01-24 09:44:04.000000000 -0500 2 | +++ After 2022-01-24 09:44:04.000000000 -0500 3 | @@ -1,23 +1,3 @@ 4 | array:1 [ 5 | - 0 => Illuminate\Container\Container { 6 | - #resolved: [] 7 | - #bindings: [] 8 | - #methodBindings: [] 9 | - #instances: [] 10 | - #scopedInstances: [] 11 | - #aliases: [] 12 | - #abstractAliases: [] 13 | - #extenders: [] 14 | - #tags: [] 15 | - #buildStack: [] 16 | - #with: [] 17 | - +contextual: [] 18 | - #reboundCallbacks: [] 19 | - #globalBeforeResolvingCallbacks: [] 20 | - #globalResolvingCallbacks: [] 21 | - #globalAfterResolvingCallbacks: [] 22 | - #beforeResolvingCallbacks: [] 23 | - #resolvingCallbacks: [] 24 | - #afterResolvingCallbacks: [] 25 | - } 26 | + 0 => Illuminate\Container\Container { …19} 27 | ] 28 | -------------------------------------------------------------------------------- /diffs/container.diff: -------------------------------------------------------------------------------- 1 | --- Before 2022-01-24 09:44:04.000000000 -0500 2 | +++ After 2022-01-24 09:44:04.000000000 -0500 3 | @@ -1,7 +1,4 @@ 4 | Illuminate\Container\Container { 5 | - #resolved: array:1 [ 6 | - "Glhd\LaravelDumper\Tests\CasterTest" => true 7 | - ] 8 | #bindings: array:1 [ 9 | "Glhd\LaravelDumper\Tests\CasterTest" => array:2 [ 10 | "concrete" => Closure() { 11 | @@ -13,16 +10,11 @@ 12 | "shared" => false 13 | ] 14 | ] 15 | - #methodBindings: [] 16 | - #instances: [] 17 | - #scopedInstances: [] 18 | #aliases: array:1 [ 19 | "bar" => "Glhd\LaravelDumper\Tests\CasterTest" 20 | ] 21 | - #abstractAliases: array:1 [ 22 | - "Glhd\LaravelDumper\Tests\CasterTest" => array:1 [ 23 | - 0 => "bar" 24 | - ] 25 | + #resolved: array:1 [ 26 | + "Glhd\LaravelDumper\Tests\CasterTest" => true 27 | ] 28 | #extenders: array:1 [ 29 | "Glhd\LaravelDumper\Tests\CasterTest" => array:1 [ 30 | @@ -34,15 +26,5 @@ 31 | } 32 | ] 33 | ] 34 | - #tags: [] 35 | - #buildStack: [] 36 | - #with: [] 37 | - +contextual: [] 38 | - #reboundCallbacks: [] 39 | - #globalBeforeResolvingCallbacks: [] 40 | - #globalResolvingCallbacks: [] 41 | - #globalAfterResolvingCallbacks: [] 42 | - #beforeResolvingCallbacks: [] 43 | - #resolvingCallbacks: [] 44 | - #afterResolvingCallbacks: [] 45 | + …15 46 | } 47 | -------------------------------------------------------------------------------- /diffs/custom-caster-2.diff: -------------------------------------------------------------------------------- 1 | --- Before 2022-01-24 09:44:05.000000000 -0500 2 | +++ After 2022-01-24 09:44:05.000000000 -0500 3 | @@ -1,3 +1,3 @@ 4 | Glhd\LaravelDumper\Tests\MyOtherCustomObject { 5 | - #bar: "bar" 6 | + foo: "bar" 7 | } 8 | -------------------------------------------------------------------------------- /diffs/custom-caster.diff: -------------------------------------------------------------------------------- 1 | --- Before 2022-01-24 09:44:05.000000000 -0500 2 | +++ After 2022-01-24 09:44:05.000000000 -0500 3 | @@ -1,6 +1,6 @@ 4 | Glhd\LaravelDumper\Tests\MyCustomObject { 5 | + +"dyn": "this is a dynamic prop" 6 | #foo: "foo" 7 | - #bar: "bar" 8 | - #nothing: null 9 | - #nah: [] 10 | + virt: "this is a virtual prop" 11 | + …1 12 | } 13 | -------------------------------------------------------------------------------- /diffs/eloquent-model.diff: -------------------------------------------------------------------------------- 1 | --- Before 2022-01-24 09:44:05.000000000 -0500 2 | +++ After 2022-01-24 09:44:05.000000000 -0500 3 | @@ -1,25 +1,28 @@ 4 | Glhd\LaravelDumper\Tests\User { 5 | - #guarded: [] 6 | - #connection: "testing" 7 | - #table: "users" 8 | - #primaryKey: "id" 9 | - #keyType: "int" 10 | - +incrementing: true 11 | - #with: [] 12 | - #withCount: [] 13 | - +preventsLazyLoading: false 14 | - #perPage: 15 15 | + +"id": 1 16 | + +"company_id": 1 17 | + +"email": "foo@bar.com" 18 | + +"name": "Chris" 19 | + +"created_at": "2022-01-24 09:44:05" 20 | + +"updated_at": "2022-01-24 09:44:05" 21 | + isDirty(): true 22 | +exists: true 23 | +wasRecentlyCreated: true 24 | - #escapeWhenCastingToString: false 25 | - #attributes: array:6 [ 26 | - "name" => "Chris" 27 | - "email" => "foo@bar.com" 28 | - "company_id" => 1 29 | - "updated_at" => "2022-01-24 09:44:05" 30 | - "created_at" => "2022-01-24 09:44:05" 31 | - "id" => 1 32 | + #relations: array:1 [ 33 | + "company" => Glhd\LaravelDumper\Tests\Company { 34 | + +"id": 1 35 | + +"name": "Galahad" 36 | + +"created_at": "2022-01-24 09:44:05" 37 | + +"updated_at": "2022-01-24 09:44:05" 38 | + isDirty(): false 39 | + +exists: true 40 | + +wasRecentlyCreated: true 41 | + #relations: [] 42 | + …27 43 | + } 44 | ] 45 | + #connection: "testing" 46 | + #table: "users" 47 | #original: array:6 [ 48 | "name" => "John" 49 | "email" => "foo@bar.com" 50 | @@ -29,61 +32,5 @@ 51 | "id" => 1 52 | ] 53 | #changes: [] 54 | - #casts: [] 55 | - #classCastCache: [] 56 | - #attributeCastCache: [] 57 | - #dates: [] 58 | - #dateFormat: null 59 | - #appends: [] 60 | - #dispatchesEvents: [] 61 | - #observables: [] 62 | - #relations: array:1 [ 63 | - "company" => Glhd\LaravelDumper\Tests\Company { 64 | - #guarded: [] 65 | - #connection: "testing" 66 | - #table: "companies" 67 | - #primaryKey: "id" 68 | - #keyType: "int" 69 | - +incrementing: true 70 | - #with: [] 71 | - #withCount: [] 72 | - +preventsLazyLoading: false 73 | - #perPage: 15 74 | - +exists: true 75 | - +wasRecentlyCreated: true 76 | - #escapeWhenCastingToString: false 77 | - #attributes: array:4 [ 78 | - "name" => "Galahad" 79 | - "updated_at" => "2022-01-24 09:44:05" 80 | - "created_at" => "2022-01-24 09:44:05" 81 | - "id" => 1 82 | - ] 83 | - #original: array:4 [ 84 | - "name" => "Galahad" 85 | - "updated_at" => "2022-01-24 09:44:05" 86 | - "created_at" => "2022-01-24 09:44:05" 87 | - "id" => 1 88 | - ] 89 | - #changes: [] 90 | - #casts: [] 91 | - #classCastCache: [] 92 | - #attributeCastCache: [] 93 | - #dates: [] 94 | - #dateFormat: null 95 | - #appends: [] 96 | - #dispatchesEvents: [] 97 | - #observables: [] 98 | - #relations: [] 99 | - #touches: [] 100 | - +timestamps: true 101 | - #hidden: [] 102 | - #visible: [] 103 | - #fillable: [] 104 | - } 105 | - ] 106 | - #touches: [] 107 | - +timestamps: true 108 | - #hidden: [] 109 | - #visible: [] 110 | - #fillable: [] 111 | + …23 112 | } 113 | -------------------------------------------------------------------------------- /diffs/request.diff: -------------------------------------------------------------------------------- 1 | --- Before 2022-01-24 09:44:04.000000000 -0500 2 | +++ After 2022-01-24 09:44:04.000000000 -0500 3 | @@ -1,86 +1,44 @@ 4 | Illuminate\Http\Request { 5 | - #json: null 6 | - #convertedFiles: null 7 | - #userResolver: null 8 | - #routeResolver: null 9 | - +attributes: Symfony\Component\HttpFoundation\ParameterBag { 10 | - #parameters: [] 11 | - } 12 | - +request: Symfony\Component\HttpFoundation\InputBag { 13 | - #parameters: [] 14 | - } 15 | - +query: Symfony\Component\HttpFoundation\InputBag { 16 | - #parameters: [] 17 | - } 18 | + +attributes: Symfony\Component\HttpFoundation\ParameterBag {} 19 | + +request: Symfony\Component\HttpFoundation\InputBag {} 20 | + +query: Symfony\Component\HttpFoundation\InputBag {} 21 | +server: Symfony\Component\HttpFoundation\ServerBag { 22 | - #parameters: array:17 [ 23 | - "SERVER_NAME" => "localhost" 24 | - "SERVER_PORT" => 80 25 | - "HTTP_HOST" => "localhost" 26 | - "HTTP_USER_AGENT" => "Symfony" 27 | - "HTTP_ACCEPT" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" 28 | - "HTTP_ACCEPT_LANGUAGE" => "en-us,en;q=0.5" 29 | - "HTTP_ACCEPT_CHARSET" => "ISO-8859-1,utf-8;q=0.7,*;q=0.7" 30 | - "REMOTE_ADDR" => "127.0.0.1" 31 | - "SCRIPT_NAME" => "" 32 | - "SCRIPT_FILENAME" => "" 33 | - "SERVER_PROTOCOL" => "HTTP/1.1" 34 | - "REQUEST_TIME" => 1643035444 35 | - "REQUEST_TIME_FLOAT" => 1643035444.9632 36 | - "PATH_INFO" => "" 37 | - "REQUEST_METHOD" => "GET" 38 | - "REQUEST_URI" => "/1" 39 | - "QUERY_STRING" => "" 40 | - ] 41 | - } 42 | - +files: Symfony\Component\HttpFoundation\FileBag { 43 | - #parameters: [] 44 | - } 45 | - +cookies: Symfony\Component\HttpFoundation\InputBag { 46 | - #parameters: [] 47 | + SERVER_NAME: "localhost" 48 | + SERVER_PORT: 80 49 | + HTTP_HOST: "localhost" 50 | + HTTP_USER_AGENT: "Symfony" 51 | + HTTP_ACCEPT: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" 52 | + HTTP_ACCEPT_LANGUAGE: "en-us,en;q=0.5" 53 | + HTTP_ACCEPT_CHARSET: "ISO-8859-1,utf-8;q=0.7,*;q=0.7" 54 | + REMOTE_ADDR: "127.0.0.1" 55 | + SCRIPT_NAME: "" 56 | + SCRIPT_FILENAME: "" 57 | + SERVER_PROTOCOL: "HTTP/1.1" 58 | + REQUEST_TIME: 1643035444 59 | + REQUEST_TIME_FLOAT: 1643035444.9632 60 | + PATH_INFO: "" 61 | + REQUEST_METHOD: "GET" 62 | + REQUEST_URI: "/1" 63 | + QUERY_STRING: "" 64 | } 65 | + +files: Symfony\Component\HttpFoundation\FileBag {} 66 | + +cookies: Symfony\Component\HttpFoundation\InputBag {} 67 | +headers: Symfony\Component\HttpFoundation\HeaderBag { 68 | - #headers: array:5 [ 69 | - "host" => array:1 [ 70 | - 0 => "localhost" 71 | - ] 72 | - "user-agent" => array:1 [ 73 | - 0 => "Symfony" 74 | - ] 75 | - "accept" => array:1 [ 76 | - 0 => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" 77 | - ] 78 | - "accept-language" => array:1 [ 79 | - 0 => "en-us,en;q=0.5" 80 | - ] 81 | - "accept-charset" => array:1 [ 82 | - 0 => "ISO-8859-1,utf-8;q=0.7,*;q=0.7" 83 | - ] 84 | - ] 85 | + host: "localhost" 86 | + user-agent: "Symfony" 87 | + accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" 88 | + accept-language: "en-us,en;q=0.5" 89 | + accept-charset: "ISO-8859-1,utf-8;q=0.7,*;q=0.7" 90 | #cacheControl: [] 91 | } 92 | - #content: null 93 | - #languages: null 94 | - #charsets: null 95 | - #encodings: null 96 | - #acceptableContentTypes: null 97 | - #pathInfo: null 98 | - #requestUri: null 99 | - #baseUrl: null 100 | - #basePath: null 101 | - #method: null 102 | - #format: null 103 | - #session: null 104 | - #locale: null 105 | #defaultLocale: "en" 106 | - -preferredFormat: null 107 | -isHostValid: true 108 | -isForwardedValid: true 109 | - -isSafeContentPreferred: null 110 | pathInfo: "/1" 111 | requestUri: "/1" 112 | baseUrl: "" 113 | basePath: "" 114 | method: "GET" 115 | format: "html" 116 | + …19 117 | } 118 | -------------------------------------------------------------------------------- /diffs/response.diff: -------------------------------------------------------------------------------- 1 | --- Before 2022-01-24 09:44:04.000000000 -0500 2 | +++ After 2022-01-24 09:44:04.000000000 -0500 3 | @@ -1,29 +1,13 @@ 4 | Illuminate\Http\Response { 5 | +headers: Symfony\Component\HttpFoundation\ResponseHeaderBag { 6 | - #computedCacheControl: array:2 [ 7 | - "no-cache" => true 8 | - "private" => true 9 | - ] 10 | - #cookies: [] 11 | - #headerNames: array:2 [ 12 | - "cache-control" => "Cache-Control" 13 | - "date" => "Date" 14 | - ] 15 | - #headers: array:2 [ 16 | - "cache-control" => array:1 [ 17 | - 0 => "no-cache, private" 18 | - ] 19 | - "date" => array:1 [ 20 | - 0 => "Mon, 24 Jan 2022 14:44:04 GMT" 21 | - ] 22 | - ] 23 | + cache-control: "no-cache, private" 24 | + date: "Mon, 24 Jan 2022 14:44:04 GMT" 25 | #cacheControl: [] 26 | } 27 | #content: "Hello world." 28 | #version: "1.0" 29 | #statusCode: 200 30 | #statusText: "OK" 31 | - #charset: null 32 | +original: "Hello world." 33 | - +exception: null 34 | + …2 35 | } 36 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | ./src 11 | 12 | 13 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Casters/BuilderCaster.php: -------------------------------------------------------------------------------- 1 | putVirtual('sql', $this->formatSql($target)); 36 | $result->putProtected('connection', $target->getConnection()); 37 | 38 | if ($target instanceof EloquentBuilder) { 39 | $result->copyAndCutProtected('model', $properties); 40 | $result->copyProtected('eagerLoad', $properties); 41 | } 42 | 43 | if ($target instanceof Relation) { 44 | $result->copyAndCutProtected('parent', $properties); 45 | $result->copyAndCutProtected('related', $properties); 46 | } 47 | 48 | $result->applyCutsToStub($stub, $properties); 49 | 50 | return $result->all(); 51 | } 52 | 53 | protected function formatSql($target): string 54 | { 55 | try { 56 | if (method_exists($target, 'toRawSql')) { 57 | return $target->toRawSql(); 58 | } 59 | } catch (Throwable $e) { 60 | // Just fall back on naive formatter below 61 | } 62 | 63 | $sql = $target->toSql(); 64 | $bindings = Arr::flatten($target->getBindings()); 65 | $merged = preg_replace_callback('/\?/', function() use (&$bindings) { 66 | return DB::getPdo()->quote(array_shift($bindings)); 67 | }, $sql); 68 | 69 | if (strlen($merged) > 120) { 70 | $merged = SqlFormatter::format($merged, false); 71 | } 72 | 73 | return $merged; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Casters/CarbonCaster.php: -------------------------------------------------------------------------------- 1 | putVirtual('date', $target->format($this->getFormat($target))) 25 | ->when($is_nested, fn(Properties $properties) => $properties->only('date')) 26 | ->filter() 27 | ->reorder(['date', '*']) 28 | ->applyCutsToStub($stub, $properties) 29 | ->all(); 30 | } 31 | 32 | protected function getFormat(CarbonInterface $target): string 33 | { 34 | // Only include microseconds if we have it 35 | $microseconds = '000000' === $target->format('u') 36 | ? '' 37 | : '.u'; 38 | 39 | // Only include timezone name ("America/New_York") if we have it 40 | $timezone = $target->getTimezone()->getLocation() 41 | ? ' e (P)' 42 | : ' P'; 43 | 44 | return 'Y-m-d H:i:s'.$microseconds.$timezone; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Casters/Caster.php: -------------------------------------------------------------------------------- 1 | singleton(static::class); 27 | } 28 | 29 | public static function callback($caster): Closure 30 | { 31 | return static function($target, array $properties, Stub $stub, bool $is_nested, int $filter = 0) use ($caster) { 32 | $instance = $caster instanceof Caster 33 | ? $caster 34 | : app($caster); 35 | 36 | return self::$enabled 37 | ? $instance->cast($target, new Properties($properties), $stub, $is_nested, $filter) 38 | : $properties; 39 | }; 40 | } 41 | 42 | public static function disable(): void 43 | { 44 | self::$enabled = false; 45 | } 46 | 47 | public static function enable(): void 48 | { 49 | self::$enabled = true; 50 | } 51 | 52 | abstract public function cast($target, Properties $properties, Stub $stub, bool $is_nested, int $filter = 0): array; 53 | } 54 | -------------------------------------------------------------------------------- /src/Casters/ContainerCaster.php: -------------------------------------------------------------------------------- 1 | cut += $properties->count(); 24 | return []; 25 | } 26 | 27 | return $properties 28 | ->only($this->included) 29 | ->reorder($this->included) 30 | ->applyCutsToStub($stub, $properties) 31 | ->all(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Casters/CustomCaster.php: -------------------------------------------------------------------------------- 1 | operations) 33 | ->reduce(fn(Properties $properties, Closure $operation) => $operation($properties, $target), $properties) 34 | ->applyCutsToStub($stub, $properties) 35 | ->all(); 36 | } 37 | 38 | public function reorder(array $rules): CustomCaster 39 | { 40 | $this->operations[] = fn(Properties $properties) => $properties->reorder($rules); 41 | 42 | return $this; 43 | } 44 | 45 | public function filter(callable $filter = null): CustomCaster 46 | { 47 | $this->operations[] = fn(Properties $properties) => $properties->filter($filter); 48 | 49 | return $this; 50 | } 51 | 52 | public function dynamic(string $key, Closure $callback): CustomCaster 53 | { 54 | $this->operations[] = fn(Properties $properties, $target) => $properties->putDynamic($key, $callback($target, $properties)); 55 | 56 | return $this; 57 | } 58 | 59 | public function virtual(string $key, Closure $callback): CustomCaster 60 | { 61 | $this->operations[] = fn(Properties $properties, $target) => $properties->putVirtual($key, $callback($target, $properties)); 62 | 63 | return $this; 64 | } 65 | 66 | public function only($keys): CustomCaster 67 | { 68 | $this->operations[] = fn(Properties $properties) => $properties->only($keys); 69 | 70 | return $this; 71 | } 72 | 73 | public function except($keys): CustomCaster 74 | { 75 | $this->operations[] = fn(Properties $properties) => $properties->except($keys); 76 | 77 | return $this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Casters/DatabaseConnectionCaster.php: -------------------------------------------------------------------------------- 1 | getProtected('config'))) { 25 | return $properties->all(); 26 | } 27 | 28 | $stub->cut += count($properties); 29 | 30 | return [ 31 | Key::virtual('name') => $config['name'], 32 | Key::virtual('database') => $config['database'], 33 | Key::virtual('driver') => $config['driver'], 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Casters/HeaderBagCaster.php: -------------------------------------------------------------------------------- 1 | all()) 25 | ->map(function(array $headers) { 26 | return 1 === count($headers) 27 | ? $headers[0] 28 | : $headers; 29 | }) 30 | ->mapWithKeys(fn($value, $key) => [Key::virtual($key) => $value]) 31 | ->all(); 32 | 33 | $result[Key::protected('cacheControl')] = $properties[Key::protected('cacheControl')]; 34 | 35 | return $result; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Casters/ModelCaster.php: -------------------------------------------------------------------------------- 1 | attributesToDynamicProperties($target), 36 | $this->virtualProperties($target), 37 | $this->cutProperties($properties, $stub, $is_nested) 38 | ); 39 | } 40 | 41 | protected function attributesToDynamicProperties(Model $obj): array 42 | { 43 | return Properties::make($obj->getAttributes()) 44 | ->sortKeys() 45 | ->reorder($this->attribute_order) 46 | ->mapWithKeys(fn($value, $key) => [Key::dynamic($key) => $value]) 47 | ->all(); 48 | } 49 | 50 | protected function virtualProperties(Model $obj): array 51 | { 52 | return [ 53 | Key::virtual('isDirty()') => $obj->isDirty(), 54 | ]; 55 | } 56 | 57 | protected function cutProperties(Properties $properties, Stub $stub, bool $is_nested): array 58 | { 59 | $keep = [ 60 | 'exists', 61 | 'wasRecentlyCreated', 62 | Key::protected('relations'), 63 | ]; 64 | 65 | if (! $is_nested) { 66 | $keep = array_merge($keep, [ 67 | Key::protected('connection'), 68 | Key::protected('table'), 69 | Key::protected('original'), 70 | Key::protected('changes'), 71 | ]); 72 | } 73 | 74 | return $properties 75 | ->only($keep) 76 | ->reorder($keep) 77 | ->applyCutsToStub($stub, $properties) 78 | ->all(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Casters/ParameterBagCaster.php: -------------------------------------------------------------------------------- 1 | all()) 25 | ->mapWithKeys(fn($value, $key) => [Key::virtual($key) => $value]) 26 | ->all(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Casters/RequestCaster.php: -------------------------------------------------------------------------------- 1 | except(['userResolver', 'routeResolver']) 25 | ->filter() 26 | ->applyCutsToStub($stub, $properties) 27 | ->all(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Casters/ResponseCaster.php: -------------------------------------------------------------------------------- 1 | filter() 26 | ->applyCutsToStub($stub, $properties) 27 | ->all(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Casters/manifest.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom($this->packageConfigFile(), 'laravel-dumper'); 12 | 13 | if ($this->isEnabledInCurrentEnvironment()) { 14 | require_once __DIR__.DIRECTORY_SEPARATOR.'helpers.php'; 15 | 16 | $this->registerCasters(); 17 | } 18 | } 19 | 20 | public function boot() 21 | { 22 | $this->publishes([ 23 | $this->packageConfigFile() => $this->app->configPath('laravel-dumper.php'), 24 | ], ['laravel-dumper', 'laravel-dumper-config']); 25 | } 26 | 27 | protected function isEnabledInCurrentEnvironment(): bool 28 | { 29 | $environments = config('laravel-dumper.environments', ['local', 'testing']); 30 | 31 | return $this->app->environment($environments); 32 | } 33 | 34 | protected function registerCasters(): void 35 | { 36 | $casters = require __DIR__.'/Casters/manifest.php'; 37 | foreach ($casters as $caster) { 38 | $caster::register($this->app); 39 | } 40 | } 41 | 42 | protected function packageConfigFile(): string 43 | { 44 | return dirname(__DIR__).DIRECTORY_SEPARATOR.'config.php'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Support/Key.php: -------------------------------------------------------------------------------- 1 | cut += ($original->count() - $this->count()); 26 | 27 | return $this; 28 | } 29 | 30 | public function cut($key, $default = null): CutStub 31 | { 32 | return new CutStub($this->get($key, $default)); 33 | } 34 | 35 | public function cutProtected($key, $default = null): CutStub 36 | { 37 | return $this->cut(Key::protected($key), $default); 38 | } 39 | 40 | public function cutVirtual($key, $default = null): CutStub 41 | { 42 | return $this->cut(Key::virtual($key), $default); 43 | } 44 | 45 | public function cutDynamic($key, $default = null): CutStub 46 | { 47 | return $this->cut(Key::dynamic($key), $default); 48 | } 49 | 50 | public function get($key, $default = null) 51 | { 52 | $missing = new stdClass(); 53 | foreach ($this->addPrefixes($key) as $prefixed_key) { 54 | $parameter = parent::get($prefixed_key, $missing); 55 | if ($missing !== $parameter) { 56 | return $parameter; 57 | } 58 | } 59 | 60 | return $default; 61 | } 62 | 63 | public function has($key) 64 | { 65 | $keys = is_array($key) 66 | ? $key 67 | : func_get_args(); 68 | 69 | foreach ($keys as $value) { 70 | if (! $this->hasAny($this->addPrefixes($value))) { 71 | return false; 72 | } 73 | } 74 | 75 | return true; 76 | } 77 | 78 | public function hasAny($key) 79 | { 80 | if ($this->isEmpty()) { 81 | return false; 82 | } 83 | 84 | $keys = is_array($key) 85 | ? $key 86 | : func_get_args(); 87 | 88 | foreach ($keys as $value) { 89 | if (array_key_exists($value, $this->items)) { 90 | return true; 91 | } 92 | } 93 | 94 | return false; 95 | } 96 | 97 | public function getProtected($key, $default = null) 98 | { 99 | return $this->get(Key::protected($key), $default); 100 | } 101 | 102 | public function getVirtual($key, $default = null) 103 | { 104 | return $this->get(Key::virtual($key), $default); 105 | } 106 | 107 | public function getDynamic($key, $default = null) 108 | { 109 | return $this->get(Key::dynamic($key), $default); 110 | } 111 | 112 | public function putProtected($key, $value): Properties 113 | { 114 | return $this->put(Key::protected($key), $value); 115 | } 116 | 117 | public function putVirtual($key, $value): Properties 118 | { 119 | return $this->put(Key::virtual($key), $value); 120 | } 121 | 122 | public function putDynamic($key, $value): Properties 123 | { 124 | return $this->put(Key::dynamic($key), $value); 125 | } 126 | 127 | public function copy($key, Properties $from, $default = null): Properties 128 | { 129 | return $this->put($key, $from->get($key, $default)); 130 | } 131 | 132 | public function copyProtected($key, Properties $from, $default = null): Properties 133 | { 134 | return $this->copy(Key::protected($key), $from, $default); 135 | } 136 | 137 | public function copyVirtual($key, Properties $from, $default = null): Properties 138 | { 139 | return $this->copy(Key::virtual($key), $from, $default); 140 | } 141 | 142 | public function copyDynamic($key, Properties $from, $default = null): Properties 143 | { 144 | return $this->copy(Key::dynamic($key), $from, $default); 145 | } 146 | 147 | public function copyAndCut($key, Properties $from, $default = null): Properties 148 | { 149 | return $this->put($key, $from->cut($key, $default)); 150 | } 151 | 152 | public function copyAndCutProtected($key, Properties $from, $default = null): Properties 153 | { 154 | return $this->copyAndCut(Key::protected($key), $from, $default); 155 | } 156 | 157 | public function copyAndCutVirtual($key, Properties $from, $default = null): Properties 158 | { 159 | return $this->copyAndCut(Key::virtual($key), $from, $default); 160 | } 161 | 162 | public function copyAndCutDynamic($key, Properties $from, $default = null): Properties 163 | { 164 | return $this->copyAndCut(Key::dynamic($key), $from, $default); 165 | } 166 | 167 | public function only($keys) 168 | { 169 | return $this->filter(function($value, $key) use ($keys) { 170 | return Str::is($keys, $key) || Str::is($keys, $this->stripPrefix($key)); 171 | }); 172 | } 173 | 174 | public function except($keys) 175 | { 176 | return $this->reject(function($value, $key) use ($keys) { 177 | return Str::is($keys, $key) || Str::is($keys, $this->stripPrefix($key)); 178 | }); 179 | } 180 | 181 | public function filter(callable $callback = null) 182 | { 183 | if (null === $callback) { 184 | $callback = static function($property) { 185 | if (is_array($property)) { 186 | return count($property); 187 | } 188 | 189 | if ($property instanceof Enumerable) { 190 | return $property->isNotEmpty(); 191 | } 192 | 193 | return null !== $property; 194 | }; 195 | } 196 | 197 | return parent::filter($callback); 198 | } 199 | 200 | public function reorder(array $rules): Properties 201 | { 202 | return $this->sortBy($this->getReorderCallback($rules)); 203 | } 204 | 205 | protected function getReorderCallback(array $rules): Closure 206 | { 207 | $map = $this->createReorderMapFromRules($rules); 208 | 209 | return function($value, $key) use ($map) { 210 | $result = Arr::pull($map, '*'); 211 | 212 | foreach ($map as $pattern => $position) { 213 | if ($key === $pattern || Str::is($pattern, $this->stripPrefix($key))) { 214 | $result = $position; 215 | } 216 | } 217 | 218 | return $result; 219 | }; 220 | } 221 | 222 | protected function createReorderMapFromRules(array $rules): array 223 | { 224 | $rules = array_values($rules); 225 | $map = array_combine($rules, array_keys($rules)); 226 | 227 | // Ensure that there's always a '*' pattern, defaulting to the end 228 | $map['*'] ??= count($map); 229 | 230 | return $map; 231 | } 232 | 233 | protected function stripPrefix(string $key): string 234 | { 235 | return str_replace($this->prefixes, '', $key); 236 | } 237 | 238 | protected function addPrefixes(string $key): array 239 | { 240 | if (Str::startsWith($key, $this->prefixes)) { 241 | return [$key]; 242 | } 243 | 244 | return array_merge([$key], array_map(fn($prefix) => $prefix.$key, $this->prefixes)); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | getDump($now); 19 | 20 | $this->assertStringStartsWith('Carbon\\Carbon', $dump); 21 | $this->assertStringContainsString('date: 2022-01-18 19:44:02.572622 America/New_York (-05:00)', $dump); 22 | $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 23 | 24 | $this->assertStringNotContainsString('localMacros', $dump); 25 | } 26 | 27 | public function test_package_can_be_disabled(): void 28 | { 29 | $this->withoutWritingDiffs(); 30 | 31 | CustomCaster::for(static::class) 32 | ->only([]) 33 | ->virtual('foo', fn() => 'bar'); 34 | 35 | $getLineCount = fn() => substr_count($this->getDump($this), "\n") + 1; 36 | 37 | $this->assertEquals(4, $getLineCount()); 38 | 39 | Caster::disable(); 40 | 41 | $this->assertGreaterThan(100, $getLineCount()); 42 | 43 | Caster::enable(); 44 | 45 | $this->assertEquals(4, $getLineCount()); 46 | } 47 | 48 | public function test_container(): void 49 | { 50 | $container = new Container(); 51 | 52 | $container->bind(static::class, fn() => $this); 53 | $container->alias(static::class, 'bar'); 54 | $container->extend('bar', fn() => $this); 55 | $container->make('bar'); 56 | 57 | $dump = $this->getDump($container); 58 | 59 | $this->assertStringStartsWith('Illuminate\\Container\\Container', $dump); 60 | $this->assertStringContainsString('#bindings', $dump); 61 | $this->assertStringContainsString('#aliases', $dump); 62 | $this->assertStringContainsString('#resolved', $dump); 63 | $this->assertStringContainsString('#extenders', $dump); 64 | $this->assertStringContainsString(static::class, $dump); 65 | 66 | // These won't be true until I get a PR to Laravel 67 | // $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 68 | // $this->assertStringNotContainsString('#globalBeforeResolvingCallbacks', $dump); 69 | } 70 | 71 | public function test_container_nested(): void 72 | { 73 | $container = new Container(); 74 | 75 | $expected = << Illuminate\Container\Container { …%d} 78 | ] 79 | EOD; 80 | 81 | $this->assertDumpMatchesFormat($expected, [$container]); 82 | } 83 | 84 | public function test_request(): void 85 | { 86 | $request = Request::create('/1'); 87 | 88 | $dump = $this->getDump($request); 89 | 90 | $this->assertStringStartsWith('Illuminate\\Http\\Request {', $dump); 91 | $this->assertStringContainsString('+attributes', $dump); 92 | $this->assertStringContainsString('+request', $dump); 93 | $this->assertStringContainsString('+query', $dump); 94 | $this->assertStringContainsString('+server', $dump); 95 | $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 96 | } 97 | 98 | public function test_response(): void 99 | { 100 | $response = new Response('Hello world.'); 101 | 102 | $dump = $this->getDump($response); 103 | 104 | $this->assertStringStartsWith('Illuminate\\Http\\Response {', $dump); 105 | $this->assertStringContainsString('+headers', $dump); 106 | $this->assertStringContainsString('#content: "Hello world."', $dump); 107 | $this->assertStringContainsString('#statusCode: 200', $dump); 108 | $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/CustomCasterTest.php: -------------------------------------------------------------------------------- 1 | only(['foo', 'nothing', 'nah']) 14 | ->dynamic('dyn', fn() => 'this is a dynamic prop') 15 | ->virtual('virt', fn() => 'this is a virtual prop') 16 | ->filter() 17 | ->reorder(['dyn', 'foo']); 18 | 19 | CustomCaster::for(MyOtherCustomObject::class) 20 | ->except('bar') 21 | ->virtual('foo', fn() => 'bar'); 22 | 23 | $expected = <<assertDumpEquals($expected, new MyCustomObject()); 33 | 34 | $expected = <<assertDumpEquals($expected, new MyOtherCustomObject()); 41 | } 42 | 43 | public function test_custom_casters_cannot_be_registered(): void 44 | { 45 | $this->expectException(BadMethodCallException::class); 46 | 47 | CustomCaster::register($this->app); 48 | } 49 | } 50 | 51 | class MyCustomObject 52 | { 53 | protected $foo = 'foo'; 54 | 55 | protected $bar = 'bar'; 56 | 57 | protected $nothing = null; 58 | 59 | protected $nah = []; 60 | } 61 | 62 | class MyOtherCustomObject 63 | { 64 | protected $bar = 'bar'; 65 | } 66 | -------------------------------------------------------------------------------- /tests/DatabaseCasterTest.php: -------------------------------------------------------------------------------- 1 | 'Galahad']); 19 | $user = User::create(['name' => 'John', 'email' => 'foo@bar.com', 'company_id' => $company->id]); 20 | $user->setRelation('company', $company); 21 | $user->name = 'Chris'; 22 | 23 | $timestamp = $now->format('Y-m-d H:i:s'); 24 | 25 | $dump = $this->getDump($user); 26 | 27 | $this->assertStringStartsWith(User::class, $dump); 28 | $this->assertStringContainsString('id', $dump); 29 | $this->assertStringContainsString('company_id', $dump); 30 | $this->assertStringContainsString('email', $dump); 31 | $this->assertStringContainsString('name', $dump); 32 | $this->assertStringContainsString('isDirty()', $dump); 33 | $this->assertStringContainsString('exists', $dump); 34 | $this->assertStringContainsString('wasRecentlyCreated', $dump); 35 | $this->assertStringContainsString($timestamp, $dump); 36 | $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 37 | 38 | $this->assertStringNotContainsString('escapeWhenCastingToString', $dump); 39 | } 40 | 41 | public function test_query_builder(): void 42 | { 43 | $builder = DB::table('users') 44 | ->where('email', 'bogdan@foo.com') 45 | ->limit(10); 46 | 47 | $dump = $this->getDump($builder); 48 | 49 | $this->assertStringStartsWith('Illuminate\\Database\\Query\\Builder {', $dump); 50 | $this->assertStringContainsString('select * from "users" where "email" = \'bogdan@foo.com\' limit 10', $dump); 51 | $this->assertStringContainsString('#connection', $dump); 52 | $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 53 | } 54 | 55 | /** @see https://github.com/glhd/laravel-dumper/issues/6 */ 56 | public function test_where_between_statement(): void 57 | { 58 | $builder = User::where('name', 'test') 59 | ->whereBetween('id', [1, 2]); 60 | 61 | $dump = $this->getDump($builder); 62 | 63 | $this->assertStringStartsWith('Illuminate\\Database\\Eloquent\\Builder {', $dump); 64 | $this->assertStringContainsString('select * from "users" where "name" = \'test\' and "id" between \'1\' and \'2\'', $dump); 65 | $this->assertStringContainsString('#connection', $dump); 66 | $this->assertStringContainsString('#model', $dump); 67 | $this->assertStringContainsString(User::class, $dump); 68 | $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 69 | } 70 | 71 | public function test_eloquent_builder(): void 72 | { 73 | $builder = User::query() 74 | ->with('company') 75 | ->where('email', 'bogdan@foo.com') 76 | ->limit(10); 77 | 78 | $dump = $this->getDump($builder); 79 | 80 | $this->assertStringStartsWith('Illuminate\\Database\\Eloquent\\Builder {', $dump); 81 | $this->assertStringContainsString('select * from "users" where "email" = \'bogdan@foo.com\' limit 10', $dump); 82 | $this->assertStringContainsString('#model', $dump); 83 | $this->assertStringContainsString('#eagerLoad', $dump); 84 | $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 85 | } 86 | 87 | public function test_eloquent_relation(): void 88 | { 89 | Company::create(['id' => 1, 'name' => 'Galahad']); 90 | $user = User::create(['id' => 1, 'name' => 'John', 'email' => 'foo@bar.com', 'company_id' => 1]); 91 | 92 | $dump = $this->getDump($user->company()); 93 | 94 | $this->assertStringStartsWith('Illuminate\\Database\\Eloquent\\Relations\\BelongsTo {', $dump); 95 | $this->assertStringContainsString('select * from "companies" where "companies"."id" = \'1\'', $dump); 96 | $this->assertStringContainsString('#parent', $dump); 97 | $this->assertStringContainsString('#related', $dump); 98 | $this->assertStringContainsString(User::class, $dump); 99 | $this->assertStringContainsString(Company::class, $dump); 100 | $this->assertMatchesRegularExpression('/\s*…\d+\n}$/', $dump); 101 | } 102 | 103 | protected function defineDatabaseMigrations() 104 | { 105 | $this->loadMigrationsFrom(__DIR__.'/migrations'); 106 | } 107 | } 108 | 109 | class User extends Model 110 | { 111 | protected $guarded = []; 112 | 113 | public function company() 114 | { 115 | return $this->belongsTo(Company::class); 116 | } 117 | } 118 | 119 | class Company extends Model 120 | { 121 | protected $guarded = []; 122 | 123 | public function users() 124 | { 125 | return $this->hasMany(User::class); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/PropertiesTest.php: -------------------------------------------------------------------------------- 1 | parameters = new Properties([ 19 | Key::protected('protected') => 1, 20 | Key::virtual('virtual') => 1, 21 | Key::dynamic('dynamic') => 1, 22 | 'prefix_b' => 1, 23 | 'prefix_a' => 1, 24 | 'b_suffix' => 1, 25 | 'a_suffix' => 1, 26 | 'other' => 1, 27 | ]); 28 | } 29 | 30 | public function test_reordering_parameters(): void 31 | { 32 | $rules = [ 33 | 'prefix_*', 34 | 'dynamic', 35 | 'virtual', 36 | '*', 37 | 'protected', 38 | '*_suffix', 39 | ]; 40 | 41 | $reordered = $this->parameters->reorder($rules)->all(); 42 | 43 | $this->assertEquals([ 44 | 'prefix_b' => 1, 45 | 'prefix_a' => 1, 46 | Key::dynamic('dynamic') => 1, 47 | Key::virtual('virtual') => 1, 48 | 'other' => 1, 49 | Key::protected('protected') => 1, 50 | 'b_suffix' => 1, 51 | 'a_suffix' => 1, 52 | ], $reordered); 53 | } 54 | 55 | public function test_only_keeping_specific_parameters(): void 56 | { 57 | $subset = $this->parameters->only(['dynamic', '*_suffix'])->all(); 58 | 59 | $this->assertEquals([ 60 | Key::dynamic('dynamic') => 1, 61 | 'b_suffix' => 1, 62 | 'a_suffix' => 1, 63 | ], $subset); 64 | } 65 | 66 | public function test_excluding_specific_parameters(): void 67 | { 68 | $subset = $this->parameters->except(['dynamic', '*_suffix'])->all(); 69 | 70 | $this->assertEquals([ 71 | Key::protected('protected') => 1, 72 | Key::virtual('virtual') => 1, 73 | 'prefix_b' => 1, 74 | 'prefix_a' => 1, 75 | 'other' => 1, 76 | ], $subset); 77 | } 78 | 79 | public function test_has_method(): void 80 | { 81 | $this->assertTrue($this->parameters->has('protected')); 82 | $this->assertTrue($this->parameters->has(Key::protected('protected'))); 83 | $this->assertTrue($this->parameters->has(Key::protected('protected'), 'prefix_b')); 84 | $this->assertTrue($this->parameters->has([Key::protected('protected'), 'prefix_b'])); 85 | 86 | $this->assertFalse($this->parameters->has(Key::virtual('protected'))); 87 | $this->assertFalse($this->parameters->has(Key::protected('protected'), 'foo')); 88 | $this->assertFalse($this->parameters->has([Key::protected('protected'), 'foo'])); 89 | 90 | $this->assertTrue($this->parameters->hasAny(Key::protected('protected'), 'foo')); 91 | $this->assertTrue($this->parameters->hasAny([Key::protected('protected'), 'foo'])); 92 | } 93 | 94 | public function test_get_methods(): void 95 | { 96 | $this->assertEquals(1, $this->parameters->getProtected('protected')); 97 | $this->assertNull($this->parameters->getProtected('virtual')); 98 | 99 | $this->assertEquals(1, $this->parameters->getVirtual('virtual')); 100 | $this->assertNull($this->parameters->getVirtual('protected')); 101 | 102 | $this->assertEquals(1, $this->parameters->getDynamic('dynamic')); 103 | $this->assertNull($this->parameters->getDynamic('protected')); 104 | 105 | $this->assertEquals(1, $this->parameters->get('protected')); 106 | $this->assertEquals(1, $this->parameters->get('virtual')); 107 | $this->assertEquals(1, $this->parameters->get('dynamic')); 108 | $this->assertEquals(1, $this->parameters->get('prefix_b')); 109 | $this->assertNull($this->parameters->get('missing param')); 110 | } 111 | 112 | public function test_cut_methods(): void 113 | { 114 | $this->assertEquals(1, $this->parameters->cutProtected('protected')->value); 115 | $this->assertNull($this->parameters->cutProtected('virtual')->value); 116 | 117 | $this->assertEquals(1, $this->parameters->cutVirtual('virtual')->value); 118 | $this->assertNull($this->parameters->cutVirtual('protected')->value); 119 | 120 | $this->assertEquals(1, $this->parameters->cutDynamic('dynamic')->value); 121 | $this->assertNull($this->parameters->cutDynamic('protected')->value); 122 | 123 | $this->assertEquals(1, $this->parameters->cut('protected')->value); 124 | $this->assertEquals(1, $this->parameters->cut('virtual')->value); 125 | $this->assertEquals(1, $this->parameters->cut('dynamic')->value); 126 | $this->assertEquals(1, $this->parameters->cut('prefix_b')->value); 127 | $this->assertNull($this->parameters->cut('missing param')->value); 128 | } 129 | 130 | public function test_copy_methods(): void 131 | { 132 | $destination = new Properties(); 133 | 134 | $destination->copyProtected('protected', $this->parameters); 135 | $destination->copyVirtual('virtual', $this->parameters); 136 | $destination->copyDynamic('dynamic', $this->parameters); 137 | $destination->copy('prefix_b', $this->parameters); 138 | 139 | $this->assertEquals(1, $destination->get('protected')); 140 | $this->assertEquals(1, $destination->get('virtual')); 141 | $this->assertEquals(1, $destination->get('dynamic')); 142 | $this->assertEquals(1, $destination->get('prefix_b')); 143 | } 144 | 145 | public function test_copy_and_cut_methods(): void 146 | { 147 | $destination = new Properties(); 148 | 149 | $destination->copyAndCutProtected('protected', $this->parameters); 150 | $destination->copyAndCutVirtual('virtual', $this->parameters); 151 | $destination->copyAndCutDynamic('dynamic', $this->parameters); 152 | 153 | $this->assertInstanceOf(CutStub::class, $destination->get('protected')); 154 | $this->assertInstanceOf(CutStub::class, $destination->get('virtual')); 155 | $this->assertInstanceOf(CutStub::class, $destination->get('dynamic')); 156 | 157 | $this->assertEquals(1, $destination->get('protected')->value); 158 | $this->assertEquals(1, $destination->get('virtual')->value); 159 | $this->assertEquals(1, $destination->get('dynamic')->value); 160 | } 161 | 162 | public function test_default_filtering(): void 163 | { 164 | $properties = new Properties([ 165 | 'null' => null, 166 | 'empty array' => [], 167 | 'empty collection' => new Collection(), 168 | 'false' => false, 169 | 'value' => 1, 170 | ]); 171 | 172 | $this->assertEquals(['false' => false, 'value' => 1], $properties->filter()->all()); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | write_diff_if_configured = true; 29 | $this->diff_count = 0; 30 | } 31 | 32 | protected function withoutWritingDiffs(): self 33 | { 34 | $this->write_diff_if_configured = false; 35 | 36 | return $this; 37 | } 38 | 39 | protected function getPackageProviders($app) 40 | { 41 | return [ 42 | LaravelDumperServiceProvider::class, 43 | ]; 44 | } 45 | 46 | protected function getApplicationTimezone($app) 47 | { 48 | return 'America/New_York'; 49 | } 50 | 51 | protected function getDump($data, $key = null, int $filter = 0): ?string 52 | { 53 | if (! $this->write_diff_if_configured || '1' !== getenv('WRITE_DIFFS')) { 54 | return $this->baseGetDump($data, $key, $filter); 55 | } 56 | 57 | Caster::disable(); 58 | $before = $this->baseGetDump($data, $key, $filter); 59 | 60 | Caster::enable(); 61 | $after = $this->baseGetDump($data, $key, $filter); 62 | 63 | $this->writeDiff($before, $after); 64 | 65 | return $after; 66 | } 67 | 68 | protected function writeDiff($before, $after) 69 | { 70 | if ($before === $after) { 71 | return; 72 | } 73 | 74 | $fs = new Filesystem(); 75 | $path = __DIR__.'/../diffs'; 76 | 77 | [$_, $_, $_, $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4); 78 | $name = Str::of($caller['function'])->after('test_')->replace('_', '-'); 79 | 80 | $this->diff_count++; 81 | if ($this->diff_count > 1) { 82 | $name .= "-{$this->diff_count}"; 83 | } 84 | 85 | $before_file = "{$path}/{$name}.1.txt"; 86 | $after_file = "{$path}/{$name}.2.txt"; 87 | 88 | try { 89 | $fs->put($before_file, "{$before}\n"); 90 | $fs->put($after_file, "{$after}\n"); 91 | 92 | $diff = (new Process(['/usr/bin/diff', '-u', $before_file, $after_file])); 93 | $diff->run(); 94 | $fs->put("{$path}/{$name}.diff", str_replace([$before_file, $after_file], ['Before', 'After'], $diff->getOutput())); 95 | } finally { 96 | $fs->delete([$before_file, $after_file]); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/migrations/2022_01_18_131057_create_test_tables.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 13 | $table->string('name'); 14 | $table->string('email'); 15 | $table->foreignId('company_id')->nullable(); 16 | $table->timestamps(); 17 | $table->softDeletes(); 18 | }); 19 | 20 | Schema::create('companies', function(Blueprint $table) { 21 | $table->bigIncrements('id'); 22 | $table->string('name'); 23 | $table->timestamps(); 24 | $table->softDeletes(); 25 | }); 26 | } 27 | 28 | public function down() 29 | { 30 | Schema::dropIfExists('companies'); 31 | Schema::dropIfExists('users'); 32 | } 33 | } 34 | --------------------------------------------------------------------------------