承接 hongxunpan/eloquent-projection 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

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

hongxunpan/eloquent-projection

最新稳定版本:0.1.0

Composer 安装命令:

composer require hongxunpan/eloquent-projection

包简介

Eloquent read-model projection helpers for append/hidden/override style output shaping

README 文档

README

中文文档

hongxunpan/eloquent-projection provides a focused projection layer for Eloquent read models. It is intended for scenarios where output shaping has become stable, repetitive, and worth standardizing across services.

Typical use cases include:

  • appending derived accessor fields for read-only responses;
  • hiding source fields before serialization;
  • overriding masked fields with raw values under explicit conditions;
  • applying the same output-shaping rules to models, collections, and loaded relationships.

This package intentionally stays narrow in scope. It is not intended for:

  • write-path transformation;
  • persistence-time mutation;
  • DTO / presenter style response composition;
  • flattening relationships into custom top-level payloads.

If Eloquent's natural select, with, and toArray() flow already expresses the response cleanly, prefer the native approach over introducing projections.

Representative Business Scenarios

Administrative lists with conditional field exposure

A common scenario is an administrative list that returns a primary resource together with related identity data, while sensitive fields must be exposed differently depending on permissions. For example:

  • an event signup list needs alumniCard.mobile_phone_masked for regular operators;
  • privileged operators may inspect the raw mobile_phone value;
  • the list should continue to use the same loaded relation structure instead of rebuilding the payload manually.

In this case, applyRelation() allows the service layer to preserve the original relation tree while applying a consistent projection profile to the related model.

Detail responses with stable serialization rules

Some detail endpoints need more than Eloquent's default serialization, but still do not justify introducing a dedicated DTO layer. A typical example is an alumni profile, submission record, or activity detail view that requires:

  • appended accessor fields for presentation;
  • hidden source fields to avoid leaking raw data;
  • field overrides under explicit business conditions.

projectModel() is intended for this class of detail response: precise output shaping without forcing the service layer to hand-assemble arrays field by field.

Nested relationship projection in read pipelines

Read pipelines often traverse multiple relation levels, such as signup.alumniCard or record.user.profile. When output shaping rules need to be applied only to the nested relation, it is usually undesirable to flatten or rebuild the entire root response structure.

applyRelation() supports nested relation paths so that projection can stay local to the relevant read model while the root payload remains natural.

Array-oriented candidate and matching workflows

Some business flows consume arrays rather than mutable model instances. Examples include:

  • candidate matching lists;
  • review queues exported to downstream processors;
  • read-only comparison results assembled from model collections.

For these scenarios, projectList() provides a consistent way to convert a model list into projected arrays without duplicating serialization logic in each service.

Problems This Package Solves

Without a dedicated projection layer, the same read-model shaping logic tends to be reimplemented repeatedly across services. This package is designed to eliminate several recurring problems:

1. Repeated append + hidden + override code

Service methods often accumulate nearly identical code paths that clone models, append accessors, hide raw fields, convert to arrays, and conditionally overwrite output fields. Repetition makes behavior drift likely and increases the cost of maintenance.

2. Projection logic mixed into business services

When output shaping rules live directly inside service classes, the service layer starts handling both business decisions and serialization mechanics. That makes the core business flow harder to read, review, and evolve.

3. Inconsistent handling between model, list, and relation outputs

It is common for a rule to be implemented one way for a detail endpoint, another way for a list endpoint, and a third way for a related model inside another response. A reusable projection kernel keeps these paths aligned.

4. Weak separation between reusable kernel and scenario-specific policy

Most projects need two different layers:

  • a reusable execution kernel;
  • business-specific profiles that express when and how fields should be exposed.

This package provides the kernel, while leaving scenario-specific policy in the business codebase.

5. High regression risk when serialization rules evolve

When masking or exposure rules change, copy-pasted serialization logic forces updates across multiple services and relation paths. Centralized projection behavior reduces regression risk and makes rule changes easier to audit.

Before / After: Reducing Repetitive Service-Layer Code

Before

Without this package, service methods often end up mixing business flow and serialization mechanics:

$projectionModel = clone $submitRecord;
$projectionModel->append(['mobile_phone_masked']);
$projectionModel->makeHidden(['mobile_phone']);

$result = $projectionModel->toArray();

if ($canViewMobileFull) {
    $result['mobile_phone_masked'] = $projectionModel->getAttribute('mobile_phone');
}

return $result;

Typical problems in the “before” style:

  • the same pattern gets copied across detail, list, and relation flows;
  • field-shaping concerns compete with business logic in the same service method;
  • when masking rules change, multiple call sites must be updated together.

After

With hongxunpan/eloquent-projection, the same intent can be expressed declaratively:

use HongXunPan\EloquentProjection\FieldOverrideRule;
use HongXunPan\EloquentProjection\ProjectionApplier;
use HongXunPan\EloquentProjection\ProjectionProfile;

$profile = new ProjectionProfile(
    overrides: [
        new FieldOverrideRule(
            'mobile_phone_masked',
            'mobile_phone',
            $canViewMobileFull,
            true,
            true
        ),
    ],
);

return ProjectionApplier::projectModel($submitRecord, $profile);

What improves in the “after” style:

  • the service method expresses what should happen instead of reimplementing how output is shaped;
  • the same profile structure can be reused in model, list, and relation scenarios;
  • serialization behavior becomes easier to review, evolve, and test.

Installation

composer require hongxunpan/eloquent-projection

Compatibility

Current target compatibility:

  • PHP: ^8.0.20
  • illuminate/database: ^9.52 || ^10.0 || ^11.0 || ^12.0

Core Concepts

FieldOverrideRule

Represents a single field override rule.

  • targetField: the field exposed in the final output;
  • sourceField: the attribute used as the value source;
  • enabled: whether the override is active;
  • appendTargetField: whether targetField should be appended automatically;
  • hideSourceField: whether sourceField should be hidden automatically.

ProjectionProfile

A declarative configuration object that contains projection rules only.

  • append
  • hidden
  • overrides

ProjectionProfile is intentionally configuration-only and does not execute projection logic.

ProjectionApplier

Executes a ProjectionProfile against models, collections, or relationships.

Error Handling

The package keeps its public entry classes at the root namespace:

  • HongXunPan\EloquentProjection\ProjectionApplier
  • HongXunPan\EloquentProjection\ProjectionProfile
  • HongXunPan\EloquentProjection\FieldOverrideRule

At the same time, it exposes a lightweight package-level exception boundary under:

  • HongXunPan\EloquentProjection\Exception\ExceptionInterface
  • HongXunPan\EloquentProjection\Exception\InvalidProjectionArgumentException

Current exception behavior:

  • invalid model / iterable input;
  • empty relation paths;
  • unsupported relation value types;
  • invalid override rule items inside ProjectionProfile::$overrides;

all raise InvalidProjectionArgumentException.

This class extends PHP's native InvalidArgumentException, so consumers may either catch the native SPL type or the package-specific exception boundary, depending on how narrowly they want to scope error handling.

Parameter Reference

ProjectionProfile(array $append = [], array $hidden = [], array $overrides = [])

Parameter Role in the package Effect on output
$append Declares accessor or derived fields that should be appended before serialization The listed fields are included in serialized output
$hidden Declares fields that should be hidden before serialization The listed fields are removed from serialized output
$overrides Provides a list of field override rules May extend append/hidden behavior and may overwrite the final exposed value of target fields

FieldOverrideRule(string $targetField, string $sourceField, bool $enabled = true, bool $appendTargetField = false, bool $hideSourceField = false)

Parameter Role in the package Effect on source / target fields
$targetField Final field name exposed to consumers Receives the output value after projection
$sourceField Original attribute used as the value source Supplies the raw attribute value used for override
$enabled Controls whether source-to-target value override is executed true: target value is replaced with source value; false: target keeps its existing serialized value
$appendTargetField Automatically appends the target field during projection setup Ensures the target field participates in serialization
$hideSourceField Automatically hides the source field during projection setup Keeps the source field out of serialized output while still allowing it to be used internally

Current behavior note: $enabled controls only the value override step. appendTargetField and hideSourceField are resolved during projection setup regardless of whether $enabled is true or false.

Method Parameters

Method parameter Meaning
$model A single Eloquent model to mutate or project
$list An iterable collection of Eloquent models
$target A model or iterable that already owns the loaded relation to be projected
$relationName A relation path such as alumniCard or signup.alumniCard
$profile The projection rule set applied by ProjectionApplier

How Field Processing Works

The package applies projection in the following order:

  1. Start with the profile-level append and hidden definitions.
  2. Merge appendTargetField and hideSourceField contributions from each override rule.
  3. Apply append() and makeHidden() before serialization.
  4. For projectModel() / projectList(), serialize into arrays; for applyModel() / applyList() / applyRelation(), keep working on model instances.
  5. For each rule whose $enabled is true, copy the value from sourceField into targetField.

This means a single projection can both:

  • hide the raw source field from the final payload; and
  • expose a safer or conditionally upgraded target field for consumers.

Example Parameter Breakdown

For the following rule:

new FieldOverrideRule(
    'mobile_phone_masked',
    'mobile_phone',
    $canViewMobileFull,
    true,
    true
)

The parameters mean:

  • targetField = 'mobile_phone_masked'
    • the consumer-facing output field;
  • sourceField = 'mobile_phone'
    • the original raw field on the model;
  • enabled = $canViewMobileFull
    • if true, the output field is replaced with the raw phone value;
    • if false, the output field keeps its existing masked serialization result;
  • appendTargetField = true
    • ensures mobile_phone_masked appears in serialized output;
  • hideSourceField = true
    • ensures mobile_phone is not exposed directly in serialized output.

So the final output behavior is:

  • when permission is not granted: output keeps mobile_phone_masked, while mobile_phone stays hidden;
  • when permission is granted: output still hides mobile_phone, but mobile_phone_masked is overwritten with the raw mobile_phone value.

API Overview

Method Behavior Typical use case
applyModel(Model $model, ProjectionProfile $profile): void Mutates the given model instance in place The same model instance continues through the read pipeline
applyList(iterable $list, ProjectionProfile $profile): void Mutates a list of models in place Paginated or aggregated model lists
applyRelation(Model|iterable $target, string $relationName, ProjectionProfile $profile): void Applies a profile to a loaded relationship path Relationship-level output shaping without rebuilding the root payload
projectModel(Model $model, ProjectionProfile $profile): array Returns a projected array without requiring the caller to keep using the mutated output state of the same instance Detail responses or controlled one-off serialization
projectList(iterable $list, ProjectionProfile $profile): array Returns an array list of projected models Candidate lists or array-oriented downstream consumers

Usage Examples

Apply a projection to a loaded relationship

use HongXunPan\EloquentProjection\FieldOverrideRule;
use HongXunPan\EloquentProjection\ProjectionApplier;
use HongXunPan\EloquentProjection\ProjectionProfile;

$profile = new ProjectionProfile(
    overrides: [
        new FieldOverrideRule(
            'mobile_phone_masked',
            'mobile_phone',
            $canViewMobileFull,
            true,
            true
        ),
    ],
);

ProjectionApplier::applyRelation($records, 'alumniCard', $profile);

Project a single model into an array

$profile = new ProjectionProfile(
    overrides: [
        new FieldOverrideRule(
            'phone_masked',
            'phone',
            $canViewPhoneFull,
            true,
            true
        ),
    ],
);

return ProjectionApplier::projectModel($user, $profile);

Project a list into arrays

return ProjectionApplier::projectList($candidates, $profile);

Design Boundaries

Keep this package focused on output projection for Eloquent read models.

Avoid using it for:

  • write workflows;
  • bidirectional transformation APIs that both mutate and return shaped output;
  • page-specific response assembly;
  • thin helper APIs that only save one extra array access.

Recommended Integration Pattern

This package should remain the reusable kernel. Business projects should keep scenario-specific profiles in their own codebase, for example:

app/Projections/AlumniCard/AlumniCardProjectionProfiles.php
app/Projections/Activity/ActivityProjectionProfiles.php

Prefer organizing profiles by the projected entity rather than by page name, modal name, or one-off endpoint context.

License

MIT

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-13

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固