tcds-io/php-jackson-laravel 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

tcds-io/php-jackson-laravel

Composer 安装命令:

composer require tcds-io/php-jackson-laravel

包简介

A Laravel plugin to inject and respond serializable objects in controllers

README 文档

README

CI

Laravel integration for tcds-io/php-jackson, a type-safe object mapper inspired by Jackson (Java).

This package lets you:

  • Inject typed objects (and collections) directly into controllers and route callables
  • Deserialize from JSON body, query params, form data, and route params
  • Automatically serialize your return values back to JSON using PHP-Jackson
  • Cast model attributes

🚀 Installation

composer require tcds-io/php-jackson-laravel

Laravel auto‑discovers the service provider. No configuration file is needed for the attribute-based setup. If you disabled package discovery, add the provider manually:

Manually adding the service provider

'providers' => [
    // ...
    Tcds\Io\Jackson\Laravel\Providers\JacksonLaravelObjectMapperProvider::class,
],

⚙️ How it works

  1. Mark request DTO parameters with #[JacksonInject].
  2. Mark return values that should be serialized with #[JacksonResponse], or return jackson($value) when response metadata belongs in the method body.
  3. The plugin inspects your method parameter types and PHPDoc generics.
  4. It builds those objects from:
    • Route params ({id})
    • Query / form data
    • JSON body
  5. Your return value is serialized using PHP‑Jackson.

🧩 Controller-based injection & response

use Tcds\Io\Jackson\Laravel\Attributes\JacksonInject;
use Tcds\Io\Jackson\Laravel\Attributes\JacksonResponse;

class FooBarController
{
    /**
     * @param list<Foo> $items
     * @return list<Foo>
     */
    #[JacksonResponse]
    public function list(#[JacksonInject] array $items): array
    {
        return $items;
    }

    #[JacksonResponse]
    public function read(int $id, #[JacksonInject] Foo $foo): Foo
    {
        return new Foo(
            id: $id,
            a: $foo->a,
            b: $foo->b,
            type: $foo->type,
        );
    }
}

Routes:

Route::post('/resource', [FooBarController::class, 'list']);
Route::post('/resource/{id}', [FooBarController::class, 'read']);

🧩 Callable routes with typed injection

use Illuminate\Support\Facades\Route;
use Tcds\Io\Jackson\Laravel\Attributes\JacksonInject;
use Tcds\Io\Jackson\Laravel\Attributes\JacksonResponse;

Route::get('/callable/resource/{id}',
    #[JacksonResponse]
    fn (int $id) => new Foo(id: $id, a: "aaa", b: "get", type: Type::AAA)
);

Route::post('/callable/resource',
    #[JacksonResponse]
    fn (#[JacksonInject] Foo $foo) => $foo
);

Route::post('/callable',
    /**
     * @param list<Foo> $items
     * @return list<Foo>
     */
    #[JacksonResponse]
    fn (#[JacksonInject] array $items): array => $items,
);

🏷 Response status and headers

use Tcds\Io\Jackson\Laravel\Attributes\JacksonInject;
use Tcds\Io\Jackson\Laravel\Attributes\JacksonResponse;

class GreetController
{
    #[JacksonResponse(status: 201, headers: ['X-Resource' => 'greeting'])]
    public function __invoke(#[JacksonInject] Greeting $greeting): Greeting
    {
        return $greeting;
    }
}
  • #[JacksonInject] on a parameter forces php-jackson to deserialize the request payload into that type, even when the type is not registered in mappers (or has been opted out via reader: null).
  • #[JacksonResponse(status: 201)] on a method (or callable) serializes the return value via php-jackson and wraps it in a JsonResponse with the given status and headers. status defaults to 200.

Both attributes also work on route closures:

Route::post(
    '/greet',
    #[JacksonResponse(status: 201)]
    fn(#[JacksonInject] Greeting $greeting): Greeting => $greeting,
);

For cases where the response metadata belongs in the method body, use the jackson() helper:

use function Tcds\Io\Jackson\Laravel\jackson;

class GreetController
{
    public function store(#[JacksonInject] Greeting $greeting)
    {
        return jackson($greeting)
            ->status(201)
            ->header('X-Resource', 'greeting');
    }
}

🛠 Extending Jackson configuration

The attribute-based API is the default path for request and response mapping, but the package also has a central configuration file for application-wide behavior. Use it to add global serialization rules, customize request parsing errors, and inject custom params into mapped objects.

Publish config

Publish the configuration file when you need global mappers, error handlers, custom params, or model casts:

php artisan vendor:publish --tag=jackson # creates jackson/config.php

Add global mappers

Global mappers tell Jackson to always handle a type in requests and responses without adding attributes to every controller method or route closure. They are useful when a DTO is part of your app-wide API contract, or when you want to define custom read/write behavior once instead of repeating it at each endpoint.

return [
    'mappers' => [
        // Simple automatic request and response mapping
        Address::class => [],

        // Custom readers and writers
        Foo::class => [
            'reader' => fn(array $data) => new Foo($data['a'], $data['b']),
            'writer' => fn(Foo $foo) => ['a' => $foo->a, 'b' => $foo->b],
        ],

        // Use Laravel services to inject values that do not come from the request
        User::class => [
            'reader' => fn () => Auth::user(),
            'writer' => fn (User $user) => [
                'id' => $user->id,
                'name' => $user->name,
                // 'email' => $user->email, // exclude sensitive fields
            ],
        ],
    ],
];

With a configured mapper, Jackson can read and write that type without #[JacksonInject] or #[JacksonResponse]:

class FooBarController
{
    public function read(int $id, Foo $foo): Foo
    {
        return new Foo(
            id: $id,
            a: $foo->a,
            b: $foo->b,
            type: $foo->type,
        );
    }
}

Responses serialized this way use status 200 and do not support custom headers. Use #[JacksonResponse] or jackson($value) when an endpoint needs a different status code or response headers.

Customizing the error handler

If parsing fails, php-jackson-laravel converts a php-jackson UnableToParseValue exception into a 400 Bad Request response by default:

{
  "message": "Unable to parse value at .type",
  "expected": ["AAA", "BBB"],
  "given": "string"
}

Set errors.request when your API needs a different response shape or status code. The handler receives an UnableToParseValue exception and must return a Throwable, typically an HttpResponseException.

use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Tcds\Io\Jackson\Exception\UnableToParseValue;

return [
    'errors' => [
        'request' => fn(UnableToParseValue $e) => new HttpResponseException(
            new JsonResponse([
                'error' => $e->getMessage(),
                'hint' => 'Check the request body format.',
            ], Response::HTTP_UNPROCESSABLE_ENTITY),
        ),
    ],
    // ...
];

The UnableToParseValue exception exposes:

  • $e->getMessage() — human-readable description of the failure
  • $e->expected — list of accepted values or types
  • $e->given — the type or value that was received

Inject custom params

Custom params let you add request-scoped values that do not come from the URL, query string, form data, or JSON body. This is useful for authenticated user IDs, tenant IDs, locale, feature flags, or any value you want available while Jackson builds a request object.

use App\Services\AuthTokenService;
use Psr\Container\ContainerInterface;

return [
    'params' => fn(ContainerInterface $container) => [
        'userId' => $container->get(AuthTokenService::class)->userId(),
    ],
    // ...
];

Those values are merged into the data used to build Jackson objects, so a DTO can receive them like any other constructor field:

readonly class InvoiceQuery
{
    public function __construct(
        public int $userId,
        public ?string $customer = null,
    ) {}
}

Model casts

Configured classes automatically become castable in Eloquent models:

class User extends Model
{
    use JacksonCasts;

    protected $fillable = [
        'settings',
    ];

    protected $casts = [
        'settings' => UserSettings::class,
    ];
}

🔧 Development

composer install
composer tests       # runs cs:check + phpstan
composer cs:fix      # auto-fix code style

End-to-end integration tests run a real Laravel app (default Laravel 13). To target Laravel 12:

LARAVEL_VERSION='^12.0' tests/install.sh
cd tests/blog && php artisan test

📦 Related packages

tcds-io/php-jackson-laravel 适用场景与选型建议

tcds-io/php-jackson-laravel 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 1.78k 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 12 月 02 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 tcds-io/php-jackson-laravel 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 tcds-io/php-jackson-laravel 我们能提供哪些服务?
定制开发 / 二次开发

基于 tcds-io/php-jackson-laravel 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 1.78k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 3
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-12-02