├── .idea
├── vcs.xml
├── .gitignore
├── phpunit.xml
├── modules.xml
├── laravel-idea.xml
├── laravel-idea-personal.xml
├── php-test-framework.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── blade.xml
├── php.xml
└── laravel-dumper.iml
├── diffs
├── custom-caster-2.diff
├── custom-caster.diff
├── carbon-date.diff
├── container-nested.diff
├── response.diff
├── container.diff
├── eloquent-model.diff
└── request.diff
├── .gitignore
├── .codeclimate.yml
├── src
├── autoload.php
├── helpers.php
├── Support
│ ├── Key.php
│ └── Properties.php
├── Casters
│ ├── manifest.php
│ ├── ContainerCaster.php
│ ├── ResponseCaster.php
│ ├── RequestCaster.php
│ ├── ParameterBagCaster.php
│ ├── DatabaseConnectionCaster.php
│ ├── HeaderBagCaster.php
│ ├── CarbonCaster.php
│ ├── Caster.php
│ ├── ModelCaster.php
│ ├── CustomCaster.php
│ └── BuilderCaster.php
└── LaravelDumperServiceProvider.php
├── config.php
├── bin
└── pre-commit.sh
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── update-changelog.yml
│ ├── php-cs-fixer.yml
│ ├── coverage.yml
│ └── phpunit.yml
├── tests
├── migrations
│ └── 2022_01_18_131057_create_test_tables.php
├── CustomCasterTest.php
├── TestCase.php
├── CasterTest.php
├── DatabaseCasterTest.php
└── PropertiesTest.php
├── phpunit.xml
├── LICENSE
├── composer.json
├── README.md
├── CHANGELOG.md
└── .php-cs-fixer.dist.php
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/diffs/custom-caster-2.diff:
--------------------------------------------------------------------------------
1 | --- Before 2025-12-17 16:35:07
2 | +++ After 2025-12-17 16:35:07
3 | @@ -1,3 +1,3 @@
4 | Glhd\LaravelDumper\Tests\MyOtherCustomObject {
5 | - #bar: "bar"
6 | + foo: "bar"
7 | }
8 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | composer.phar
3 | composer.lock
4 | .phpunit.result.cache
5 | .php-cs-fixer.cache
6 | .phpunit.cache
7 |
8 | .DS_Store
9 | .phpstorm.meta.php
10 | _ide_helper.php
11 |
12 | node_modules
13 | mix-manifest.json
14 | yarn-error.log
15 |
--------------------------------------------------------------------------------
/.idea/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/diffs/custom-caster.diff:
--------------------------------------------------------------------------------
1 | --- Before 2025-12-17 16:35:07
2 | +++ After 2025-12-17 16:35:07
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 |
--------------------------------------------------------------------------------
/src/autoload.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/config.php:
--------------------------------------------------------------------------------
1 | ['local', 'testing'],
16 | ];
17 |
--------------------------------------------------------------------------------
/src/Casters/manifest.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Casters/ContainerCaster.php:
--------------------------------------------------------------------------------
1 | cut += $properties->count();
25 | return [];
26 | }
27 |
28 | return $properties
29 | ->only($this->included)
30 | ->reorder($this->included)
31 | ->applyCutsToStub($stub, $properties)
32 | ->all();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Casters/ResponseCaster.php:
--------------------------------------------------------------------------------
1 | filter()
27 | ->applyCutsToStub($stub, $properties)
28 | ->all();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Casters/RequestCaster.php:
--------------------------------------------------------------------------------
1 | except(['userResolver', 'routeResolver'])
26 | ->filter()
27 | ->applyCutsToStub($stub, $properties)
28 | ->all();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Casters/ParameterBagCaster.php:
--------------------------------------------------------------------------------
1 | all())
26 | ->mapWithKeys(fn($value, $key) => [Key::virtual($key) => $value])
27 | ->all();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/diffs/container-nested.diff:
--------------------------------------------------------------------------------
1 | --- Before 2025-12-17 16:35:07
2 | +++ After 2025-12-17 16:35:07
3 | @@ -1,28 +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 | - +contextualAttributes: []
19 | - #checkedForAttributeBindings: []
20 | - #checkedForSingletonOrScopedAttributes: []
21 | - #reboundCallbacks: []
22 | - #globalBeforeResolvingCallbacks: []
23 | - #globalResolvingCallbacks: []
24 | - #globalAfterResolvingCallbacks: []
25 | - #beforeResolvingCallbacks: []
26 | - #resolvingCallbacks: []
27 | - #afterResolvingCallbacks: []
28 | - #afterResolvingAttributeCallbacks: []
29 | - #environmentResolver: null
30 | - }
31 | + 0 => Illuminate\Container\Container { …24}
32 | ]
33 |
--------------------------------------------------------------------------------
/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 |
9 |
10 |
11 | ./tests
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | ./src
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Casters/DatabaseConnectionCaster.php:
--------------------------------------------------------------------------------
1 | getProtected('config'))) {
26 | return $properties->all();
27 | }
28 |
29 | $stub->cut += count($properties);
30 |
31 | return [
32 | Key::virtual('name') => $config['name'],
33 | Key::virtual('database') => $config['database'],
34 | Key::virtual('driver') => $config['driver'],
35 | ];
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Casters/HeaderBagCaster.php:
--------------------------------------------------------------------------------
1 | all())
26 | ->map(function(array $headers) {
27 | return 1 === count($headers)
28 | ? $headers[0]
29 | : $headers;
30 | })
31 | ->mapWithKeys(fn($value, $key) => [Key::virtual($key) => $value])
32 | ->all();
33 |
34 | $result[Key::protected('cacheControl')] = $properties[Key::protected('cacheControl')];
35 |
36 | return $result;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/LaravelDumperServiceProvider.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.github/workflows/php-cs-fixer.yml:
--------------------------------------------------------------------------------
1 | name: Code Style
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | coverage:
11 | runs-on: ubuntu-latest
12 |
13 | name: Run code style checks
14 |
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v4
18 |
19 | - name: Setup PHP
20 | uses: shivammathur/setup-php@v2
21 | with:
22 | php-version: 8.3
23 | extensions: dom, curl, libxml, mbstring, zip, pcntl, bcmath, intl, iconv
24 | coverage: none
25 |
26 | - name: Get composer cache directory
27 | id: composer-cache
28 | run: |
29 | echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
30 |
31 | - name: Cache dependencies
32 | uses: actions/cache@v4
33 | with:
34 | path: ${{ steps.composer-cache.outputs.dir }}
35 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
36 | restore-keys: |
37 | ${{ runner.os }}-composer-
38 |
39 | - name: Install dependencies
40 | env:
41 | COMPOSER_DISCARD_CHANGES: true
42 | run: composer require --no-progress --no-interaction --prefer-dist --update-with-all-dependencies "laravel/framework:^11.0"
43 |
44 | - name: Run PHP CS Fixer
45 | run: ./vendor/bin/php-cs-fixer fix --diff --dry-run
46 |
--------------------------------------------------------------------------------
/src/Casters/CarbonCaster.php:
--------------------------------------------------------------------------------
1 | putVirtual('date', $target->format($this->getFormat($target)))
26 | ->when($is_nested, fn(Properties $properties) => $properties->only('date'))
27 | ->filter()
28 | ->reorder(['date', '*'])
29 | ->applyCutsToStub($stub, $properties)
30 | ->all();
31 | }
32 |
33 | protected function getFormat(CarbonInterface $target): string
34 | {
35 | // Only include microseconds if we have it
36 | $microseconds = '000000' === $target->format('u')
37 | ? ''
38 | : '.u';
39 |
40 | // Only include timezone name ("America/New_York") if we have it
41 | $timezone = $target->getTimezone()->getLocation()
42 | ? ' e (P)'
43 | : ' P';
44 |
45 | return 'Y-m-d H:i:s'.$microseconds.$timezone;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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@v4
17 |
18 | - name: Setup PHP
19 | uses: shivammathur/setup-php@v2
20 | with:
21 | php-version: 8.3
22 | extensions: dom, curl, libxml, mbstring, zip, pcntl, bcmath, intl, iconv
23 | coverage: pcov
24 |
25 | - name: Get composer cache directory
26 | id: composer-cache
27 | run: |
28 | echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
29 |
30 | - name: Cache dependencies
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.composer-cache.outputs.dir }}
34 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
35 | restore-keys: |
36 | ${{ runner.os }}-composer-
37 |
38 | - name: Install dependencies
39 | env:
40 | COMPOSER_DISCARD_CHANGES: true
41 | run: composer require --no-progress --no-interaction --prefer-dist --update-with-all-dependencies "laravel/framework:^11.0"
42 |
43 | - name: Run and publish code coverage
44 | uses: paambaati/codeclimate-action@v9.0
45 | env:
46 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
47 | with:
48 | coverageCommand: vendor/bin/phpunit --coverage-clover ${{ github.workspace }}/clover.xml
49 | coverageLocations:
50 | "${{github.workspace}}/clover.xml:clover"
51 |
--------------------------------------------------------------------------------
/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": "^10|^11|^12",
18 | "ext-json": "*"
19 | },
20 | "require-dev": {
21 | "orchestra/testbench": "^6.47|^7.55|^8.36|^9.15|^10.8|11.x-dev|dev-master|dev-main",
22 | "friendsofphp/php-cs-fixer": "^3.9",
23 | "mockery/mockery": "^1.4",
24 | "phpunit/phpunit": "^9|^10.5|^11.5"
25 | },
26 | "suggest": {
27 | "jdorn/sql-formatter": "Allows better SQL formatting and highlighting"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "Glhd\\LaravelDumper\\": "src/"
32 | },
33 | "files": [
34 | "src/autoload.php"
35 | ]
36 | },
37 | "autoload-dev": {
38 | "classmap": [
39 | "tests/TestCase.php"
40 | ],
41 | "psr-4": {
42 | "Glhd\\LaravelDumper\\Tests\\": "tests/"
43 | }
44 | },
45 | "scripts": {
46 | "write-diffs": "WRITE_DIFFS=1 vendor/bin/phpunit",
47 | "fix-style": "vendor/bin/php-cs-fixer fix",
48 | "check-style": "vendor/bin/php-cs-fixer fix --diff --dry-run"
49 | },
50 | "extra": {
51 | "laravel": {
52 | "providers": [
53 | "Glhd\\LaravelDumper\\LaravelDumperServiceProvider"
54 | ]
55 | }
56 | },
57 | "minimum-stability": "dev",
58 | "prefer-stable": true
59 | }
60 |
--------------------------------------------------------------------------------
/src/Casters/Caster.php:
--------------------------------------------------------------------------------
1 | singleton(static::class);
33 | }
34 |
35 | public static function callback($caster): Closure
36 | {
37 | return static function($target, array $properties, Stub|HerdStub $stub, bool $is_nested, int $filter = 0) use ($caster) {
38 | $instance = $caster instanceof Caster
39 | ? $caster
40 | : app($caster);
41 |
42 | return self::$enabled
43 | ? $instance->cast($target, new Properties($properties), $stub, $is_nested, $filter)
44 | : $properties;
45 | };
46 | }
47 |
48 | public static function disable(): void
49 | {
50 | self::$enabled = false;
51 | }
52 |
53 | public static function enable(): void
54 | {
55 | self::$enabled = true;
56 | }
57 |
58 | abstract public function cast($target, Properties $properties, Stub|HerdStub $stub, bool $is_nested, int $filter = 0): array;
59 | }
60 |
--------------------------------------------------------------------------------
/.github/workflows/phpunit.yml:
--------------------------------------------------------------------------------
1 | name: PHPUnit
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | schedule:
9 | - cron: '0 14 * * 3' # Run Wednesdays at 2pm EST
10 |
11 | jobs:
12 | php-tests:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | php: [ 8.1, 8.2, 8.3, 8.4 ]
18 | laravel: [ 10.*, 11.*, 12.* ]
19 | dependency-version: [ stable, lowest ]
20 | exclude:
21 | - php: 8.1
22 | laravel: 12.*
23 | - php: 8.1
24 | laravel: 11.*
25 | - php: 8.0
26 | laravel: 11.*
27 | - php: 8.0
28 | laravel: 12.*
29 | - php: 8.0
30 | laravel: 10.*
31 |
32 | timeout-minutes: 10
33 | name: "${{ matrix.php }} / ${{ matrix.laravel }} (${{ matrix.dependency-version }})"
34 |
35 | steps:
36 | - name: Checkout code
37 | uses: actions/checkout@v3
38 |
39 | - name: Setup PHP
40 | uses: shivammathur/setup-php@v2
41 | with:
42 | php-version: ${{ matrix.php }}
43 | extensions: dom, curl, libxml, mbstring, zip, pcntl, bcmath, intl, iconv
44 | tools: composer:v2
45 |
46 | - name: Register composer cache directory
47 | id: composer-cache
48 | run: |
49 | echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
50 |
51 | - name: Cache dependencies
52 | uses: actions/cache@v4
53 | with:
54 | path: ${{ steps.composer-cache.outputs.dir }}
55 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
56 | restore-keys: |
57 | ${{ runner.os }}-composer-
58 |
59 | - name: Install dependencies
60 | run: |
61 | composer require --no-interaction --prefer-dist --prefer-${{ matrix.dependency-version }} --update-with-all-dependencies "laravel/framework:${{ matrix.laravel }}"
62 |
63 | - name: Execute tests
64 | run: vendor/bin/phpunit
65 |
--------------------------------------------------------------------------------
/src/Casters/ModelCaster.php:
--------------------------------------------------------------------------------
1 | attributesToDynamicProperties($target),
37 | $this->virtualProperties($target),
38 | $this->cutProperties($properties, $stub, $is_nested)
39 | );
40 | }
41 |
42 | protected function attributesToDynamicProperties(Model $obj): array
43 | {
44 | return Properties::make($obj->getAttributes())
45 | ->sortKeys()
46 | ->reorder($this->attribute_order)
47 | ->mapWithKeys(fn($value, $key) => [Key::dynamic($key) => $value])
48 | ->all();
49 | }
50 |
51 | protected function virtualProperties(Model $obj): array
52 | {
53 | return [
54 | Key::virtual('isDirty()') => $obj->isDirty(),
55 | ];
56 | }
57 |
58 | protected function cutProperties(Properties $properties, Stub|HerdStub $stub, bool $is_nested): array
59 | {
60 | $keep = [
61 | 'exists',
62 | 'wasRecentlyCreated',
63 | Key::protected('relations'),
64 | ];
65 |
66 | if (! $is_nested) {
67 | $keep = array_merge($keep, [
68 | Key::protected('connection'),
69 | Key::protected('table'),
70 | Key::protected('original'),
71 | Key::protected('changes'),
72 | ]);
73 | }
74 |
75 | return $properties
76 | ->only($keep)
77 | ->reorder($keep)
78 | ->applyCutsToStub($stub, $properties)
79 | ->all();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/src/Casters/CustomCaster.php:
--------------------------------------------------------------------------------
1 | operations)
34 | ->reduce(fn(Properties $properties, Closure $operation) => $operation($properties, $target), $properties)
35 | ->applyCutsToStub($stub, $properties)
36 | ->all();
37 | }
38 |
39 | public function reorder(array $rules): CustomCaster
40 | {
41 | $this->operations[] = fn(Properties $properties) => $properties->reorder($rules);
42 |
43 | return $this;
44 | }
45 |
46 | public function filter(callable $filter = null): CustomCaster
47 | {
48 | $this->operations[] = fn(Properties $properties) => $properties->filter($filter);
49 |
50 | return $this;
51 | }
52 |
53 | public function dynamic(string $key, Closure $callback): CustomCaster
54 | {
55 | $this->operations[] = fn(Properties $properties, $target) => $properties->putDynamic($key, $callback($target, $properties));
56 |
57 | return $this;
58 | }
59 |
60 | public function virtual(string $key, Closure $callback): CustomCaster
61 | {
62 | $this->operations[] = fn(Properties $properties, $target) => $properties->putVirtual($key, $callback($target, $properties));
63 |
64 | return $this;
65 | }
66 |
67 | public function only($keys): CustomCaster
68 | {
69 | $this->operations[] = fn(Properties $properties) => $properties->only($keys);
70 |
71 | return $this;
72 | }
73 |
74 | public function except($keys): CustomCaster
75 | {
76 | $this->operations[] = fn(Properties $properties) => $properties->except($keys);
77 |
78 | return $this;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Casters/BuilderCaster.php:
--------------------------------------------------------------------------------
1 | putVirtual('sql', $this->formatSql($target));
37 | $result->putProtected('connection', $target->getConnection());
38 |
39 | if ($target instanceof EloquentBuilder) {
40 | $result->copyAndCutProtected('model', $properties);
41 | $result->copyProtected('eagerLoad', $properties);
42 | }
43 |
44 | if ($target instanceof Relation) {
45 | $result->copyAndCutProtected('parent', $properties);
46 | $result->copyAndCutProtected('related', $properties);
47 | }
48 |
49 | $result->applyCutsToStub($stub, $properties);
50 |
51 | return $result->all();
52 | }
53 |
54 | protected function formatSql($target): string
55 | {
56 | $formatted = null;
57 |
58 | try {
59 | if (method_exists($target, 'toRawSql')) {
60 | $formatted = $target->toRawSql();
61 | }
62 | } catch (Throwable) {
63 | // Just fall back on naive formatter below
64 | }
65 |
66 | if (null === $formatted) {
67 | $sql = $target->toSql();
68 | $bindings = Arr::flatten($target->getBindings());
69 | $formatted = preg_replace_callback('/\?/', function() use (&$bindings) {
70 | return DB::getPdo()->quote(array_shift($bindings));
71 | }, $sql);
72 | }
73 |
74 | if (strlen($formatted) > 120 && class_exists(SqlFormatter::class)) {
75 | $formatted = SqlFormatter::format($formatted, false);
76 | }
77 |
78 | return $formatted;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | ## [3.0.0] - 2025-12-17
9 |
10 | ## [2.0.0] - 2023-12-16
11 |
12 | ## [1.1.0] - 2023-02-17
13 |
14 | ### Added
15 |
16 | - Added Laravel 10 support
17 |
18 | ## [1.0.1] - 2022-02-10
19 |
20 | ### Fixed
21 |
22 | - Fixed how bindings are merged into SQL queries when dumping a query builder
23 |
24 | ## [1.0.0] - 2022-01-24
25 |
26 | ## [0.5.0] - 2022-01-23
27 |
28 | ### Changed
29 |
30 | - Removed the `LaravelDumper` facade in favor of registering custom casters directly
31 |
32 | ## [0.4.0]
33 |
34 | ### Added
35 |
36 | - Added Laravel 9 support
37 |
38 | ## [0.3.0]
39 |
40 | ### Added
41 |
42 | - Added support for dynamic custom casters
43 | - Added `LaravelDumper` facade
44 | - Added custom `Properties` collection for easier manipulation of dumped properties
45 |
46 | ### Changed
47 |
48 | - Changed `Caster` interface to use `Properties` collection
49 | - Updated all casters to use new `Properties` collection
50 |
51 | ## [0.2.0]
52 |
53 | ### Added
54 |
55 | - Added `ddf()` and `dumpf()` for access to original `dd()` and `dump()` behavior
56 |
57 | ## [0.1.0]
58 |
59 | ### Added
60 |
61 | - Added support for Requests and Responses
62 | - Added support for ParameterBags
63 | - Added support for HeaderBags
64 |
65 | ### Changed
66 |
67 | - Improved tests
68 |
69 | ## [0.0.1]
70 |
71 | ### Added
72 |
73 | - Initial release
74 |
75 | # Keep a Changelog Syntax
76 |
77 | - `Added` for new features.
78 | - `Changed` for changes in existing functionality.
79 | - `Deprecated` for soon-to-be removed features.
80 | - `Removed` for now removed features.
81 | - `Fixed` for any bug fixes.
82 | - `Security` in case of vulnerabilities.
83 |
84 | [Unreleased]: https://github.com/glhd/laravel-dumper/compare/3.0.0...HEAD
85 |
86 | [3.0.0]: https://github.com/glhd/laravel-dumper/compare/2.0.0...3.0.0
87 |
88 | [2.0.0]: https://github.com/glhd/laravel-dumper/compare/1.1.0...2.0.0
89 |
90 | [1.1.0]: https://github.com/glhd/laravel-dumper/compare/1.0.1...1.1.0
91 |
92 | [1.0.1]: https://github.com/glhd/laravel-dumper/compare/1.0.0...1.0.1
93 |
94 | [1.0.0]: https://github.com/glhd/laravel-dumper/compare/0.5.0...1.0.0
95 |
96 | [0.5.0]: https://github.com/glhd/laravel-dumper/compare/0.4.0...0.5.0
97 |
98 | [0.4.0]: https://github.com/glhd/laravel-dumper/compare/0.3.0...0.4.0
99 |
100 | [0.3.0]: https://github.com/glhd/laravel-dumper/compare/0.2.0...0.3.0
101 |
102 | [0.2.0]: https://github.com/glhd/laravel-dumper/compare/0.1.0...0.2.0
103 |
104 | [0.1.0]: https://github.com/glhd/laravel-dumper/compare/0.0.1...0.1.0
105 |
106 | [0.0.1]: https://github.com/glhd/laravel-dumper
107 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tests/CasterTest.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Support/Properties.php:
--------------------------------------------------------------------------------
1 | cut += ($original->count() - $this->count());
27 |
28 | return $this;
29 | }
30 |
31 | public function cut($key, $default = null): CutStub
32 | {
33 | return new CutStub($this->get($key, $default));
34 | }
35 |
36 | public function cutProtected($key, $default = null): CutStub
37 | {
38 | return $this->cut(Key::protected($key), $default);
39 | }
40 |
41 | public function cutVirtual($key, $default = null): CutStub
42 | {
43 | return $this->cut(Key::virtual($key), $default);
44 | }
45 |
46 | public function cutDynamic($key, $default = null): CutStub
47 | {
48 | return $this->cut(Key::dynamic($key), $default);
49 | }
50 |
51 | public function get($key, $default = null)
52 | {
53 | $missing = new stdClass();
54 | foreach ($this->addPrefixes($key) as $prefixed_key) {
55 | $parameter = parent::get($prefixed_key, $missing);
56 | if ($missing !== $parameter) {
57 | return $parameter;
58 | }
59 | }
60 |
61 | return $default;
62 | }
63 |
64 | public function has($key)
65 | {
66 | $keys = is_array($key)
67 | ? $key
68 | : func_get_args();
69 |
70 | foreach ($keys as $value) {
71 | if (! $this->hasAny($this->addPrefixes($value))) {
72 | return false;
73 | }
74 | }
75 |
76 | return true;
77 | }
78 |
79 | public function hasAny($key)
80 | {
81 | if ($this->isEmpty()) {
82 | return false;
83 | }
84 |
85 | $keys = is_array($key)
86 | ? $key
87 | : func_get_args();
88 |
89 | foreach ($keys as $value) {
90 | if (array_key_exists($value, $this->items)) {
91 | return true;
92 | }
93 | }
94 |
95 | return false;
96 | }
97 |
98 | public function getProtected($key, $default = null)
99 | {
100 | return $this->get(Key::protected($key), $default);
101 | }
102 |
103 | public function getVirtual($key, $default = null)
104 | {
105 | return $this->get(Key::virtual($key), $default);
106 | }
107 |
108 | public function getDynamic($key, $default = null)
109 | {
110 | return $this->get(Key::dynamic($key), $default);
111 | }
112 |
113 | public function putProtected($key, $value): Properties
114 | {
115 | return $this->put(Key::protected($key), $value);
116 | }
117 |
118 | public function putVirtual($key, $value): Properties
119 | {
120 | return $this->put(Key::virtual($key), $value);
121 | }
122 |
123 | public function putDynamic($key, $value): Properties
124 | {
125 | return $this->put(Key::dynamic($key), $value);
126 | }
127 |
128 | public function copy($key, Properties $from, $default = null): Properties
129 | {
130 | return $this->put($key, $from->get($key, $default));
131 | }
132 |
133 | public function copyProtected($key, Properties $from, $default = null): Properties
134 | {
135 | return $this->copy(Key::protected($key), $from, $default);
136 | }
137 |
138 | public function copyVirtual($key, Properties $from, $default = null): Properties
139 | {
140 | return $this->copy(Key::virtual($key), $from, $default);
141 | }
142 |
143 | public function copyDynamic($key, Properties $from, $default = null): Properties
144 | {
145 | return $this->copy(Key::dynamic($key), $from, $default);
146 | }
147 |
148 | public function copyAndCut($key, Properties $from, $default = null): Properties
149 | {
150 | return $this->put($key, $from->cut($key, $default));
151 | }
152 |
153 | public function copyAndCutProtected($key, Properties $from, $default = null): Properties
154 | {
155 | return $this->copyAndCut(Key::protected($key), $from, $default);
156 | }
157 |
158 | public function copyAndCutVirtual($key, Properties $from, $default = null): Properties
159 | {
160 | return $this->copyAndCut(Key::virtual($key), $from, $default);
161 | }
162 |
163 | public function copyAndCutDynamic($key, Properties $from, $default = null): Properties
164 | {
165 | return $this->copyAndCut(Key::dynamic($key), $from, $default);
166 | }
167 |
168 | public function only($keys)
169 | {
170 | return $this->filter(function($value, $key) use ($keys) {
171 | return Str::is($keys, $key) || Str::is($keys, $this->stripPrefix($key));
172 | });
173 | }
174 |
175 | public function except($keys)
176 | {
177 | return $this->reject(function($value, $key) use ($keys) {
178 | return Str::is($keys, $key) || Str::is($keys, $this->stripPrefix($key));
179 | });
180 | }
181 |
182 | public function filter(callable $callback = null)
183 | {
184 | if (null === $callback) {
185 | $callback = static function($property) {
186 | if (is_array($property)) {
187 | return count($property);
188 | }
189 |
190 | if ($property instanceof Enumerable) {
191 | return $property->isNotEmpty();
192 | }
193 |
194 | return null !== $property;
195 | };
196 | }
197 |
198 | return parent::filter($callback);
199 | }
200 |
201 | public function reorder(array $rules): Properties
202 | {
203 | return $this->sortBy($this->getReorderCallback($rules));
204 | }
205 |
206 | protected function getReorderCallback(array $rules): Closure
207 | {
208 | $map = $this->createReorderMapFromRules($rules);
209 |
210 | return function($value, $key) use ($map) {
211 | $result = Arr::pull($map, '*');
212 |
213 | foreach ($map as $pattern => $position) {
214 | if ($key === $pattern || Str::is($pattern, $this->stripPrefix($key))) {
215 | $result = $position;
216 | }
217 | }
218 |
219 | return $result;
220 | };
221 | }
222 |
223 | protected function createReorderMapFromRules(array $rules): array
224 | {
225 | $rules = array_values($rules);
226 | $map = array_combine($rules, array_keys($rules));
227 |
228 | // Ensure that there's always a '*' pattern, defaulting to the end
229 | $map['*'] ??= count($map);
230 |
231 | return $map;
232 | }
233 |
234 | protected function stripPrefix(string $key): string
235 | {
236 | return str_replace($this->prefixes, '', $key);
237 | }
238 |
239 | protected function addPrefixes(string $key): array
240 | {
241 | if (Str::startsWith($key, $this->prefixes)) {
242 | return [$key];
243 | }
244 |
245 | return array_merge([$key], array_map(fn($prefix) => $prefix.$key, $this->prefixes));
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.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 |
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 |
--------------------------------------------------------------------------------
/.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/php.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 |
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 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
--------------------------------------------------------------------------------
/.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 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------