PHPackages                             gpalyan/proto-resource - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. gpalyan/proto-resource

ActiveLibrary[API Development](/categories/api)

gpalyan/proto-resource
======================

Laravel gRPC resources for transforming data to protobuf messages

v1.0.0(1mo ago)10[1 issues](https://github.com/GaiPalyan/proto-resource/issues)MITPHPPHP ^8.4CI passing

Since Apr 17Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/GaiPalyan/proto-resource)[ Packagist](https://packagist.org/packages/gpalyan/proto-resource)[ RSS](/packages/gpalyan-proto-resource/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (7)Versions (4)Used By (0)

gRPC Resource Mapper
====================

[](#grpc-resource-mapper)

[![Tests](https://github.com/GaiPalyan/proto-resource/actions/workflows/tests.yml/badge.svg)](https://github.com/GaiPalyan/proto-resource/actions/workflows/tests.yml)[![codecov](https://camo.githubusercontent.com/c6be667ccfae44554c9a47655a47b203213e2d5b3191a815f2911627de2045eb/68747470733a2f2f636f6465636f762e696f2f67682f47616950616c79616e2f70726f746f2d7265736f757263652f6272616e63682f6d61696e2f67726170682f62616467652e737667)](https://codecov.io/gh/GaiPalyan/proto-resource)[![Latest Stable Version](https://camo.githubusercontent.com/443d4bfc024f67f813cdb5a8c04035d26332eac24795d2ffd66f2071f1a5d55d/68747470733a2f2f706f7365722e707567782e6f72672f6770616c79616e2f70726f746f2d7265736f757263652f762f737461626c65)](https://packagist.org/packages/gpalyan/proto-resource)[![License](https://camo.githubusercontent.com/80cd79e725c59e0d93f0cb7d9c5cf08c7f767067ca892560ec33f5837868ecde/68747470733a2f2f706f7365722e707567782e6f72672f6770616c79616e2f70726f746f2d7265736f757263652f6c6963656e7365)](https://packagist.org/packages/gpalyan/proto-resource)

When you implement a gRPC service in PHP, `protoc` generates stub classes for your messages — `UserMessage`, `AddressMessage`, etc. Filling them manually is repetitive and error-prone:

```
// Without this library — manual, every time
$message = new UserMessage();
$message->setId($user->id);
$message->setName($user->full_name);

$address = new AddressMessage();
$address->setCity($user->address->city);
$message->setAddress($address);

return $message;
```

This library solves that problem the Laravel way — the same pattern as `JsonResource` for REST APIs, but for gRPC and Protobuf. You define a resource class once, and it handles mapping, nesting, collections, and Field Masks automatically:

```
#[ProtoMessage(UserMessage::class)]
class UserResource extends Resource
{
    public static function fields(): array
    {
        return [
            new Value('id'),
            new Value('name', 'full_name'),
            new Relation('address', 'address', AddressResource::class),
        ];
    }
}

// In your gRPC handler
return (new UserResource($user, $request->getFieldMask()))->toProto();
```

Features
--------

[](#features)

- **Declarative resources** — define structure once per resource class
- **Field Masks** — clients request only needed fields
- **Nested resources** — compose resources for nested objects
- **Collections** — repeated field support
- **Map fields** — protobuf `map` support
- **OneOf** — Protobuf union type support
- **Raw filling** — map source data directly without a resource class

Requirements
------------

[](#requirements)

`google/protobuf` is not listed as an explicit dependency — any project using this library necessarily has generated proto stub classes, which already bring `google/protobuf` in as a transitive dependency.

Installation
------------

[](#installation)

```
composer require gpalyan/proto-resource
```

Quick Start
-----------

[](#quick-start)

### 1. Create a Resource

[](#1-create-a-resource)

```
use ProtoResource\Attributes\ProtoMessage;
use ProtoResource\Resources\Resource;
use ProtoResource\Types\Value;

#[ProtoMessage(UserMessage::class)]
class UserResource extends Resource
{
    public static function fields(): array
    {
        return [
            new Value('id'),
            new Value('name', 'full_name'),
        ];
    }
}
```

### 2. Use the Resource

[](#2-use-the-resource)

```
// Without Field Mask (all fields)
$grpcMessage = (new UserResource($user))->toProto();

// With Field Mask (specific fields only)
$mask = new \Google\Protobuf\FieldMask();
$mask->setPaths(['id', 'name', 'address.city']);

$grpcMessage = (new UserResource($user, $mask))->toProto();

// With array of paths (shorthand, no FieldMask object needed)
$grpcMessage = (new UserResource($user, ['id', 'name', 'address.city']))->toProto();
```

### 3. Collections

[](#3-collections)

```
$collection = UserResource::collection($users, $mask);

foreach ($collection as $grpcMessage) {
    // Each $grpcMessage is a ready gRPC message
}
```

Complete Example
----------------

[](#complete-example)

```
#[ProtoMessage(AddressMessage::class)]
class AddressResource extends Resource
{
    public static function fields(): array
    {
        return [
            new Value('city'),
            new Value('street'),
            new Value('zipCode', 'zip_code'),
        ];
    }
}

#[ProtoMessage(PostMessage::class)]
class PostResource extends Resource
{
    public static function fields(): array
    {
        return [
            new Value('id'),
            new Value('title'),
            new Value('publishedAt', fn(Post $post) => $post->published_at?->timestamp),
            new Relation('author', 'user', UserResource::class),
        ];
    }
}

#[ProtoMessage(UserMessage::class)]
class UserResource extends Resource
{
    public static function fields(): array
    {
        return [
            new Value('id'),
            new Value('name', 'full_name'),
            new Relation('address', 'address', AddressResource::class),
            new Repeated('posts', 'posts', PostResource::class),
            new Map('metadata'),
        ];
    }
}
```

Field Types
-----------

[](#field-types)

### Value

[](#value)

Maps a single scalar field.

```
// Same name in source and message
new Value('id'),

// Custom source key
new Value('name', 'full_name'),

// Computed value via callback
new Value('status', fn(User $user) => $user->is_active ? 'active' : 'inactive'),
```

### Relation

[](#relation)

Maps a nested object to a proto message. Pass a Resource class to define the nested structure.

```
use ProtoResource\Types\Relation;

// Using a nested resource (recommended)
new Relation('address', 'address', AddressResource::class),

// With explicit proto class override
new Relation('address', 'address', AddressResource::class, AddressMessage::class),

// Without a resource — raw filling (see Raw Filling)
new Relation('address', fn($u) => [...], messageClass: AddressMessage::class),
```

### Repeated

[](#repeated)

Maps a collection of items to a repeated proto field.

```
use ProtoResource\Types\Repeated;

// Using a nested resource (recommended)
new Repeated('posts', 'posts', PostResource::class),

// Without a resource — raw filling (see Raw Filling)
new Repeated('posts', fn($u) => $u->posts->toArray(), messageClass: PostMessage::class),
```

### Map

[](#map)

Maps an associative array to a protobuf `map` field.

```
use ProtoResource\Types\Map;

// map — scalar values
new Map('metadata'),

// With custom source key
new Map('metadata', 'meta'),

// map — using a nested resource
new Map('items', 'items', ItemResource::class),

// map — with explicit proto class
new Map('items', 'items', ItemResource::class, ItemMessage::class),
```

### OneOf

[](#oneof)

Resolves one field from a group based on a callable resolver.

```
use ProtoResource\Types\OneOf;

new OneOf(
    name: 'result',
    fields: [
        'success' => new Relation('success', fn($r) => $r->data, SuccessResource::class),
        'error'   => new Relation('error',   fn($r) => $r->error, ErrorResource::class),
    ],
    resolver: fn($r) => match($r->status) {
        'ok'   => 'success',
        'fail' => 'error',
        default => null,
    }
),
```

Raw Filling
-----------

[](#raw-filling)

When source data maps 1-to-1 to proto field names, defining a dedicated resource class is unnecessary overhead. `Relation`, `Repeated`, and `Map` can be used without a resource class by passing only `messageClass`. In this mode the library falls back to **raw filling** — it maps source data directly to proto message fields by matching property names to setter methods (`set` + `ucfirst($key)`).

```
new Relation('address', 'address', messageClass: AddressMessage::class),
new Repeated('posts', 'posts', messageClass: PostMessage::class),
```

### Scalar fields

[](#scalar-fields)

Property names in the source data must match the proto field names exactly:

```
$user->address = (object) ['city' => 'Moscow', 'street' => 'Arbat'];
// maps to: $addressMessage->setCity('Moscow'), $addressMessage->setStreet('Arbat')
```

### Nested objects

[](#nested-objects)

For nested objects, raw filling reads the `@param` type from the setter's docblock to instantiate the child message:

```
$user->address = (object) [
    'city'     => 'Moscow',
    'district' => (object) ['name' => 'Central'],
];
```

This works out of the box with `protoc`-generated stubs, which always include typed `@param` annotations:

```
/**
 * @param \App\Messages\DistrictMessage $var
 */
public function setDistrict($var) { ... }
```

> For hand-written message classes without `@param` docblocks, nested object filling is skipped. For complex mappings, key renaming, or type coercion, define a dedicated resource class instead.

License
-------

[](#license)

MIT

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance70

Regular maintenance activity

Popularity2

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

53d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/79068666?v=4)[Gai Palyan](/maintainers/GaiPalyan)[@GaiPalyan](https://github.com/GaiPalyan)

---

Top Contributors

[![GaiPalyan](https://avatars.githubusercontent.com/u/79068666?v=4)](https://github.com/GaiPalyan "GaiPalyan (5 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/gpalyan-proto-resource/health.svg)

```
[![Health](https://phpackages.com/badges/gpalyan-proto-resource/health.svg)](https://phpackages.com/packages/gpalyan-proto-resource)
```

###  Alternatives

[craftcms/cms

Craft CMS

3.6k3.6M2.9k](/packages/craftcms-cms)[swisnl/json-api-client

A PHP package for mapping remote JSON:API resources to Eloquent like models and collections.

211492.1k17](/packages/swisnl-json-api-client)[icawebdesign/hibp-php

PHP library for accessing the Have I Been Pwned API.

2548.8k2](/packages/icawebdesign-hibp-php)[directorytree/dummy

418.9k](/packages/directorytree-dummy)[vazaha-nl/mastodon-api-client

A fully typed and feature complete Mastodon API client for PHP

271.8k1](/packages/vazaha-nl-mastodon-api-client)[craftpulse/craft-typesense

Craft Plugin that synchronises with Typesense

122.7k](/packages/craftpulse-craft-typesense)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
