Skip to content

Commit

Permalink
Custom caster API (#4)
Browse files Browse the repository at this point in the history
* Refactor to dedicated Properties collection class

* Add custom caster support

* Add test to make sure multiple custom dumpers can be registered
  • Loading branch information
inxilpro authored Jan 21, 2022
1 parent f0737f7 commit 9341e04
Show file tree
Hide file tree
Showing 25 changed files with 680 additions and 147 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Release

on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"

permissions:
contents: write

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Create GitHub release
uses: docker://antonyurchenko/git-release:v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRE_RELEASE: true
DRAFT_RELEASE: true
3 changes: 3 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ format. This project adheres to [Semantic Versioning](https://semver.org/spec/v2

## [Unreleased]

### Added

- Added support for dynamic custom casters
- Added `LaravelDumper` facade
- Added custom `Properties` collection for easier manipulation of dumped properties

### Changed

- Changed `Caster` interface to use `Properties` collection
- Updated all casters to use new `Properties` collection

## [0.2.0]

### Added
Expand Down
54 changes: 36 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,47 @@ do a "full" dump with `ddf()` and `dumpf()`.
## Comparison to Default Output

You can see comparisons between the default `dd()` output and the `laravel-dumper` output
in the [diffs](/diffs/) directory of this repository.
in the [diffs directory of this repository](/diffs/).

## Custom Casters

> Please note, the API for custom casting is likely to change. Use at your own risk!
If there are objects in your project that you would like to customize the `dd()` behavior
for, you can register custom casters with the `LaravelDumper` facade:

It's possible to register your own casters for any class by publishing the `laravel-dumper`
config file and registering your custom classes in the `'casters'` section of the config.
```php
LaravelDumper::for(User::class)
->only(['attributes', 'exists', 'wasRecentlyCreated']) // Props to keep (or use `except` to exclude)
->virtual('admin', fn(User $user) => $user->isAdmin()) // Add virtual props
->filter() // Filter out empty/null props (accepts callback)
->reorder(['attributes', 'admin', '*']); // Adjust the order of props
```

Your custom casters should extend `Glhd\LaravelDumper\Casters\Caster` and would look
something like (this example would mean that dumped `User` objects would only include
a single piece of `summary` data):
The `reorder` method accepts an array of patterns. For example, the default `Model` caster
uses the following ordering rules:

```php
class UserCaster extends Caster
{
public static array $targets = [User::class];

public function cast($target, array $properties, Stub $stub, bool $is_nested, int $filter = 0): array
{
return [
Key::virtual('summary') => "$target->name ($target->email)",
];
}
}
$order = [
'id',
'*_id',
'*',
'*_at',
'created_at',
'updated_at',
'deleted_at',
];
```

This ensures that `id` is always first, followed by all foreign keys, followed by all
other attributes, and then finally followed by timestamp attributes (with `deleted_at` last).
By applying bespoke ordering rules, you can make sure that the properties you usually
need to debug are at the top of the `dd()` output.

### Advanced Custom Casters

It's also possible to register your own casters for any class by publishing the `laravel-dumper`
config file and registering your custom classes in the `'casters'` section of the config.
This gives you the same level of control over the `dd()` output as the core Symfony
VarDumper package, but is more complex to implement.

Your custom casters should extend `Glhd\LaravelDumper\Casters\Caster` and implement the
`cast` method. See any of our [built-in casters](/src/Casters/) for more details.
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@
"laravel": {
"providers": [
"Glhd\\LaravelDumper\\LaravelDumperServiceProvider"
]
],
"aliases": {
"LaravelDumper": "Glhd\\LaravelDumper\\Facades\\LaravelDumper"
}
}
}
}
26 changes: 13 additions & 13 deletions src/Casters/BuilderCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace Glhd\LaravelDumper\Casters;

use Glhd\LaravelDumper\Support\Properties;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as BaseBuilder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use SqlFormatter;
use Symfony\Component\VarDumper\Caster\CutStub;
use Symfony\Component\VarDumper\Cloner\Stub;

class BuilderCaster extends Caster
Expand All @@ -21,32 +21,32 @@ class BuilderCaster extends Caster

/**
* @param BaseBuilder|EloquentBuilder|Relation $target
* @param array $properties
* @param \Glhd\LaravelDumper\Support\Properties $properties
* @param \Symfony\Component\VarDumper\Cloner\Stub $stub
* @param bool $is_nested
* @param int $filter
* @return array
*/
public function cast($target, array $properties, Stub $stub, bool $is_nested, int $filter = 0): array
public function cast($target, Properties $properties, Stub $stub, bool $is_nested, int $filter = 0): array
{
$result = [
Key::virtual('sql') => $this->formatSql($target->toSql(), $target->getBindings()),
Key::protected('connection') => $target->getConnection(),
];
$result = new Properties();

$result->putVirtual('sql', $this->formatSql($target->toSql(), $target->getBindings()));
$result->putProtected('connection', $target->getConnection());

if ($target instanceof EloquentBuilder) {
$result[Key::protected('model')] = new CutStub($properties[Key::protected('model')]);
$result[Key::protected('eagerLoad')] = $properties[Key::protected('eagerLoad')];
$result->copyAndCutProtected('model', $properties);
$result->copyProtected('eagerLoad', $properties);
}

if ($target instanceof Relation) {
$result[Key::protected('parent')] = new CutStub($properties[Key::protected('parent')]);
$result[Key::protected('related')] = new CutStub($properties[Key::protected('related')]);
$result->copyAndCutProtected('parent', $properties);
$result->copyAndCutProtected('related', $properties);
}

$stub->cut += (count($properties) - count($result));
$result->applyCutsToStub($stub, $properties);

return $result;
return $result->all();
}

protected function formatSql($sql, $bindings): string
Expand Down
26 changes: 10 additions & 16 deletions src/Casters/CarbonCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Glhd\LaravelDumper\Casters;

use Carbon\CarbonInterface;
use Symfony\Component\VarDumper\Caster\Caster as BaseCaster;
use Glhd\LaravelDumper\Support\Properties;
use Symfony\Component\VarDumper\Cloner\Stub;

class CarbonCaster extends Caster
Expand All @@ -12,27 +12,21 @@ class CarbonCaster extends Caster

/**
* @param CarbonInterface $target
* @param array $properties
* @param \Glhd\LaravelDumper\Support\Properties $properties
* @param \Symfony\Component\VarDumper\Cloner\Stub $stub
* @param bool $is_nested
* @param int $filter
* @return array
*/
public function cast($target, array $properties, Stub $stub, bool $is_nested, int $filter = 0): array
public function cast($target, Properties $properties, Stub $stub, bool $is_nested, int $filter = 0): array
{
$date_key = Key::virtual('date');

$result = [$date_key => $target->format($this->getFormat($target))];

if (!$is_nested) {
$result += $properties;
}

$result = BaseCaster::filter($result, BaseCaster::EXCLUDE_NULL, [], $filtered);

$stub->cut += $filtered + count($properties) - count($result);

return $result;
return $properties
->putVirtual('date', $target->format($this->getFormat($target)))
->when($is_nested, fn(Properties $properties) => $properties->only('date'))
->filter()
->reorder(['date', '*'])
->applyCutsToStub($stub, $properties)
->all();
}

protected function getFormat(CarbonInterface $target): string
Expand Down
13 changes: 7 additions & 6 deletions src/Casters/Caster.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Glhd\LaravelDumper\Casters;

use Glhd\LaravelDumper\Support\Properties;
use Illuminate\Contracts\Foundation\Application;
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
use Symfony\Component\VarDumper\Cloner\Stub;
Expand All @@ -10,30 +11,30 @@ abstract class Caster
{
public static array $targets = [];

protected static bool $enabled = true;
public static bool $enabled = true;

public static function register(Application $app): void
{
$app->singleton(static::class);

foreach (static::$targets as $target) {
AbstractCloner::$defaultCasters[$target] = static function($target, array $properties, Stub $stub, bool $is_nested, int $filter = 0) {
return static::$enabled
? app(static::class)->cast($target, $properties, $stub, $is_nested, $filter)
return self::$enabled
? app(static::class)->cast($target, new Properties($properties), $stub, $is_nested, $filter)
: $properties;
};
}
}

public static function disable(): void
{
static::$enabled = false;
self::$enabled = false;
}

public static function enable(): void
{
static::$enabled = true;
self::$enabled = true;
}

abstract public function cast($target, array $properties, Stub $stub, bool $is_nested, int $filter = 0): array;
abstract public function cast($target, Properties $properties, Stub $stub, bool $is_nested, int $filter = 0): array;
}
26 changes: 10 additions & 16 deletions src/Casters/ContainerCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Glhd\LaravelDumper\Casters;

use Glhd\LaravelDumper\Support\Properties;
use Illuminate\Contracts\Container\Container;
use Symfony\Component\VarDumper\Caster\Caster as BaseCaster;
use Symfony\Component\VarDumper\Cloner\Stub;

class ContainerCaster extends Caster
Expand All @@ -17,23 +17,17 @@ class ContainerCaster extends Caster
'extenders',
];

public function cast($target, array $properties, Stub $stub, bool $is_nested, int $filter = 0): array
public function cast($target, Properties $properties, Stub $stub, bool $is_nested, int $filter = 0): array
{
$result = [];

if (!$is_nested) {
// We want to do this in a foreach so that we can re-order the list as well as filter it
foreach ($this->included as $property) {
$index = Key::protected($property);
if (isset($properties[$index])) {
$result[$index] = $properties[$index];
}
}
if ($is_nested) {
$stub->cut += $properties->count();
return [];
}

$result = BaseCaster::filter($result, BaseCaster::EXCLUDE_EMPTY, [], $filtered);
$stub->cut += $filtered + count($properties) - count($result);

return $result;
return $properties
->only($this->included)
->reorder($this->included)
->applyCutsToStub($stub, $properties)
->all();
}
}
69 changes: 69 additions & 0 deletions src/Casters/CustomCaster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Glhd\LaravelDumper\Casters;

use BadMethodCallException;
use Closure;
use Glhd\LaravelDumper\Support\Properties;
use Illuminate\Contracts\Foundation\Application;
use Symfony\Component\VarDumper\Cloner\Stub;

class CustomCaster extends Caster
{
protected array $operations = [];

public static function register(Application $app): void
{
throw new BadMethodCallException('Custom casters must be registered via the LaravelDumper facade.');
}

public function cast($target, Properties $properties, Stub $stub, bool $is_nested, int $filter = 0): array
{
return collect($this->operations)
->reduce(fn(Properties $properties, Closure $operation) => $operation($properties, $target), $properties)
->applyCutsToStub($stub, $properties)
->all();
}

public function reorder(array $rules): CustomCaster
{
$this->operations[] = fn(Properties $properties) => $properties->reorder($rules);

return $this;
}

public function filter(callable $filter = null): CustomCaster
{
$this->operations[] = fn(Properties $properties) => $properties->filter($filter);

return $this;
}

public function dynamic(string $key, Closure $callback): CustomCaster
{
$this->operations[] = fn(Properties $properties, $target) => $properties->putDynamic($key, $callback($target, $properties));

return $this;
}

public function virtual(string $key, Closure $callback): CustomCaster
{
$this->operations[] = fn(Properties $properties, $target) => $properties->putVirtual($key, $callback($target, $properties));

return $this;
}

public function only($keys): CustomCaster
{
$this->operations[] = fn(Properties $properties) => $properties->only($keys);

return $this;
}

public function except($keys): CustomCaster
{
$this->operations[] = fn(Properties $properties) => $properties->except($keys);

return $this;
}
}
Loading

0 comments on commit 9341e04

Please sign in to comment.