承接 esanj/remote-eloquent 相关项目开发

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

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

esanj/remote-eloquent

Composer 安装命令:

composer require esanj/remote-eloquent

包简介

Remote Eloquent models for Laravel — run native Eloquent queries against the Esanj Accounting service over gRPC or REST.

README 文档

README

Remote Eloquent lets a Laravel service talk to the Esanj Accounting database as if the tables were local. You extend RemoteModel instead of Laravel's Model, and every Eloquent query you write is compiled to SQL and executed on the Accounting service over gRPC or REST — you get rows back and hydrate real models. No local tables, no schema copies, no hand-written API client.

use Esanj\RemoteEloquent\Eloquent\RemoteModel;

class User extends RemoteModel
{
    protected $table = 'users';
    protected $casts = ['id' => 'int', 'email_verified_at' => 'datetime'];
}

User::where('email', 'ada@example.com')->first();   // SELECT ... over the wire
User::query()->orderByDesc('id')->paginate();       // count + page, remotely
$user->update(['name' => 'Ada L.']);                // UPDATE ... over the wire

Built to pair with esanj/auth-bridge. It reuses the same OAuth client-credentials model to authenticate, and defaults its credentials to the same ACCOUNTING_BRIDGE_* env vars.

How it works

Your service                         Remote Eloquent (this package)        Accounting service
   |  User::where(...)->get()              |                                     |
   |------------------------------------->| Eloquent builds the query           |
   |                                       | MySQL grammar compiles -> SQL + ?   |
   |                                       | attach cached Bearer token          |
   |                                       |----- {sql, bindings} (gRPC/REST) -->| authorize by feature
   |                                       |                                     | run single-table statement
   |                                       |<---- {rows, affected_rows} ---------| return string rows
   |<-- hydrated Collection<User> ---------| rows -> stdClass -> models (casts)  |

The heavy lifting is a custom database connection with no PDO. It reuses Laravel's MySQL query grammar and processor to turn any Eloquent query into a single SQL statement, then hands that statement to a transport (gRPC or REST) instead of a local driver. Because the whole query builder is reused, the entire Eloquent read/write surface keeps working: where, whereIn, orderBy, limit/offset, aggregates (count, sum), paginate, find, first, pluck, exists, create, update, delete, updateOrCreate, and so on.

Features

  • Drop-in Eloquent — extend RemoteModel; keep writing normal Eloquent.
  • Two transports, one contractrest (turnkey, zero extra deps) or grpc. Switch the default with one env var, or pin an individual model to a transport with protected $transport = 'grpc';.
  • Automatic token caching — an OAuth client-credentials token is fetched once, cached, and transparently refreshed shortly before it expires (via the refresh-token grant when available, otherwise re-requested).
  • Server-enforced authorization — every statement is gated on the Accounting side by the calling application's capability features (per table.operation). This package never bypasses that.
  • Typed exceptionsInvalidQueryException (422), QueryAccessDeniedException (403), TransportException, TokenRequestException.
  • RemoteQuery facade — for raw one-off statements and token control without a model.

Requirements

  • PHP 8.2+, Laravel 11 – 13 (tested on 13).
  • Reachable Accounting service (REST base URL and/or gRPC endpoint) plus an OAuth client id/secret issued by it.
  • gRPC only: the ext-grpc PHP extension plus the grpc/grpc and google/protobuf composer packages. The protobuf message classes ship with this package — no code generation needed. REST needs none of this.

Installation

This package lives in the Esanj monorepo.

composer require esanj/remote-eloquent
php artisan vendor:publish --tag="esanj-remote-eloquent-config"   # optional

The service provider and the RemoteQuery facade are auto-discovered. The package registers its own remote database connection — you do not touch config/database.php.

Configuration

Minimum .env (credentials fall back to the esanj/auth-bridge variables, so a service already wired for Accounting needs almost nothing new):

# Where the Accounting service lives (REST). Defaults to ACCOUNTING_BRIDGE_BASE_URL.
REMOTE_ELOQUENT_BASE_URL=https://accounting.example.com

# OAuth client-credentials. Default to ACCOUNTING_BRIDGE_CLIENT_ID / _SECRET.
REMOTE_ELOQUENT_CLIENT_ID=your-client-id
REMOTE_ELOQUENT_CLIENT_SECRET=your-client-secret

# Transport: rest (default) or grpc
REMOTE_ELOQUENT_DRIVER=rest

For gRPC, install the stack (ext-grpc, grpc/grpc, google/protobuf) and set:

REMOTE_ELOQUENT_DRIVER=grpc
REMOTE_ELOQUENT_GRPC_HOST=accounting.example.com:50051

The QueryRequest/QueryResponse protobuf classes ship with the package (namespace Esanj\RemoteEloquent\Grpc) and are wired as the defaults — you do not need to run protoc. Only set REMOTE_ELOQUENT_GRPC_REQUEST / REMOTE_ELOQUENT_GRPC_RESPONSE if you want to point at your own generated classes.

See src/config/remote_eloquent.php for every option (timeouts, token cache store & buffer, connection name, table prefix, …).

Usage

1. Write a model

use Esanj\RemoteEloquent\Eloquent\RemoteModel;

class Client extends RemoteModel
{
    protected $table = 'clients';           // must match the remote table name exactly
    protected $guarded = [];

    // Remote values arrive as strings; casts restore real types on the way in.
    protected $casts = [
        'id' => 'int',
        'revoked' => 'bool',
        'created_at' => 'datetime',
    ];
}

2. Use Eloquent normally

Client::find($id);
Client::where('revoked', false)->orderByDesc('id')->limit(20)->get();
Client::query()->paginate(15);
Client::count();

3. Writes

$user = User::create(['name' => 'Grace', 'email' => 'grace@example.com']);
$user->update(['name' => 'Grace H.']);
$user->delete();

⚠️ Auto-increment ids on insert. The current server contract returns affected rows for writes, not the new id, so create() cannot echo back a database-generated id unless the server is extended to return one. Prefer client-generated keys (UUID/ULID via HasUuids) for models you create remotely. Reads, updates and deletes are unaffected. See docs/GUIDE.md.

Per-model transport

REMOTE_ELOQUENT_DRIVER sets the default transport for every model. To pin a single model to a specific transport — regardless of that default — declare $transport:

class Ledger extends RemoteModel
{
    protected $table = 'ledgers';
    protected $transport = 'grpc';   // this model always talks gRPC; others use the default
}

Accepted values are 'rest' and 'grpc' (an unknown value throws InvalidArgumentException). Under the hood each transport has its own auto-registered remote connection (remote, remote_rest, remote_grpc), so models using different transports stay fully isolated. For dynamic decisions, override getTransportName(): ?string instead.

Raw queries (no model)

use Esanj\RemoteEloquent\Facades\RemoteQuery;

$rows = RemoteQuery::select('select * from `users` where `id` = ?', [7]);   // list<array<string,string>>
$affected = RemoteQuery::affectingStatement('update `users` set `name` = ? where `id` = ?', ['Ada', 7]);
RemoteQuery::forgetToken();   // drop the cached access token

Important constraints

These come from the Accounting server contract — Remote Eloquent surfaces them, it does not impose them:

Constraint What it means for you
Single table per query No JOIN/UNION, no cross-table whereHas. Load related data with separate queries. Violations throw InvalidQueryException.
Feature-gated Each table.operation must be permitted for your application on the Accounting side (e.g. USER_LISTSELECT users). Otherwise QueryAccessDeniedException.
Values are strings Every column comes back as a string, so declare $casts. Timestamps, ints and bools then hydrate correctly.
NULL reads as "" The contract stringifies NULL to an empty string. A nullable column that is NULL arrives as ''; a nullable-int cast becomes 0. Keep this in mind for nullable columns.
No transactions The remote connection cannot open a real DB transaction; DB::transaction() on it runs the callback without atomicity.

Error handling

use Esanj\RemoteEloquent\Exceptions\QueryAccessDeniedException;
use Esanj\RemoteEloquent\Exceptions\InvalidQueryException;
use Esanj\RemoteEloquent\Exceptions\TransportException;

try {
    User::create([...]);
} catch (QueryAccessDeniedException $e) {   // 403 — your app lacks the capability feature
    report($e);
} catch (InvalidQueryException $e) {        // 422 — the statement was rejected (e.g. a JOIN)
    report($e);
} catch (TransportException $e) {           // connectivity / auth / unexpected status
    report($e);
}

All extend Esanj\RemoteEloquent\Exceptions\RemoteEloquentException (which carries getContext()). They are surfaced unwrapped through Eloquent, so you catch them directly.

Documentation

For a step-by-step walkthrough — installation, a first model, both transports, generating the gRPC stubs, the write caveats and troubleshooting — see docs/GUIDE.md.

Credits

Developed and maintained by the Esanj Tech Team.

esanj/remote-eloquent 适用场景与选型建议

esanj/remote-eloquent 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 5 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 07 月 09 日, 在 PHP 生态内属于活跃度较高的组件。

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

围绕 esanj/remote-eloquent 我们能提供哪些服务?
定制开发 / 二次开发

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

BUG 修复 & 性能优化

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

项目外包 & 长期维护

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

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

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-07-09