+24
.github/workflows/code-style.yml
+24
.github/workflows/code-style.yml
···
1
+
name: Code Style
2
+
3
+
on:
4
+
pull_request:
5
+
branches: [ main, dev ]
6
+
7
+
jobs:
8
+
php-cs-fixer:
9
+
runs-on: ubuntu-latest
10
+
11
+
steps:
12
+
- name: Checkout code
13
+
uses: actions/checkout@v4
14
+
15
+
- name: Setup PHP
16
+
uses: shivammathur/setup-php@v2
17
+
with:
18
+
php-version: 8.3
19
+
extensions: gmp, mbstring, json
20
+
coverage: none
21
+
tools: php-cs-fixer
22
+
23
+
- name: Run PHP CS Fixer
24
+
run: php-cs-fixer fix --dry-run --diff --verbose
+30
.github/workflows/tests.yml
+30
.github/workflows/tests.yml
···
1
+
name: Tests
2
+
3
+
on:
4
+
pull_request:
5
+
branches: [ main, dev ]
6
+
7
+
jobs:
8
+
test:
9
+
runs-on: ubuntu-latest
10
+
11
+
name: Tests (PHP 8.2 - Laravel 12)
12
+
13
+
steps:
14
+
- name: Checkout code
15
+
uses: actions/checkout@v4
16
+
17
+
- name: Setup PHP
18
+
uses: shivammathur/setup-php@v2
19
+
with:
20
+
php-version: 8.2
21
+
extensions: gmp, mbstring, json
22
+
coverage: none
23
+
24
+
- name: Install dependencies
25
+
run: |
26
+
composer require "laravel/framework:^12.0" "orchestra/testbench:^10.0" --no-interaction --no-update
27
+
composer update --prefer-stable --prefer-dist --no-interaction
28
+
29
+
- name: Execute tests
30
+
run: vendor/bin/phpunit
+6
.gitignore
+6
.gitignore
+35
.php-cs-fixer.php
+35
.php-cs-fixer.php
···
1
+
<?php
2
+
3
+
use PhpCsFixer\Config;
4
+
use PhpCsFixer\Finder;
5
+
6
+
$finder = Finder::create()
7
+
->in(__DIR__ . '/src')
8
+
->in(__DIR__ . '/tests')
9
+
->name('*.php')
10
+
->notName('*.blade.php')
11
+
->ignoreDotFiles(true)
12
+
->ignoreVCS(true);
13
+
14
+
return (new Config())
15
+
->setRules([
16
+
'@PSR12' => true,
17
+
'array_syntax' => ['syntax' => 'short'],
18
+
'ordered_imports' => ['sort_algorithm' => 'alpha'],
19
+
'no_unused_imports' => true,
20
+
'not_operator_with_successor_space' => true,
21
+
'trailing_comma_in_multiline' => true,
22
+
'phpdoc_scalar' => true,
23
+
'unary_operator_spaces' => true,
24
+
'binary_operator_spaces' => true,
25
+
'blank_line_before_statement' => [
26
+
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
27
+
],
28
+
'phpdoc_single_line_var_spacing' => true,
29
+
'phpdoc_var_without_name' => true,
30
+
'method_argument_space' => [
31
+
'on_multiline' => 'ensure_fully_multiline',
32
+
'keep_multiple_spaces_after_comma' => true,
33
+
],
34
+
])
35
+
->setFinder($finder);
+67
CONTRIBUTING.md
+67
CONTRIBUTING.md
···
1
+
# Contributing
2
+
3
+
Contributions are **welcome** and will be fully **credited**.
4
+
5
+
## Etiquette
6
+
7
+
This project is open source, and as such, the maintainers give their free time to build and maintain the source code held within. They make the code freely available in the hope that it will be of use to other developers. It would be extremely unfair for them to suffer abuse or anger for their hard work.
8
+
9
+
Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the world that developers are civilized and selfless people.
10
+
11
+
It's the duty of the maintainer to ensure that all submissions to the project are of sufficient quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
12
+
13
+
## Viability
14
+
15
+
When requesting or submitting new features, first consider whether it might be useful to others. Open source projects are used by many developers, who may have entirely different needs to your own. Think about whether or not your feature is likely to be used by other users of the project.
16
+
17
+
## Procedure
18
+
19
+
### Before Filing an Issue
20
+
21
+
- Search existing issues to avoid duplicates
22
+
- Check the [documentation](docs/) to ensure it's not a usage question
23
+
- Provide a clear title and description
24
+
- Include steps to reproduce the issue
25
+
- Specify your environment (PHP version, Laravel version, Signal version, mode)
26
+
- Include relevant code samples and full error messages
27
+
28
+
### Before Submitting a Pull Request
29
+
30
+
- **Discuss non-trivial changes first** by opening an issue
31
+
- **Fork the repository** and create a feature branch from `main`
32
+
- **Follow all requirements** listed below
33
+
- **Write tests** for your changes
34
+
- **Update documentation** if behavior changes
35
+
- **Run code style checks** with `vendor/bin/php-cs-fixer fix`
36
+
- **Ensure all tests pass** with `vendor/bin/phpunit`
37
+
- **Write clear commit messages** that explain what and why
38
+
39
+
## Requirements
40
+
41
+
- **[PSR-12 Coding Standard](https://www.php-fig.org/psr/psr-12/)** - Run `vendor/bin/php-cs-fixer fix` to automatically fix code style issues.
42
+
43
+
- **Add tests** - Your patch won't be accepted if it doesn't have tests. All tests must use [PHPUnit](https://phpunit.de/).
44
+
45
+
- **Document any change in behaviour** - Make sure the `README.md`, `docs/`, and any other relevant documentation are kept up-to-date.
46
+
47
+
- **Consider our release cycle** - We follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
48
+
49
+
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
50
+
51
+
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
52
+
53
+
## Running Tests
54
+
55
+
```bash
56
+
vendor/bin/phpunit
57
+
```
58
+
59
+
## Code Style
60
+
61
+
Signal follows PSR-12 coding standard. Run PHP CS Fixer before submitting:
62
+
63
+
```bash
64
+
vendor/bin/php-cs-fixer fix
65
+
```
66
+
67
+
**Happy coding**!
+114
-739
README.md
+114
-739
README.md
···
1
-
# Signal
1
+
[](https://github.com/socialdept/atp-signals)
2
2
3
-
**Laravel package for building Signals that respond to AT Protocol events**
3
+
<h3 align="center">
4
+
Consume real-time AT Protocol events in your Laravel application.
5
+
</h3>
4
6
5
-
Signal provides a clean, Laravel-style interface for consuming real-time events from the AT Protocol. Supports both **Jetstream** (simplified JSON events) and **Firehose** (raw CBOR/CAR format) for maximum flexibility. Build reactive applications, AppViews, and custom indexers that respond to posts, likes, follows, and other social interactions on the AT Protocol network.
7
+
<p align="center">
8
+
<br>
9
+
<a href="https://packagist.org/packages/socialdept/atp-signals" title="Latest Version on Packagist"><img src="https://img.shields.io/packagist/v/socialdept/atp-signals.svg?style=flat-square"></a>
10
+
<a href="https://packagist.org/packages/socialdept/atp-signals" title="Total Downloads"><img src="https://img.shields.io/packagist/dt/socialdept/atp-signals.svg?style=flat-square"></a>
11
+
<a href="https://github.com/socialdept/atp-signals/actions/workflows/tests.yml" title="GitHub Tests Action Status"><img src="https://img.shields.io/github/actions/workflow/status/socialdept/atp-signals/tests.yml?branch=main&label=tests&style=flat-square"></a>
12
+
<a href="LICENSE" title="Software License"><img src="https://img.shields.io/github/license/socialdept/atp-signals?style=flat-square"></a>
13
+
</p>
6
14
7
15
---
8
16
9
-
## Features
17
+
## What is Signal?
10
18
11
-
- ๐ **Dual-Mode Support** - Choose between Jetstream (JSON) or Firehose (CBOR/CAR) based on your needs
12
-
- ๐ **WebSocket Connection** - Connect to AT Protocol with automatic reconnection and exponential backoff
13
-
- ๐ฏ **Signal-based Architecture** - Clean, testable event handlers (avoiding Laravel's "listener" naming collision)
14
-
- โญ **Wildcard Collection Filtering** - Match multiple collections with patterns like `app.bsky.feed.*`
15
-
- ๐๏ธ **AppView Ready** - Full support for custom collections and building AT Protocol AppViews
16
-
- ๐พ **Cursor Management** - Resume from last position after disconnections (Database, Redis, or File storage)
17
-
- โก **Queue Integration** - Process events asynchronously with Laravel queues
18
-
- ๐ **Auto-Discovery** - Automatically find and register Signals in `app/Signals`
19
-
- ๐งช **Testing Tools** - Test your Signals with sample data
20
-
- ๐ ๏ธ **Artisan Commands** - Full CLI support for managing and testing Signals
19
+
**Signal** is a Laravel package that lets you respond to real-time events from the AT Protocol network. Build reactive applications, custom feeds, moderation tools, analytics systems, and AppViews by listening to posts, likes, follows, and other social interactions as they happen across Bluesky and the entire AT Protocol ecosystem.
21
20
22
-
---
21
+
Think of it as Laravel's event listeners, but for the decentralized social web.
23
22
24
-
## Table of Contents
23
+
## Why use Signal?
25
24
26
-
<!-- TOC -->
27
-
* [Installation](#installation)
28
-
* [Quick Start](#quick-start)
29
-
* [Jetstream vs Firehose](#jetstream-vs-firehose)
30
-
* [Creating Signals](#creating-signals)
31
-
* [Filtering Events](#filtering-events)
32
-
* [Queue Integration](#queue-integration)
33
-
* [Configuration](#configuration-1)
34
-
* [Programmatic Usage](#programmatic-usage)
35
-
* [Available Commands](#available-commands)
36
-
* [Testing](#testing)
37
-
* [External Resources](#external-resources)
38
-
* [Examples](#examples)
39
-
* [Requirements](#requirements)
40
-
* [License](#license)
41
-
* [Support](#support)
42
-
<!-- TOC -->
43
-
44
-
---
45
-
46
-
## Installation
47
-
48
-
Install the package via Composer:
25
+
- **Laravel-style code** - Familiar patterns you already know
26
+
- **Real-time processing** - React to events as they happen
27
+
- **Dual-mode support** - Choose Jetstream (efficient JSON) or Firehose (comprehensive CBOR)
28
+
- **AppView ready** - Full support for custom collections and protocols
29
+
- **Production features** - Queue integration, cursor management, auto-reconnection
30
+
- **Easy filtering** - Target specific collections, operations, and users with wildcards
31
+
- **Built-in testing** - Test your signals with sample data
49
32
50
-
```bash
51
-
composer require socialdept/signal
52
-
```
53
-
54
-
Run the installation command:
55
-
56
-
```bash
57
-
php artisan signal:install
58
-
```
59
-
60
-
This will:
61
-
- Publish the configuration file to `config/signal.php`
62
-
- Publish the database migration
63
-
- Run migrations (with confirmation)
64
-
- Display next steps
65
-
66
-
### Manual Installation
67
-
68
-
If you prefer manual installation:
69
-
70
-
```bash
71
-
php artisan vendor:publish --tag=signal-config
72
-
php artisan vendor:publish --tag=signal-migrations
73
-
php artisan migrate
74
-
```
75
-
76
-
---
77
-
78
-
## Quick Start
79
-
80
-
### 1. Create Your First Signal
81
-
82
-
```bash
83
-
php artisan make:signal NewPostSignal
84
-
```
85
-
86
-
This creates `app/Signals/NewPostSignal.php`:
33
+
## Quick Example
87
34
88
35
```php
89
-
<?php
90
-
91
-
namespace App\Signals;
92
-
93
-
use SocialDept\Signal\Events\SignalEvent;
94
-
use SocialDept\Signal\Signals\Signal;
36
+
use SocialDept\AtpSignals\Events\SignalEvent;
37
+
use SocialDept\AtpSignals\Signals\Signal;
95
38
96
39
class NewPostSignal extends Signal
97
40
{
···
117
60
}
118
61
```
119
62
120
-
### 2. Start Consuming Events
121
-
122
-
```bash
123
-
php artisan signal:consume
124
-
```
125
-
126
-
Your Signal will now respond to new posts on the AT Protocol network in real-time!
127
-
128
-
---
63
+
Run `php artisan signal:consume` and start responding to every post on Bluesky in real-time.
129
64
130
-
## Jetstream vs Firehose
131
-
132
-
Signal supports two modes for consuming AT Protocol events. Choose based on your use case:
133
-
134
-
### Jetstream Mode (Default)
135
-
136
-
**Best for**: Standard Bluesky collections, production efficiency, lower bandwidth
65
+
## Installation
137
66
138
67
```bash
139
-
php artisan signal:consume --mode=jetstream
68
+
composer require socialdept/atp-signals
69
+
php artisan signal:install
140
70
```
141
71
142
-
**Characteristics:**
143
-
- โ
Simplified JSON events (easy to work with)
144
-
- โ
Server-side collection filtering (efficient)
145
-
- โ
Lower bandwidth and processing overhead
146
-
- โ ๏ธ Only standard `app.bsky.*` collections get create/update operations
147
-
- โ ๏ธ Custom collections only receive delete operations
72
+
That's it. [Read the installation docs โ](docs/installation.md)
148
73
149
-
**Jetstream URL options:**
150
-
- US East: `wss://jetstream2.us-east.bsky.network` (default)
151
-
- US West: `wss://jetstream1.us-west.bsky.network`
74
+
## Getting Started
152
75
153
-
### Firehose Mode
76
+
Once installed, you're three steps away from consuming AT Protocol events:
154
77
155
-
**Best for**: Custom collections, AppViews, comprehensive indexing
78
+
### 1. Create a Signal
156
79
157
80
```bash
158
-
php artisan signal:consume --mode=firehose
81
+
php artisan make:signal NewPostSignal
159
82
```
160
83
161
-
**Characteristics:**
162
-
- โ
**All operations** (create, update, delete) for **all collections**
163
-
- โ
Perfect for custom collections (e.g., `app.yourapp.*.collection`)
164
-
- โ
Full CBOR/CAR decoding with package `revolution/laravel-bluesky`
165
-
- โ ๏ธ Client-side filtering only (higher bandwidth)
166
-
- โ ๏ธ More processing overhead
167
-
168
-
**When to use Firehose:**
169
-
- Building an AT Protocol AppView
170
-
- Working with custom collections
171
-
- Need create/update events for non-standard collections
172
-
- Building comprehensive indexes
173
-
174
-
### Configuration
175
-
176
-
Set your preferred mode in `.env`:
177
-
178
-
```env
179
-
# Use Jetstream (default)
180
-
SIGNAL_MODE=jetstream
181
-
182
-
# Or use Firehose for custom collections
183
-
SIGNAL_MODE=firehose
184
-
```
185
-
186
-
### Example: Custom Collections
187
-
188
-
If you're tracking custom collections like `app.offprint.beta.publication`, you **must** use Firehose mode:
84
+
### 2. Define What to Listen For
189
85
190
86
```php
191
-
class PublicationSignal extends Signal
87
+
public function collections(): ?array
192
88
{
193
-
public function collections(): ?array
194
-
{
195
-
return ['app.offprint.beta.publication'];
196
-
}
197
-
198
-
public function handle(SignalEvent $event): void
199
-
{
200
-
// With Jetstream: Only sees deletes โ
201
-
// With Firehose: Sees creates, updates, deletes โ
202
-
}
89
+
return ['app.bsky.feed.post'];
203
90
}
204
91
```
205
92
206
-
---
93
+
### 3. Start Consuming
207
94
208
-
## Creating Signals
95
+
```bash
96
+
php artisan signal:consume
97
+
```
209
98
210
-
### Basic Signal Structure
99
+
Your Signal will now handle every matching event from the network. [Read the quickstart guide โ](docs/quickstart.md)
211
100
212
-
Every Signal extends the base `Signal` class and must implement:
101
+
## What can you build?
213
102
214
-
```php
215
-
use SocialDept\Signal\Enums\SignalEventType;
216
-
use SocialDept\Signal\Events\SignalEvent;
217
-
use SocialDept\Signal\Signals\Signal;
103
+
- **Custom feeds** - Curate content based on your own algorithms
104
+
- **Moderation tools** - Detect and flag problematic content automatically
105
+
- **Analytics platforms** - Track engagement, trends, and network growth
106
+
- **Social integrations** - Mirror content to other platforms in real-time
107
+
- **Notification systems** - Alert users about relevant activity
108
+
- **AppViews** - Build custom AT Protocol applications with your own collections
218
109
219
-
class MySignal extends Signal
220
-
{
221
-
// Required: Define which event types to listen for
222
-
public function eventTypes(): array
223
-
{
224
-
return [SignalEventType::Commit];
110
+
## Documentation
225
111
226
-
// Or use strings:
227
-
// return ['commit'];
228
-
}
112
+
**Getting Started**
113
+
- [Installation](docs/installation.md) - Detailed setup instructions
114
+
- [Quickstart Guide](docs/quickstart.md) - Build your first Signal
115
+
- [Jetstream vs Firehose](docs/modes.md) - Choose the right mode
229
116
230
-
// Required: Handle the event
231
-
public function handle(SignalEvent $event): void
232
-
{
233
-
// Your logic here
234
-
}
235
-
}
236
-
```
117
+
**Building Signals**
118
+
- [Creating Signals](docs/signals.md) - Complete Signal reference
119
+
- [Filtering Events](docs/filtering.md) - Target specific collections and operations
120
+
- [Queue Integration](docs/queues.md) - Process events asynchronously
237
121
238
-
**Enums vs Strings**: Signal supports both typed enums and strings for better IDE support and type safety. Use whichever you prefer!
239
-
240
-
### Event Types
241
-
242
-
Three event types are available:
243
-
244
-
| Enum | String | Description | Use Cases |
245
-
|-----------------------------|--------------|--------------------------------------------------|---------------------------------------|
246
-
| `SignalEventType::Commit` | `'commit'` | Repository commits (posts, likes, follows, etc.) | Content creation, social interactions |
247
-
| `SignalEventType::Identity` | `'identity'` | Identity changes (handle updates) | User profile tracking |
248
-
| `SignalEventType::Account` | `'account'` | Account status changes | Account monitoring |
122
+
**Advanced**
123
+
- [Configuration](docs/configuration.md) - All config options explained
124
+
- [Testing](docs/testing.md) - Test your Signals
125
+
- [Examples](docs/examples.md) - Real-world use cases
249
126
250
-
### Accessing Event Data
127
+
## Example Use Cases
251
128
129
+
### Track User Growth
252
130
```php
253
-
use SocialDept\Signal\Enums\SignalCommitOperation;
254
-
255
-
public function handle(SignalEvent $event): void
131
+
public function collections(): ?array
256
132
{
257
-
// Common properties
258
-
$did = $event->did; // User's DID
259
-
$kind = $event->kind; // Event type
260
-
$timestamp = $event->timeUs; // Microsecond timestamp
261
-
262
-
// Commit events
263
-
if ($event->isCommit()) {
264
-
$collection = $event->getCollection(); // e.g., 'app.bsky.feed.post'
265
-
$operation = $event->getOperation(); // SignalCommitOperation enum
266
-
$record = $event->getRecord(); // The actual record data
267
-
$rkey = $event->commit->rkey; // Record key
268
-
269
-
// Use enum for type-safe comparisons
270
-
if ($operation === SignalCommitOperation::Create) {
271
-
// Handle new records
272
-
}
273
-
274
-
// Or get string value
275
-
$operationString = $operation->value; // 'create', 'update', or 'delete'
276
-
}
277
-
278
-
// Identity events
279
-
if ($event->isIdentity()) {
280
-
$handle = $event->identity->handle;
281
-
}
282
-
283
-
// Account events
284
-
if ($event->isAccount()) {
285
-
$active = $event->account->active;
286
-
$status = $event->account->status;
287
-
}
133
+
return ['app.bsky.graph.follow'];
288
134
}
289
135
```
290
136
291
-
---
292
-
293
-
## Filtering Events
294
-
295
-
### Collection Filtering (with Wildcards!)
296
-
297
-
Filter events by AT Protocol collection.
298
-
299
-
**Important**:
300
-
- **Jetstream mode**: Exact collection names are sent as URL parameters for server-side filtering. Wildcards work for client-side filtering only.
301
-
- **Firehose mode**: All filtering is client-side. Wildcards work normally.
302
-
137
+
### Monitor Content Moderation
303
138
```php
304
-
// Exact match - only posts
305
-
public function collections(): ?array
306
-
{
307
-
return ['app.bsky.feed.post'];
308
-
}
309
-
310
-
// Wildcard - all feed events
311
139
public function collections(): ?array
312
140
{
313
141
return ['app.bsky.feed.*'];
314
142
}
315
143
316
-
// Multiple patterns
317
-
public function collections(): ?array
144
+
public function shouldQueue(): bool
318
145
{
319
-
return [
320
-
'app.bsky.feed.post',
321
-
'app.bsky.feed.repost',
322
-
'app.bsky.graph.*', // All graph collections
323
-
];
324
-
}
325
-
326
-
// No filter - all collections
327
-
public function collections(): ?array
328
-
{
329
-
return null;
146
+
return true; // Process in background
330
147
}
331
148
```
332
149
333
-
### Common Collection Patterns
334
-
335
-
| Pattern | Matches |
336
-
|--------------------|-----------------------------|
337
-
| `app.bsky.feed.*` | Posts, likes, reposts, etc. |
338
-
| `app.bsky.graph.*` | Follows, blocks, mutes |
339
-
| `app.bsky.actor.*` | Profile updates |
340
-
| `app.bsky.*` | All Bluesky collections |
341
-
342
-
### Operation Filtering
343
-
344
-
Filter events by operation type (only applies to `commit` events):
345
-
150
+
### Build Custom Collections (AppView)
346
151
```php
347
-
use SocialDept\Signal\Enums\SignalCommitOperation;
348
-
349
-
// Only handle creates (using enum)
350
-
public function operations(): ?array
152
+
public function collections(): ?array
351
153
{
352
-
return [SignalCommitOperation::Create];
353
-
}
354
-
355
-
// Only handle creates and updates (using enums)
356
-
public function operations(): ?array
357
-
{
358
-
return [
359
-
SignalCommitOperation::Create,
360
-
SignalCommitOperation::Update,
361
-
];
362
-
}
363
-
364
-
// Only handle deletes (using string)
365
-
public function operations(): ?array
366
-
{
367
-
return ['delete'];
368
-
}
369
-
370
-
// No filter - all operations (default)
371
-
public function operations(): ?array
372
-
{
373
-
return null;
154
+
return ['app.yourapp.custom.collection'];
374
155
}
375
156
```
376
157
377
-
**Available operations:**
378
-
379
-
| Enum | String | Description |
380
-
|---------------------------------|------------|---------------------------|
381
-
| `SignalCommitOperation::Create` | `'create'` | New records created |
382
-
| `SignalCommitOperation::Update` | `'update'` | Existing records modified |
383
-
| `SignalCommitOperation::Delete` | `'delete'` | Records removed |
384
-
385
-
**Example use cases:**
386
-
```php
387
-
use SocialDept\Signal\Enums\SignalCommitOperation;
158
+
[See more examples โ](docs/examples.md)
388
159
389
-
// Signal that only handles new posts (not edits)
390
-
class NewPostSignal extends Signal
391
-
{
392
-
public function collections(): ?array
393
-
{
394
-
return ['app.bsky.feed.post'];
395
-
}
160
+
## Key Features Explained
396
161
397
-
public function operations(): ?array
398
-
{
399
-
return [SignalCommitOperation::Create];
400
-
}
401
-
}
162
+
### Jetstream vs Firehose
402
163
403
-
// Signal that only handles content updates
404
-
class ContentUpdateSignal extends Signal
405
-
{
406
-
public function collections(): ?array
407
-
{
408
-
return ['app.bsky.feed.post'];
409
-
}
164
+
Signal supports two modes for consuming AT Protocol events:
410
165
411
-
public function operations(): ?array
412
-
{
413
-
return [SignalCommitOperation::Update];
414
-
}
415
-
}
166
+
- **Jetstream** (default) - Simplified JSON events with server-side filtering
167
+
- **Firehose** - Raw CBOR/CAR format with client-side filtering
416
168
417
-
// Signal that handles deletions for cleanup
418
-
class CleanupSignal extends Signal
419
-
{
420
-
public function collections(): ?array
421
-
{
422
-
return ['app.bsky.feed.*'];
423
-
}
424
-
425
-
public function operations(): ?array
426
-
{
427
-
return [SignalCommitOperation::Delete];
428
-
}
429
-
}
430
-
```
169
+
[Learn more about modes โ](docs/modes.md)
431
170
432
-
### DID Filtering
171
+
### Wildcard Filtering
433
172
434
-
Filter events by specific users:
173
+
Match multiple collections with patterns:
435
174
436
175
```php
437
-
public function dids(): ?array
176
+
public function collections(): ?array
438
177
{
439
178
return [
440
-
'did:plc:z72i7hdynmk6r22z27h6tvur', // Specific user
441
-
'did:plc:ragtjsm2j2vknwkz3zp4oxrd', // Another user
179
+
'app.bsky.feed.*', // All feed events
180
+
'app.bsky.graph.*', // All graph events
181
+
'app.yourapp.*', // All your custom collections
442
182
];
443
183
}
444
184
```
445
185
446
-
### Custom Filtering
186
+
[Learn more about filtering โ](docs/filtering.md)
447
187
448
-
Add complex filtering logic:
188
+
### Queue Integration
189
+
190
+
Process events asynchronously for better performance:
449
191
450
192
```php
451
-
public function shouldHandle(SignalEvent $event): bool
193
+
public function shouldQueue(): bool
452
194
{
453
-
// Only handle posts with images
454
-
if ($event->isCommit() && $event->commit->collection === 'app.bsky.feed.post') {
455
-
$record = $event->getRecord();
456
-
return isset($record->embed);
457
-
}
458
-
459
195
return true;
460
196
}
461
197
```
462
198
463
-
---
464
-
465
-
## Queue Integration
466
-
467
-
Process events asynchronously using Laravel queues:
468
-
469
-
```php
470
-
class HeavyProcessingSignal extends Signal
471
-
{
472
-
public function eventTypes(): array
473
-
{
474
-
return ['commit'];
475
-
}
476
-
477
-
// Enable queueing
478
-
public function shouldQueue(): bool
479
-
{
480
-
return true;
481
-
}
482
-
483
-
// Optional: Customize queue
484
-
public function queue(): string
485
-
{
486
-
return 'high-priority';
487
-
}
488
-
489
-
// Optional: Customize connection
490
-
public function queueConnection(): string
491
-
{
492
-
return 'redis';
493
-
}
494
-
495
-
public function handle(SignalEvent $event): void
496
-
{
497
-
// This runs in a queue job
498
-
$this->performExpensiveOperation($event);
499
-
}
500
-
501
-
// Handle failures
502
-
public function failed(SignalEvent $event, \Throwable $exception): void
503
-
{
504
-
Log::error('Signal failed', [
505
-
'event' => $event->toArray(),
506
-
'error' => $exception->getMessage(),
507
-
]);
508
-
}
509
-
}
510
-
```
511
-
512
-
---
513
-
514
-
## Configuration
515
-
516
-
Configuration is stored in `config/signal.php`:
517
-
518
-
### Consumer Mode
519
-
520
-
Choose between Jetstream (JSON) or Firehose (CBOR) mode:
521
-
522
-
```php
523
-
'mode' => env('SIGNAL_MODE', 'jetstream'),
524
-
```
525
-
526
-
Options:
527
-
- `jetstream` - JSON events, server-side filtering (default)
528
-
- `firehose` - CBOR events, client-side filtering (required for custom collections)
529
-
530
-
### Jetstream Configuration
531
-
532
-
```php
533
-
'websocket_url' => env('SIGNAL_JETSTREAM_URL', 'wss://jetstream2.us-east.bsky.network'),
534
-
```
535
-
536
-
Available endpoints:
537
-
- **US East**: `wss://jetstream2.us-east.bsky.network` (default)
538
-
- **US West**: `wss://jetstream1.us-west.bsky.network`
539
-
540
-
### Firehose Configuration
541
-
542
-
```php
543
-
'firehose' => [
544
-
'host' => env('SIGNAL_FIREHOSE_HOST', 'bsky.network'),
545
-
],
546
-
```
547
-
548
-
The raw firehose endpoint is: `wss://{host}/xrpc/com.atproto.sync.subscribeRepos`
549
-
550
-
### Cursor Storage
551
-
552
-
Choose how to store cursor positions:
553
-
554
-
```php
555
-
'cursor_storage' => env('SIGNAL_CURSOR_STORAGE', 'database'),
556
-
```
557
-
558
-
| Driver | Best For | Configuration |
559
-
|------------|-------------------------------|--------------------|
560
-
| `database` | Production, multi-server | Default connection |
561
-
| `redis` | High performance, distributed | Redis connection |
562
-
| `file` | Development, single server | Storage path |
563
-
564
-
### Environment Variables
565
-
566
-
Add to your `.env`:
567
-
568
-
```env
569
-
# Consumer Mode
570
-
SIGNAL_MODE=jetstream # or 'firehose' for custom collections
571
-
572
-
# Jetstream Configuration
573
-
SIGNAL_JETSTREAM_URL=wss://jetstream2.us-east.bsky.network
574
-
575
-
# Firehose Configuration (only needed if using firehose mode)
576
-
SIGNAL_FIREHOSE_HOST=bsky.network
577
-
578
-
# Optional Configuration
579
-
SIGNAL_CURSOR_STORAGE=database
580
-
SIGNAL_QUEUE_CONNECTION=redis
581
-
SIGNAL_QUEUE=signal
582
-
SIGNAL_BATCH_SIZE=100
583
-
SIGNAL_RATE_LIMIT=1000
584
-
```
585
-
586
-
### Auto-Discovery
587
-
588
-
Signals are automatically discovered from `app/Signals`. Disable if needed:
589
-
590
-
```php
591
-
'auto_discovery' => [
592
-
'enabled' => true,
593
-
'path' => app_path('Signals'),
594
-
'namespace' => 'App\\Signals',
595
-
],
596
-
```
597
-
598
-
Or manually register Signals:
599
-
600
-
```php
601
-
'signals' => [
602
-
\App\Signals\NewPostSignal::class,
603
-
\App\Signals\NewFollowSignal::class,
604
-
],
605
-
```
606
-
607
-
---
608
-
609
-
## Programmatic Usage
610
-
611
-
You can start and stop the consumer programmatically using the `Signal` facade:
612
-
613
-
```php
614
-
use SocialDept\Signal\Facades\Signal;
615
-
616
-
// Start consuming events (uses mode from config)
617
-
Signal::start();
618
-
619
-
// Start from a specific cursor
620
-
Signal::start(cursor: 123456789);
621
-
622
-
// Check which mode is active
623
-
$mode = Signal::getMode(); // Returns 'jetstream' or 'firehose'
624
-
625
-
// Stop consuming events
626
-
Signal::stop();
627
-
```
628
-
629
-
The facade automatically resolves the correct consumer (Jetstream or Firehose) based on your `config('signal.mode')` setting. This allows you to:
630
-
631
-
- Switch between modes by changing configuration
632
-
- Start consumers from application code (e.g., in a custom command)
633
-
- Integrate Signal into existing application workflows
634
-
635
-
```php
636
-
// Example: Start consumer based on environment
637
-
if (app()->environment('production')) {
638
-
config(['signal.mode' => 'jetstream']); // Use efficient Jetstream
639
-
} else {
640
-
config(['signal.mode' => 'firehose']); // Use comprehensive Firehose for testing
641
-
}
642
-
643
-
Signal::start();
644
-
```
645
-
646
-
---
199
+
[Learn more about queues โ](docs/queues.md)
647
200
648
201
## Available Commands
649
202
650
-
### `signal:install`
651
-
Install the package (publish config, migrations, run migrations)
652
-
653
203
```bash
204
+
# Install Signal
654
205
php artisan signal:install
655
-
```
656
206
657
-
### `signal:consume`
658
-
Start consuming events from AT Protocol
659
-
660
-
```bash
661
-
# Use default mode from config
662
-
php artisan signal:consume
663
-
664
-
# Override mode
665
-
php artisan signal:consume --mode=jetstream
666
-
php artisan signal:consume --mode=firehose
667
-
668
-
# Start from specific cursor
669
-
php artisan signal:consume --cursor=123456789
207
+
# Create a new Signal
208
+
php artisan make:signal YourSignal
670
209
671
-
# Start fresh (ignore stored cursor)
672
-
php artisan signal:consume --fresh
673
-
674
-
# Combine options
675
-
php artisan signal:consume --mode=firehose --fresh
676
-
```
677
-
678
-
### `signal:list`
679
-
List all registered Signals
680
-
681
-
```bash
210
+
# List all registered Signals
682
211
php artisan signal:list
683
-
```
684
212
685
-
### `signal:make`
686
-
Create a new Signal class
687
-
688
-
```bash
689
-
php artisan make:signal NewPostSignal
213
+
# Start consuming events
214
+
php artisan signal:consume
690
215
691
-
# With options
692
-
php artisan make:signal FollowSignal --type=commit --collection=app.bsky.graph.follow
216
+
# Test a Signal with sample data
217
+
php artisan signal:test YourSignal
693
218
```
694
219
695
-
### `signal:test`
696
-
Test a Signal with sample data
220
+
## Requirements
697
221
698
-
```bash
699
-
php artisan signal:test NewPostSignal
700
-
```
222
+
- PHP 8.2+
223
+
- Laravel 11+
224
+
- WebSocket support (enabled by default)
701
225
702
-
---
703
-
704
-
## Testing
705
-
706
-
Signal includes a comprehensive test suite. Test your Signals:
707
-
708
-
### Unit Testing
709
-
710
-
```php
711
-
use SocialDept\Signal\Events\CommitEvent;
712
-
use SocialDept\Signal\Events\SignalEvent;
713
-
714
-
class NewPostSignalTest extends TestCase
715
-
{
716
-
/** @test */
717
-
public function it_handles_new_posts()
718
-
{
719
-
$signal = new NewPostSignal();
720
-
721
-
$event = new SignalEvent(
722
-
did: 'did:plc:test',
723
-
timeUs: time() * 1000000,
724
-
kind: 'commit',
725
-
commit: new CommitEvent(
726
-
rev: 'test',
727
-
operation: 'create',
728
-
collection: 'app.bsky.feed.post',
729
-
rkey: 'test',
730
-
record: (object) [
731
-
'text' => 'Hello World!',
732
-
'createdAt' => now()->toIso8601String(),
733
-
],
734
-
),
735
-
);
736
-
737
-
$signal->handle($event);
738
-
739
-
// Assert your expected behavior
740
-
}
741
-
}
742
-
```
743
-
744
-
### Testing with Artisan
745
-
746
-
```bash
747
-
php artisan signal:test NewPostSignal
748
-
```
749
-
750
-
---
751
-
752
-
## External Resources
226
+
## Resources
753
227
754
228
- [AT Protocol Documentation](https://atproto.com/)
229
+
- [Bluesky API Docs](https://docs.bsky.app/)
755
230
- [Firehose Documentation](https://docs.bsky.app/docs/advanced-guides/firehose)
756
-
- [Bluesky Lexicon](https://atproto.com/lexicons)
231
+
- [Jetstream Documentation](https://github.com/bluesky-social/jetstream)
757
232
758
-
---
233
+
## Support & Contributing
759
234
760
-
## Examples
235
+
Found a bug or have a feature request? [Open an issue](https://github.com/socialdept/atp-signals/issues).
761
236
762
-
### Monitor All Feed Activity
237
+
Want to contribute? We'd love your help! Check out the [contribution guidelines](CONTRIBUTING.md).
763
238
764
-
```php
765
-
class FeedMonitorSignal extends Signal
766
-
{
767
-
public function eventTypes(): array
768
-
{
769
-
return ['commit'];
770
-
}
771
-
772
-
public function collections(): ?array
773
-
{
774
-
return ['app.bsky.feed.*'];
775
-
}
776
-
777
-
public function handle(SignalEvent $event): void
778
-
{
779
-
// Handles posts, likes, reposts, etc.
780
-
Log::info('Feed activity', [
781
-
'collection' => $event->getCollection(),
782
-
'operation' => $event->getOperation(),
783
-
'did' => $event->did,
784
-
]);
785
-
}
786
-
}
787
-
```
788
-
789
-
### Track New Follows
790
-
791
-
```php
792
-
class NewFollowSignal extends Signal
793
-
{
794
-
public function eventTypes(): array
795
-
{
796
-
return ['commit'];
797
-
}
798
-
799
-
public function collections(): ?array
800
-
{
801
-
return ['app.bsky.graph.follow'];
802
-
}
803
-
804
-
public function handle(SignalEvent $event): void
805
-
{
806
-
if ($event->commit->isCreate()) {
807
-
$record = $event->getRecord();
808
-
809
-
// Store follow relationship
810
-
Follow::create([
811
-
'follower_did' => $event->did,
812
-
'following_did' => $record->subject,
813
-
]);
814
-
}
815
-
}
816
-
}
817
-
```
818
-
819
-
### Content Moderation
239
+
## Credits
820
240
821
-
```php
822
-
class ModerationSignal extends Signal
823
-
{
824
-
public function eventTypes(): array
825
-
{
826
-
return ['commit'];
827
-
}
828
-
829
-
public function collections(): ?array
830
-
{
831
-
return ['app.bsky.feed.post'];
832
-
}
833
-
834
-
public function shouldQueue(): bool
835
-
{
836
-
return true;
837
-
}
838
-
839
-
public function handle(SignalEvent $event): void
840
-
{
841
-
$record = $event->getRecord();
842
-
843
-
if ($this->containsProhibitedContent($record->text)) {
844
-
$this->flagForModeration($event->did, $record);
845
-
}
846
-
}
847
-
}
848
-
```
849
-
850
-
---
851
-
852
-
## Requirements
853
-
854
-
- PHP 8.2 or higher
855
-
- Laravel 11.0 or higher
856
-
- WebSocket support (enabled by default in most environments)
857
-
858
-
---
241
+
- [Miguel Batres](https://batres.co) - founder & lead maintainer
242
+
- [All contributors](https://github.com/socialdept/atp-signals/graphs/contributors)
859
243
860
244
## License
861
245
862
-
The MIT License (MIT). Please see [LICENSE](LICENSE) for more information.
246
+
Signal is open-source software licensed under the [MIT license](LICENSE).
863
247
864
248
---
865
249
866
-
## Support
867
-
868
-
For issues, questions, or feature requests:
869
-
- Read the [README.md](./README.md) before opening issues
870
-
- Search through existing issues
871
-
- Open new issue
872
-
873
-
---
874
-
875
-
**Built for the AT Protocol ecosystem** โข Made with โค๏ธ by Social Dept
250
+
**Built for the Federation** โข By Social Dept.
+9
-8
composer.json
+9
-8
composer.json
···
1
1
{
2
-
"name": "socialdept/signal",
2
+
"name": "socialdept/atp-signals",
3
3
"description": "Build Reactive Signals for Bluesky's AT Protocol Firehose in Laravel",
4
4
"type": "library",
5
5
"license": "MIT",
6
6
"require": {
7
7
"php": "^8.2",
8
+
"ext-gmp": "*",
8
9
"illuminate/support": "^11.0|^12.0",
9
10
"illuminate/console": "^11.0|^12.0",
10
11
"illuminate/database": "^11.0|^12.0",
11
12
"ratchet/pawl": "^0.4",
12
-
"react/event-loop": "^1.5",
13
-
"revolution/laravel-bluesky": "^1.1"
13
+
"react/event-loop": "^1.5"
14
14
},
15
15
"require-dev": {
16
16
"orchestra/testbench": "^9.0",
17
-
"phpunit/phpunit": "^11.0"
17
+
"phpunit/phpunit": "^11.0",
18
+
"friendsofphp/php-cs-fixer": "^3.89"
18
19
},
19
20
"autoload": {
20
21
"psr-4": {
21
-
"SocialDept\\Signal\\": "src/"
22
+
"SocialDept\\AtpSignals\\": "src/"
22
23
}
23
24
},
24
25
"autoload-dev": {
25
26
"psr-4": {
26
-
"SocialDept\\Signal\\Tests\\": "tests/"
27
+
"SocialDept\\AtpSignals\\Tests\\": "tests/"
27
28
}
28
29
},
29
30
"extra": {
30
31
"laravel": {
31
32
"providers": [
32
-
"SocialDept\\Signal\\SignalServiceProvider"
33
+
"SocialDept\\AtpSignals\\SignalServiceProvider"
33
34
],
34
35
"aliases": {
35
-
"Signal": "SocialDept\\Signal\\Facades\\Signal"
36
+
"Signal": "SocialDept\\AtpSignals\\Facades\\Signal"
36
37
}
37
38
}
38
39
}
-9912
composer.lock
-9912
composer.lock
···
1
-
{
2
-
"_readme": [
3
-
"This file locks the dependencies of your project to a known state",
4
-
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
-
"This file is @generated automatically"
6
-
],
7
-
"content-hash": "b00f2db2fb718f1b2d56431ebb175d75",
8
-
"packages": [
9
-
{
10
-
"name": "brick/math",
11
-
"version": "0.14.0",
12
-
"source": {
13
-
"type": "git",
14
-
"url": "https://github.com/brick/math.git",
15
-
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2"
16
-
},
17
-
"dist": {
18
-
"type": "zip",
19
-
"url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
20
-
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
21
-
"shasum": ""
22
-
},
23
-
"require": {
24
-
"php": "^8.2"
25
-
},
26
-
"require-dev": {
27
-
"php-coveralls/php-coveralls": "^2.2",
28
-
"phpstan/phpstan": "2.1.22",
29
-
"phpunit/phpunit": "^11.5"
30
-
},
31
-
"type": "library",
32
-
"autoload": {
33
-
"psr-4": {
34
-
"Brick\\Math\\": "src/"
35
-
}
36
-
},
37
-
"notification-url": "https://packagist.org/downloads/",
38
-
"license": [
39
-
"MIT"
40
-
],
41
-
"description": "Arbitrary-precision arithmetic library",
42
-
"keywords": [
43
-
"Arbitrary-precision",
44
-
"BigInteger",
45
-
"BigRational",
46
-
"arithmetic",
47
-
"bigdecimal",
48
-
"bignum",
49
-
"bignumber",
50
-
"brick",
51
-
"decimal",
52
-
"integer",
53
-
"math",
54
-
"mathematics",
55
-
"rational"
56
-
],
57
-
"support": {
58
-
"issues": "https://github.com/brick/math/issues",
59
-
"source": "https://github.com/brick/math/tree/0.14.0"
60
-
},
61
-
"funding": [
62
-
{
63
-
"url": "https://github.com/BenMorel",
64
-
"type": "github"
65
-
}
66
-
],
67
-
"time": "2025-08-29T12:40:03+00:00"
68
-
},
69
-
{
70
-
"name": "carbonphp/carbon-doctrine-types",
71
-
"version": "3.2.0",
72
-
"source": {
73
-
"type": "git",
74
-
"url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
75
-
"reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d"
76
-
},
77
-
"dist": {
78
-
"type": "zip",
79
-
"url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
80
-
"reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
81
-
"shasum": ""
82
-
},
83
-
"require": {
84
-
"php": "^8.1"
85
-
},
86
-
"conflict": {
87
-
"doctrine/dbal": "<4.0.0 || >=5.0.0"
88
-
},
89
-
"require-dev": {
90
-
"doctrine/dbal": "^4.0.0",
91
-
"nesbot/carbon": "^2.71.0 || ^3.0.0",
92
-
"phpunit/phpunit": "^10.3"
93
-
},
94
-
"type": "library",
95
-
"autoload": {
96
-
"psr-4": {
97
-
"Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
98
-
}
99
-
},
100
-
"notification-url": "https://packagist.org/downloads/",
101
-
"license": [
102
-
"MIT"
103
-
],
104
-
"authors": [
105
-
{
106
-
"name": "KyleKatarn",
107
-
"email": "kylekatarnls@gmail.com"
108
-
}
109
-
],
110
-
"description": "Types to use Carbon in Doctrine",
111
-
"keywords": [
112
-
"carbon",
113
-
"date",
114
-
"datetime",
115
-
"doctrine",
116
-
"time"
117
-
],
118
-
"support": {
119
-
"issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
120
-
"source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0"
121
-
},
122
-
"funding": [
123
-
{
124
-
"url": "https://github.com/kylekatarnls",
125
-
"type": "github"
126
-
},
127
-
{
128
-
"url": "https://opencollective.com/Carbon",
129
-
"type": "open_collective"
130
-
},
131
-
{
132
-
"url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
133
-
"type": "tidelift"
134
-
}
135
-
],
136
-
"time": "2024-02-09T16:56:22+00:00"
137
-
},
138
-
{
139
-
"name": "dflydev/dot-access-data",
140
-
"version": "v3.0.3",
141
-
"source": {
142
-
"type": "git",
143
-
"url": "https://github.com/dflydev/dflydev-dot-access-data.git",
144
-
"reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f"
145
-
},
146
-
"dist": {
147
-
"type": "zip",
148
-
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f",
149
-
"reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f",
150
-
"shasum": ""
151
-
},
152
-
"require": {
153
-
"php": "^7.1 || ^8.0"
154
-
},
155
-
"require-dev": {
156
-
"phpstan/phpstan": "^0.12.42",
157
-
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
158
-
"scrutinizer/ocular": "1.6.0",
159
-
"squizlabs/php_codesniffer": "^3.5",
160
-
"vimeo/psalm": "^4.0.0"
161
-
},
162
-
"type": "library",
163
-
"extra": {
164
-
"branch-alias": {
165
-
"dev-main": "3.x-dev"
166
-
}
167
-
},
168
-
"autoload": {
169
-
"psr-4": {
170
-
"Dflydev\\DotAccessData\\": "src/"
171
-
}
172
-
},
173
-
"notification-url": "https://packagist.org/downloads/",
174
-
"license": [
175
-
"MIT"
176
-
],
177
-
"authors": [
178
-
{
179
-
"name": "Dragonfly Development Inc.",
180
-
"email": "info@dflydev.com",
181
-
"homepage": "http://dflydev.com"
182
-
},
183
-
{
184
-
"name": "Beau Simensen",
185
-
"email": "beau@dflydev.com",
186
-
"homepage": "http://beausimensen.com"
187
-
},
188
-
{
189
-
"name": "Carlos Frutos",
190
-
"email": "carlos@kiwing.it",
191
-
"homepage": "https://github.com/cfrutos"
192
-
},
193
-
{
194
-
"name": "Colin O'Dell",
195
-
"email": "colinodell@gmail.com",
196
-
"homepage": "https://www.colinodell.com"
197
-
}
198
-
],
199
-
"description": "Given a deep data structure, access data by dot notation.",
200
-
"homepage": "https://github.com/dflydev/dflydev-dot-access-data",
201
-
"keywords": [
202
-
"access",
203
-
"data",
204
-
"dot",
205
-
"notation"
206
-
],
207
-
"support": {
208
-
"issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
209
-
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3"
210
-
},
211
-
"time": "2024-07-08T12:26:09+00:00"
212
-
},
213
-
{
214
-
"name": "doctrine/inflector",
215
-
"version": "2.1.0",
216
-
"source": {
217
-
"type": "git",
218
-
"url": "https://github.com/doctrine/inflector.git",
219
-
"reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b"
220
-
},
221
-
"dist": {
222
-
"type": "zip",
223
-
"url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b",
224
-
"reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b",
225
-
"shasum": ""
226
-
},
227
-
"require": {
228
-
"php": "^7.2 || ^8.0"
229
-
},
230
-
"require-dev": {
231
-
"doctrine/coding-standard": "^12.0 || ^13.0",
232
-
"phpstan/phpstan": "^1.12 || ^2.0",
233
-
"phpstan/phpstan-phpunit": "^1.4 || ^2.0",
234
-
"phpstan/phpstan-strict-rules": "^1.6 || ^2.0",
235
-
"phpunit/phpunit": "^8.5 || ^12.2"
236
-
},
237
-
"type": "library",
238
-
"autoload": {
239
-
"psr-4": {
240
-
"Doctrine\\Inflector\\": "src"
241
-
}
242
-
},
243
-
"notification-url": "https://packagist.org/downloads/",
244
-
"license": [
245
-
"MIT"
246
-
],
247
-
"authors": [
248
-
{
249
-
"name": "Guilherme Blanco",
250
-
"email": "guilhermeblanco@gmail.com"
251
-
},
252
-
{
253
-
"name": "Roman Borschel",
254
-
"email": "roman@code-factory.org"
255
-
},
256
-
{
257
-
"name": "Benjamin Eberlei",
258
-
"email": "kontakt@beberlei.de"
259
-
},
260
-
{
261
-
"name": "Jonathan Wage",
262
-
"email": "jonwage@gmail.com"
263
-
},
264
-
{
265
-
"name": "Johannes Schmitt",
266
-
"email": "schmittjoh@gmail.com"
267
-
}
268
-
],
269
-
"description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
270
-
"homepage": "https://www.doctrine-project.org/projects/inflector.html",
271
-
"keywords": [
272
-
"inflection",
273
-
"inflector",
274
-
"lowercase",
275
-
"manipulation",
276
-
"php",
277
-
"plural",
278
-
"singular",
279
-
"strings",
280
-
"uppercase",
281
-
"words"
282
-
],
283
-
"support": {
284
-
"issues": "https://github.com/doctrine/inflector/issues",
285
-
"source": "https://github.com/doctrine/inflector/tree/2.1.0"
286
-
},
287
-
"funding": [
288
-
{
289
-
"url": "https://www.doctrine-project.org/sponsorship.html",
290
-
"type": "custom"
291
-
},
292
-
{
293
-
"url": "https://www.patreon.com/phpdoctrine",
294
-
"type": "patreon"
295
-
},
296
-
{
297
-
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector",
298
-
"type": "tidelift"
299
-
}
300
-
],
301
-
"time": "2025-08-10T19:31:58+00:00"
302
-
},
303
-
{
304
-
"name": "doctrine/lexer",
305
-
"version": "3.0.1",
306
-
"source": {
307
-
"type": "git",
308
-
"url": "https://github.com/doctrine/lexer.git",
309
-
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd"
310
-
},
311
-
"dist": {
312
-
"type": "zip",
313
-
"url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
314
-
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
315
-
"shasum": ""
316
-
},
317
-
"require": {
318
-
"php": "^8.1"
319
-
},
320
-
"require-dev": {
321
-
"doctrine/coding-standard": "^12",
322
-
"phpstan/phpstan": "^1.10",
323
-
"phpunit/phpunit": "^10.5",
324
-
"psalm/plugin-phpunit": "^0.18.3",
325
-
"vimeo/psalm": "^5.21"
326
-
},
327
-
"type": "library",
328
-
"autoload": {
329
-
"psr-4": {
330
-
"Doctrine\\Common\\Lexer\\": "src"
331
-
}
332
-
},
333
-
"notification-url": "https://packagist.org/downloads/",
334
-
"license": [
335
-
"MIT"
336
-
],
337
-
"authors": [
338
-
{
339
-
"name": "Guilherme Blanco",
340
-
"email": "guilhermeblanco@gmail.com"
341
-
},
342
-
{
343
-
"name": "Roman Borschel",
344
-
"email": "roman@code-factory.org"
345
-
},
346
-
{
347
-
"name": "Johannes Schmitt",
348
-
"email": "schmittjoh@gmail.com"
349
-
}
350
-
],
351
-
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
352
-
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
353
-
"keywords": [
354
-
"annotations",
355
-
"docblock",
356
-
"lexer",
357
-
"parser",
358
-
"php"
359
-
],
360
-
"support": {
361
-
"issues": "https://github.com/doctrine/lexer/issues",
362
-
"source": "https://github.com/doctrine/lexer/tree/3.0.1"
363
-
},
364
-
"funding": [
365
-
{
366
-
"url": "https://www.doctrine-project.org/sponsorship.html",
367
-
"type": "custom"
368
-
},
369
-
{
370
-
"url": "https://www.patreon.com/phpdoctrine",
371
-
"type": "patreon"
372
-
},
373
-
{
374
-
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
375
-
"type": "tidelift"
376
-
}
377
-
],
378
-
"time": "2024-02-05T11:56:58+00:00"
379
-
},
380
-
{
381
-
"name": "dragonmantank/cron-expression",
382
-
"version": "v3.4.0",
383
-
"source": {
384
-
"type": "git",
385
-
"url": "https://github.com/dragonmantank/cron-expression.git",
386
-
"reference": "8c784d071debd117328803d86b2097615b457500"
387
-
},
388
-
"dist": {
389
-
"type": "zip",
390
-
"url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500",
391
-
"reference": "8c784d071debd117328803d86b2097615b457500",
392
-
"shasum": ""
393
-
},
394
-
"require": {
395
-
"php": "^7.2|^8.0",
396
-
"webmozart/assert": "^1.0"
397
-
},
398
-
"replace": {
399
-
"mtdowling/cron-expression": "^1.0"
400
-
},
401
-
"require-dev": {
402
-
"phpstan/extension-installer": "^1.0",
403
-
"phpstan/phpstan": "^1.0",
404
-
"phpunit/phpunit": "^7.0|^8.0|^9.0"
405
-
},
406
-
"type": "library",
407
-
"extra": {
408
-
"branch-alias": {
409
-
"dev-master": "3.x-dev"
410
-
}
411
-
},
412
-
"autoload": {
413
-
"psr-4": {
414
-
"Cron\\": "src/Cron/"
415
-
}
416
-
},
417
-
"notification-url": "https://packagist.org/downloads/",
418
-
"license": [
419
-
"MIT"
420
-
],
421
-
"authors": [
422
-
{
423
-
"name": "Chris Tankersley",
424
-
"email": "chris@ctankersley.com",
425
-
"homepage": "https://github.com/dragonmantank"
426
-
}
427
-
],
428
-
"description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
429
-
"keywords": [
430
-
"cron",
431
-
"schedule"
432
-
],
433
-
"support": {
434
-
"issues": "https://github.com/dragonmantank/cron-expression/issues",
435
-
"source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0"
436
-
},
437
-
"funding": [
438
-
{
439
-
"url": "https://github.com/dragonmantank",
440
-
"type": "github"
441
-
}
442
-
],
443
-
"time": "2024-10-09T13:47:03+00:00"
444
-
},
445
-
{
446
-
"name": "egulias/email-validator",
447
-
"version": "4.0.4",
448
-
"source": {
449
-
"type": "git",
450
-
"url": "https://github.com/egulias/EmailValidator.git",
451
-
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa"
452
-
},
453
-
"dist": {
454
-
"type": "zip",
455
-
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
456
-
"reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa",
457
-
"shasum": ""
458
-
},
459
-
"require": {
460
-
"doctrine/lexer": "^2.0 || ^3.0",
461
-
"php": ">=8.1",
462
-
"symfony/polyfill-intl-idn": "^1.26"
463
-
},
464
-
"require-dev": {
465
-
"phpunit/phpunit": "^10.2",
466
-
"vimeo/psalm": "^5.12"
467
-
},
468
-
"suggest": {
469
-
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
470
-
},
471
-
"type": "library",
472
-
"extra": {
473
-
"branch-alias": {
474
-
"dev-master": "4.0.x-dev"
475
-
}
476
-
},
477
-
"autoload": {
478
-
"psr-4": {
479
-
"Egulias\\EmailValidator\\": "src"
480
-
}
481
-
},
482
-
"notification-url": "https://packagist.org/downloads/",
483
-
"license": [
484
-
"MIT"
485
-
],
486
-
"authors": [
487
-
{
488
-
"name": "Eduardo Gulias Davis"
489
-
}
490
-
],
491
-
"description": "A library for validating emails against several RFCs",
492
-
"homepage": "https://github.com/egulias/EmailValidator",
493
-
"keywords": [
494
-
"email",
495
-
"emailvalidation",
496
-
"emailvalidator",
497
-
"validation",
498
-
"validator"
499
-
],
500
-
"support": {
501
-
"issues": "https://github.com/egulias/EmailValidator/issues",
502
-
"source": "https://github.com/egulias/EmailValidator/tree/4.0.4"
503
-
},
504
-
"funding": [
505
-
{
506
-
"url": "https://github.com/egulias",
507
-
"type": "github"
508
-
}
509
-
],
510
-
"time": "2025-03-06T22:45:56+00:00"
511
-
},
512
-
{
513
-
"name": "evenement/evenement",
514
-
"version": "v3.0.2",
515
-
"source": {
516
-
"type": "git",
517
-
"url": "https://github.com/igorw/evenement.git",
518
-
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
519
-
},
520
-
"dist": {
521
-
"type": "zip",
522
-
"url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
523
-
"reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
524
-
"shasum": ""
525
-
},
526
-
"require": {
527
-
"php": ">=7.0"
528
-
},
529
-
"require-dev": {
530
-
"phpunit/phpunit": "^9 || ^6"
531
-
},
532
-
"type": "library",
533
-
"autoload": {
534
-
"psr-4": {
535
-
"Evenement\\": "src/"
536
-
}
537
-
},
538
-
"notification-url": "https://packagist.org/downloads/",
539
-
"license": [
540
-
"MIT"
541
-
],
542
-
"authors": [
543
-
{
544
-
"name": "Igor Wiedler",
545
-
"email": "igor@wiedler.ch"
546
-
}
547
-
],
548
-
"description": "รvรฉnement is a very simple event dispatching library for PHP",
549
-
"keywords": [
550
-
"event-dispatcher",
551
-
"event-emitter"
552
-
],
553
-
"support": {
554
-
"issues": "https://github.com/igorw/evenement/issues",
555
-
"source": "https://github.com/igorw/evenement/tree/v3.0.2"
556
-
},
557
-
"time": "2023-08-08T05:53:35+00:00"
558
-
},
559
-
{
560
-
"name": "firebase/php-jwt",
561
-
"version": "v6.11.1",
562
-
"source": {
563
-
"type": "git",
564
-
"url": "https://github.com/firebase/php-jwt.git",
565
-
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
566
-
},
567
-
"dist": {
568
-
"type": "zip",
569
-
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
570
-
"reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
571
-
"shasum": ""
572
-
},
573
-
"require": {
574
-
"php": "^8.0"
575
-
},
576
-
"require-dev": {
577
-
"guzzlehttp/guzzle": "^7.4",
578
-
"phpspec/prophecy-phpunit": "^2.0",
579
-
"phpunit/phpunit": "^9.5",
580
-
"psr/cache": "^2.0||^3.0",
581
-
"psr/http-client": "^1.0",
582
-
"psr/http-factory": "^1.0"
583
-
},
584
-
"suggest": {
585
-
"ext-sodium": "Support EdDSA (Ed25519) signatures",
586
-
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
587
-
},
588
-
"type": "library",
589
-
"autoload": {
590
-
"psr-4": {
591
-
"Firebase\\JWT\\": "src"
592
-
}
593
-
},
594
-
"notification-url": "https://packagist.org/downloads/",
595
-
"license": [
596
-
"BSD-3-Clause"
597
-
],
598
-
"authors": [
599
-
{
600
-
"name": "Neuman Vong",
601
-
"email": "neuman+pear@twilio.com",
602
-
"role": "Developer"
603
-
},
604
-
{
605
-
"name": "Anant Narayanan",
606
-
"email": "anant@php.net",
607
-
"role": "Developer"
608
-
}
609
-
],
610
-
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
611
-
"homepage": "https://github.com/firebase/php-jwt",
612
-
"keywords": [
613
-
"jwt",
614
-
"php"
615
-
],
616
-
"support": {
617
-
"issues": "https://github.com/firebase/php-jwt/issues",
618
-
"source": "https://github.com/firebase/php-jwt/tree/v6.11.1"
619
-
},
620
-
"time": "2025-04-09T20:32:01+00:00"
621
-
},
622
-
{
623
-
"name": "fruitcake/php-cors",
624
-
"version": "v1.3.0",
625
-
"source": {
626
-
"type": "git",
627
-
"url": "https://github.com/fruitcake/php-cors.git",
628
-
"reference": "3d158f36e7875e2f040f37bc0573956240a5a38b"
629
-
},
630
-
"dist": {
631
-
"type": "zip",
632
-
"url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b",
633
-
"reference": "3d158f36e7875e2f040f37bc0573956240a5a38b",
634
-
"shasum": ""
635
-
},
636
-
"require": {
637
-
"php": "^7.4|^8.0",
638
-
"symfony/http-foundation": "^4.4|^5.4|^6|^7"
639
-
},
640
-
"require-dev": {
641
-
"phpstan/phpstan": "^1.4",
642
-
"phpunit/phpunit": "^9",
643
-
"squizlabs/php_codesniffer": "^3.5"
644
-
},
645
-
"type": "library",
646
-
"extra": {
647
-
"branch-alias": {
648
-
"dev-master": "1.2-dev"
649
-
}
650
-
},
651
-
"autoload": {
652
-
"psr-4": {
653
-
"Fruitcake\\Cors\\": "src/"
654
-
}
655
-
},
656
-
"notification-url": "https://packagist.org/downloads/",
657
-
"license": [
658
-
"MIT"
659
-
],
660
-
"authors": [
661
-
{
662
-
"name": "Fruitcake",
663
-
"homepage": "https://fruitcake.nl"
664
-
},
665
-
{
666
-
"name": "Barryvdh",
667
-
"email": "barryvdh@gmail.com"
668
-
}
669
-
],
670
-
"description": "Cross-origin resource sharing library for the Symfony HttpFoundation",
671
-
"homepage": "https://github.com/fruitcake/php-cors",
672
-
"keywords": [
673
-
"cors",
674
-
"laravel",
675
-
"symfony"
676
-
],
677
-
"support": {
678
-
"issues": "https://github.com/fruitcake/php-cors/issues",
679
-
"source": "https://github.com/fruitcake/php-cors/tree/v1.3.0"
680
-
},
681
-
"funding": [
682
-
{
683
-
"url": "https://fruitcake.nl",
684
-
"type": "custom"
685
-
},
686
-
{
687
-
"url": "https://github.com/barryvdh",
688
-
"type": "github"
689
-
}
690
-
],
691
-
"time": "2023-10-12T05:21:21+00:00"
692
-
},
693
-
{
694
-
"name": "graham-campbell/result-type",
695
-
"version": "v1.1.3",
696
-
"source": {
697
-
"type": "git",
698
-
"url": "https://github.com/GrahamCampbell/Result-Type.git",
699
-
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
700
-
},
701
-
"dist": {
702
-
"type": "zip",
703
-
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
704
-
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
705
-
"shasum": ""
706
-
},
707
-
"require": {
708
-
"php": "^7.2.5 || ^8.0",
709
-
"phpoption/phpoption": "^1.9.3"
710
-
},
711
-
"require-dev": {
712
-
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
713
-
},
714
-
"type": "library",
715
-
"autoload": {
716
-
"psr-4": {
717
-
"GrahamCampbell\\ResultType\\": "src/"
718
-
}
719
-
},
720
-
"notification-url": "https://packagist.org/downloads/",
721
-
"license": [
722
-
"MIT"
723
-
],
724
-
"authors": [
725
-
{
726
-
"name": "Graham Campbell",
727
-
"email": "hello@gjcampbell.co.uk",
728
-
"homepage": "https://github.com/GrahamCampbell"
729
-
}
730
-
],
731
-
"description": "An Implementation Of The Result Type",
732
-
"keywords": [
733
-
"Graham Campbell",
734
-
"GrahamCampbell",
735
-
"Result Type",
736
-
"Result-Type",
737
-
"result"
738
-
],
739
-
"support": {
740
-
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
741
-
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
742
-
},
743
-
"funding": [
744
-
{
745
-
"url": "https://github.com/GrahamCampbell",
746
-
"type": "github"
747
-
},
748
-
{
749
-
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
750
-
"type": "tidelift"
751
-
}
752
-
],
753
-
"time": "2024-07-20T21:45:45+00:00"
754
-
},
755
-
{
756
-
"name": "guzzlehttp/guzzle",
757
-
"version": "7.10.0",
758
-
"source": {
759
-
"type": "git",
760
-
"url": "https://github.com/guzzle/guzzle.git",
761
-
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4"
762
-
},
763
-
"dist": {
764
-
"type": "zip",
765
-
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
766
-
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
767
-
"shasum": ""
768
-
},
769
-
"require": {
770
-
"ext-json": "*",
771
-
"guzzlehttp/promises": "^2.3",
772
-
"guzzlehttp/psr7": "^2.8",
773
-
"php": "^7.2.5 || ^8.0",
774
-
"psr/http-client": "^1.0",
775
-
"symfony/deprecation-contracts": "^2.2 || ^3.0"
776
-
},
777
-
"provide": {
778
-
"psr/http-client-implementation": "1.0"
779
-
},
780
-
"require-dev": {
781
-
"bamarni/composer-bin-plugin": "^1.8.2",
782
-
"ext-curl": "*",
783
-
"guzzle/client-integration-tests": "3.0.2",
784
-
"php-http/message-factory": "^1.1",
785
-
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
786
-
"psr/log": "^1.1 || ^2.0 || ^3.0"
787
-
},
788
-
"suggest": {
789
-
"ext-curl": "Required for CURL handler support",
790
-
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
791
-
"psr/log": "Required for using the Log middleware"
792
-
},
793
-
"type": "library",
794
-
"extra": {
795
-
"bamarni-bin": {
796
-
"bin-links": true,
797
-
"forward-command": false
798
-
}
799
-
},
800
-
"autoload": {
801
-
"files": [
802
-
"src/functions_include.php"
803
-
],
804
-
"psr-4": {
805
-
"GuzzleHttp\\": "src/"
806
-
}
807
-
},
808
-
"notification-url": "https://packagist.org/downloads/",
809
-
"license": [
810
-
"MIT"
811
-
],
812
-
"authors": [
813
-
{
814
-
"name": "Graham Campbell",
815
-
"email": "hello@gjcampbell.co.uk",
816
-
"homepage": "https://github.com/GrahamCampbell"
817
-
},
818
-
{
819
-
"name": "Michael Dowling",
820
-
"email": "mtdowling@gmail.com",
821
-
"homepage": "https://github.com/mtdowling"
822
-
},
823
-
{
824
-
"name": "Jeremy Lindblom",
825
-
"email": "jeremeamia@gmail.com",
826
-
"homepage": "https://github.com/jeremeamia"
827
-
},
828
-
{
829
-
"name": "George Mponos",
830
-
"email": "gmponos@gmail.com",
831
-
"homepage": "https://github.com/gmponos"
832
-
},
833
-
{
834
-
"name": "Tobias Nyholm",
835
-
"email": "tobias.nyholm@gmail.com",
836
-
"homepage": "https://github.com/Nyholm"
837
-
},
838
-
{
839
-
"name": "Mรกrk Sรกgi-Kazรกr",
840
-
"email": "mark.sagikazar@gmail.com",
841
-
"homepage": "https://github.com/sagikazarmark"
842
-
},
843
-
{
844
-
"name": "Tobias Schultze",
845
-
"email": "webmaster@tubo-world.de",
846
-
"homepage": "https://github.com/Tobion"
847
-
}
848
-
],
849
-
"description": "Guzzle is a PHP HTTP client library",
850
-
"keywords": [
851
-
"client",
852
-
"curl",
853
-
"framework",
854
-
"http",
855
-
"http client",
856
-
"psr-18",
857
-
"psr-7",
858
-
"rest",
859
-
"web service"
860
-
],
861
-
"support": {
862
-
"issues": "https://github.com/guzzle/guzzle/issues",
863
-
"source": "https://github.com/guzzle/guzzle/tree/7.10.0"
864
-
},
865
-
"funding": [
866
-
{
867
-
"url": "https://github.com/GrahamCampbell",
868
-
"type": "github"
869
-
},
870
-
{
871
-
"url": "https://github.com/Nyholm",
872
-
"type": "github"
873
-
},
874
-
{
875
-
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
876
-
"type": "tidelift"
877
-
}
878
-
],
879
-
"time": "2025-08-23T22:36:01+00:00"
880
-
},
881
-
{
882
-
"name": "guzzlehttp/promises",
883
-
"version": "2.3.0",
884
-
"source": {
885
-
"type": "git",
886
-
"url": "https://github.com/guzzle/promises.git",
887
-
"reference": "481557b130ef3790cf82b713667b43030dc9c957"
888
-
},
889
-
"dist": {
890
-
"type": "zip",
891
-
"url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957",
892
-
"reference": "481557b130ef3790cf82b713667b43030dc9c957",
893
-
"shasum": ""
894
-
},
895
-
"require": {
896
-
"php": "^7.2.5 || ^8.0"
897
-
},
898
-
"require-dev": {
899
-
"bamarni/composer-bin-plugin": "^1.8.2",
900
-
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
901
-
},
902
-
"type": "library",
903
-
"extra": {
904
-
"bamarni-bin": {
905
-
"bin-links": true,
906
-
"forward-command": false
907
-
}
908
-
},
909
-
"autoload": {
910
-
"psr-4": {
911
-
"GuzzleHttp\\Promise\\": "src/"
912
-
}
913
-
},
914
-
"notification-url": "https://packagist.org/downloads/",
915
-
"license": [
916
-
"MIT"
917
-
],
918
-
"authors": [
919
-
{
920
-
"name": "Graham Campbell",
921
-
"email": "hello@gjcampbell.co.uk",
922
-
"homepage": "https://github.com/GrahamCampbell"
923
-
},
924
-
{
925
-
"name": "Michael Dowling",
926
-
"email": "mtdowling@gmail.com",
927
-
"homepage": "https://github.com/mtdowling"
928
-
},
929
-
{
930
-
"name": "Tobias Nyholm",
931
-
"email": "tobias.nyholm@gmail.com",
932
-
"homepage": "https://github.com/Nyholm"
933
-
},
934
-
{
935
-
"name": "Tobias Schultze",
936
-
"email": "webmaster@tubo-world.de",
937
-
"homepage": "https://github.com/Tobion"
938
-
}
939
-
],
940
-
"description": "Guzzle promises library",
941
-
"keywords": [
942
-
"promise"
943
-
],
944
-
"support": {
945
-
"issues": "https://github.com/guzzle/promises/issues",
946
-
"source": "https://github.com/guzzle/promises/tree/2.3.0"
947
-
},
948
-
"funding": [
949
-
{
950
-
"url": "https://github.com/GrahamCampbell",
951
-
"type": "github"
952
-
},
953
-
{
954
-
"url": "https://github.com/Nyholm",
955
-
"type": "github"
956
-
},
957
-
{
958
-
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
959
-
"type": "tidelift"
960
-
}
961
-
],
962
-
"time": "2025-08-22T14:34:08+00:00"
963
-
},
964
-
{
965
-
"name": "guzzlehttp/psr7",
966
-
"version": "2.8.0",
967
-
"source": {
968
-
"type": "git",
969
-
"url": "https://github.com/guzzle/psr7.git",
970
-
"reference": "21dc724a0583619cd1652f673303492272778051"
971
-
},
972
-
"dist": {
973
-
"type": "zip",
974
-
"url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
975
-
"reference": "21dc724a0583619cd1652f673303492272778051",
976
-
"shasum": ""
977
-
},
978
-
"require": {
979
-
"php": "^7.2.5 || ^8.0",
980
-
"psr/http-factory": "^1.0",
981
-
"psr/http-message": "^1.1 || ^2.0",
982
-
"ralouphie/getallheaders": "^3.0"
983
-
},
984
-
"provide": {
985
-
"psr/http-factory-implementation": "1.0",
986
-
"psr/http-message-implementation": "1.0"
987
-
},
988
-
"require-dev": {
989
-
"bamarni/composer-bin-plugin": "^1.8.2",
990
-
"http-interop/http-factory-tests": "0.9.0",
991
-
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
992
-
},
993
-
"suggest": {
994
-
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
995
-
},
996
-
"type": "library",
997
-
"extra": {
998
-
"bamarni-bin": {
999
-
"bin-links": true,
1000
-
"forward-command": false
1001
-
}
1002
-
},
1003
-
"autoload": {
1004
-
"psr-4": {
1005
-
"GuzzleHttp\\Psr7\\": "src/"
1006
-
}
1007
-
},
1008
-
"notification-url": "https://packagist.org/downloads/",
1009
-
"license": [
1010
-
"MIT"
1011
-
],
1012
-
"authors": [
1013
-
{
1014
-
"name": "Graham Campbell",
1015
-
"email": "hello@gjcampbell.co.uk",
1016
-
"homepage": "https://github.com/GrahamCampbell"
1017
-
},
1018
-
{
1019
-
"name": "Michael Dowling",
1020
-
"email": "mtdowling@gmail.com",
1021
-
"homepage": "https://github.com/mtdowling"
1022
-
},
1023
-
{
1024
-
"name": "George Mponos",
1025
-
"email": "gmponos@gmail.com",
1026
-
"homepage": "https://github.com/gmponos"
1027
-
},
1028
-
{
1029
-
"name": "Tobias Nyholm",
1030
-
"email": "tobias.nyholm@gmail.com",
1031
-
"homepage": "https://github.com/Nyholm"
1032
-
},
1033
-
{
1034
-
"name": "Mรกrk Sรกgi-Kazรกr",
1035
-
"email": "mark.sagikazar@gmail.com",
1036
-
"homepage": "https://github.com/sagikazarmark"
1037
-
},
1038
-
{
1039
-
"name": "Tobias Schultze",
1040
-
"email": "webmaster@tubo-world.de",
1041
-
"homepage": "https://github.com/Tobion"
1042
-
},
1043
-
{
1044
-
"name": "Mรกrk Sรกgi-Kazรกr",
1045
-
"email": "mark.sagikazar@gmail.com",
1046
-
"homepage": "https://sagikazarmark.hu"
1047
-
}
1048
-
],
1049
-
"description": "PSR-7 message implementation that also provides common utility methods",
1050
-
"keywords": [
1051
-
"http",
1052
-
"message",
1053
-
"psr-7",
1054
-
"request",
1055
-
"response",
1056
-
"stream",
1057
-
"uri",
1058
-
"url"
1059
-
],
1060
-
"support": {
1061
-
"issues": "https://github.com/guzzle/psr7/issues",
1062
-
"source": "https://github.com/guzzle/psr7/tree/2.8.0"
1063
-
},
1064
-
"funding": [
1065
-
{
1066
-
"url": "https://github.com/GrahamCampbell",
1067
-
"type": "github"
1068
-
},
1069
-
{
1070
-
"url": "https://github.com/Nyholm",
1071
-
"type": "github"
1072
-
},
1073
-
{
1074
-
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
1075
-
"type": "tidelift"
1076
-
}
1077
-
],
1078
-
"time": "2025-08-23T21:21:41+00:00"
1079
-
},
1080
-
{
1081
-
"name": "guzzlehttp/uri-template",
1082
-
"version": "v1.0.5",
1083
-
"source": {
1084
-
"type": "git",
1085
-
"url": "https://github.com/guzzle/uri-template.git",
1086
-
"reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1"
1087
-
},
1088
-
"dist": {
1089
-
"type": "zip",
1090
-
"url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1",
1091
-
"reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1",
1092
-
"shasum": ""
1093
-
},
1094
-
"require": {
1095
-
"php": "^7.2.5 || ^8.0",
1096
-
"symfony/polyfill-php80": "^1.24"
1097
-
},
1098
-
"require-dev": {
1099
-
"bamarni/composer-bin-plugin": "^1.8.2",
1100
-
"phpunit/phpunit": "^8.5.44 || ^9.6.25",
1101
-
"uri-template/tests": "1.0.0"
1102
-
},
1103
-
"type": "library",
1104
-
"extra": {
1105
-
"bamarni-bin": {
1106
-
"bin-links": true,
1107
-
"forward-command": false
1108
-
}
1109
-
},
1110
-
"autoload": {
1111
-
"psr-4": {
1112
-
"GuzzleHttp\\UriTemplate\\": "src"
1113
-
}
1114
-
},
1115
-
"notification-url": "https://packagist.org/downloads/",
1116
-
"license": [
1117
-
"MIT"
1118
-
],
1119
-
"authors": [
1120
-
{
1121
-
"name": "Graham Campbell",
1122
-
"email": "hello@gjcampbell.co.uk",
1123
-
"homepage": "https://github.com/GrahamCampbell"
1124
-
},
1125
-
{
1126
-
"name": "Michael Dowling",
1127
-
"email": "mtdowling@gmail.com",
1128
-
"homepage": "https://github.com/mtdowling"
1129
-
},
1130
-
{
1131
-
"name": "George Mponos",
1132
-
"email": "gmponos@gmail.com",
1133
-
"homepage": "https://github.com/gmponos"
1134
-
},
1135
-
{
1136
-
"name": "Tobias Nyholm",
1137
-
"email": "tobias.nyholm@gmail.com",
1138
-
"homepage": "https://github.com/Nyholm"
1139
-
}
1140
-
],
1141
-
"description": "A polyfill class for uri_template of PHP",
1142
-
"keywords": [
1143
-
"guzzlehttp",
1144
-
"uri-template"
1145
-
],
1146
-
"support": {
1147
-
"issues": "https://github.com/guzzle/uri-template/issues",
1148
-
"source": "https://github.com/guzzle/uri-template/tree/v1.0.5"
1149
-
},
1150
-
"funding": [
1151
-
{
1152
-
"url": "https://github.com/GrahamCampbell",
1153
-
"type": "github"
1154
-
},
1155
-
{
1156
-
"url": "https://github.com/Nyholm",
1157
-
"type": "github"
1158
-
},
1159
-
{
1160
-
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template",
1161
-
"type": "tidelift"
1162
-
}
1163
-
],
1164
-
"time": "2025-08-22T14:27:06+00:00"
1165
-
},
1166
-
{
1167
-
"name": "laravel/framework",
1168
-
"version": "v11.46.1",
1169
-
"source": {
1170
-
"type": "git",
1171
-
"url": "https://github.com/laravel/framework.git",
1172
-
"reference": "5fd457f807570a962a53b403b1346efe4cc80bb8"
1173
-
},
1174
-
"dist": {
1175
-
"type": "zip",
1176
-
"url": "https://api.github.com/repos/laravel/framework/zipball/5fd457f807570a962a53b403b1346efe4cc80bb8",
1177
-
"reference": "5fd457f807570a962a53b403b1346efe4cc80bb8",
1178
-
"shasum": ""
1179
-
},
1180
-
"require": {
1181
-
"brick/math": "^0.9.3|^0.10.2|^0.11|^0.12|^0.13|^0.14",
1182
-
"composer-runtime-api": "^2.2",
1183
-
"doctrine/inflector": "^2.0.5",
1184
-
"dragonmantank/cron-expression": "^3.4",
1185
-
"egulias/email-validator": "^3.2.1|^4.0",
1186
-
"ext-ctype": "*",
1187
-
"ext-filter": "*",
1188
-
"ext-hash": "*",
1189
-
"ext-mbstring": "*",
1190
-
"ext-openssl": "*",
1191
-
"ext-session": "*",
1192
-
"ext-tokenizer": "*",
1193
-
"fruitcake/php-cors": "^1.3",
1194
-
"guzzlehttp/guzzle": "^7.8.2",
1195
-
"guzzlehttp/uri-template": "^1.0",
1196
-
"laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
1197
-
"laravel/serializable-closure": "^1.3|^2.0",
1198
-
"league/commonmark": "^2.7",
1199
-
"league/flysystem": "^3.25.1",
1200
-
"league/flysystem-local": "^3.25.1",
1201
-
"league/uri": "^7.5.1",
1202
-
"monolog/monolog": "^3.0",
1203
-
"nesbot/carbon": "^2.72.6|^3.8.4",
1204
-
"nunomaduro/termwind": "^2.0",
1205
-
"php": "^8.2",
1206
-
"psr/container": "^1.1.1|^2.0.1",
1207
-
"psr/log": "^1.0|^2.0|^3.0",
1208
-
"psr/simple-cache": "^1.0|^2.0|^3.0",
1209
-
"ramsey/uuid": "^4.7",
1210
-
"symfony/console": "^7.0.3",
1211
-
"symfony/error-handler": "^7.0.3",
1212
-
"symfony/finder": "^7.0.3",
1213
-
"symfony/http-foundation": "^7.2.0",
1214
-
"symfony/http-kernel": "^7.0.3",
1215
-
"symfony/mailer": "^7.0.3",
1216
-
"symfony/mime": "^7.0.3",
1217
-
"symfony/polyfill-php83": "^1.31",
1218
-
"symfony/process": "^7.0.3",
1219
-
"symfony/routing": "^7.0.3",
1220
-
"symfony/uid": "^7.0.3",
1221
-
"symfony/var-dumper": "^7.0.3",
1222
-
"tijsverkoyen/css-to-inline-styles": "^2.2.5",
1223
-
"vlucas/phpdotenv": "^5.6.1",
1224
-
"voku/portable-ascii": "^2.0.2"
1225
-
},
1226
-
"conflict": {
1227
-
"tightenco/collect": "<5.5.33"
1228
-
},
1229
-
"provide": {
1230
-
"psr/container-implementation": "1.1|2.0",
1231
-
"psr/log-implementation": "1.0|2.0|3.0",
1232
-
"psr/simple-cache-implementation": "1.0|2.0|3.0"
1233
-
},
1234
-
"replace": {
1235
-
"illuminate/auth": "self.version",
1236
-
"illuminate/broadcasting": "self.version",
1237
-
"illuminate/bus": "self.version",
1238
-
"illuminate/cache": "self.version",
1239
-
"illuminate/collections": "self.version",
1240
-
"illuminate/concurrency": "self.version",
1241
-
"illuminate/conditionable": "self.version",
1242
-
"illuminate/config": "self.version",
1243
-
"illuminate/console": "self.version",
1244
-
"illuminate/container": "self.version",
1245
-
"illuminate/contracts": "self.version",
1246
-
"illuminate/cookie": "self.version",
1247
-
"illuminate/database": "self.version",
1248
-
"illuminate/encryption": "self.version",
1249
-
"illuminate/events": "self.version",
1250
-
"illuminate/filesystem": "self.version",
1251
-
"illuminate/hashing": "self.version",
1252
-
"illuminate/http": "self.version",
1253
-
"illuminate/log": "self.version",
1254
-
"illuminate/macroable": "self.version",
1255
-
"illuminate/mail": "self.version",
1256
-
"illuminate/notifications": "self.version",
1257
-
"illuminate/pagination": "self.version",
1258
-
"illuminate/pipeline": "self.version",
1259
-
"illuminate/process": "self.version",
1260
-
"illuminate/queue": "self.version",
1261
-
"illuminate/redis": "self.version",
1262
-
"illuminate/routing": "self.version",
1263
-
"illuminate/session": "self.version",
1264
-
"illuminate/support": "self.version",
1265
-
"illuminate/testing": "self.version",
1266
-
"illuminate/translation": "self.version",
1267
-
"illuminate/validation": "self.version",
1268
-
"illuminate/view": "self.version",
1269
-
"spatie/once": "*"
1270
-
},
1271
-
"require-dev": {
1272
-
"ably/ably-php": "^1.0",
1273
-
"aws/aws-sdk-php": "^3.322.9",
1274
-
"ext-gmp": "*",
1275
-
"fakerphp/faker": "^1.24",
1276
-
"guzzlehttp/promises": "^2.0.3",
1277
-
"guzzlehttp/psr7": "^2.4",
1278
-
"laravel/pint": "^1.18",
1279
-
"league/flysystem-aws-s3-v3": "^3.25.1",
1280
-
"league/flysystem-ftp": "^3.25.1",
1281
-
"league/flysystem-path-prefixing": "^3.25.1",
1282
-
"league/flysystem-read-only": "^3.25.1",
1283
-
"league/flysystem-sftp-v3": "^3.25.1",
1284
-
"mockery/mockery": "^1.6.10",
1285
-
"orchestra/testbench-core": "^9.16.1",
1286
-
"pda/pheanstalk": "^5.0.6",
1287
-
"php-http/discovery": "^1.15",
1288
-
"phpstan/phpstan": "^2.0",
1289
-
"phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1",
1290
-
"predis/predis": "^2.3",
1291
-
"resend/resend-php": "^0.10.0",
1292
-
"symfony/cache": "^7.0.3",
1293
-
"symfony/http-client": "^7.0.3",
1294
-
"symfony/psr-http-message-bridge": "^7.0.3",
1295
-
"symfony/translation": "^7.0.3"
1296
-
},
1297
-
"suggest": {
1298
-
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
1299
-
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).",
1300
-
"brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).",
1301
-
"ext-apcu": "Required to use the APC cache driver.",
1302
-
"ext-fileinfo": "Required to use the Filesystem class.",
1303
-
"ext-ftp": "Required to use the Flysystem FTP driver.",
1304
-
"ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().",
1305
-
"ext-memcached": "Required to use the memcache cache driver.",
1306
-
"ext-pcntl": "Required to use all features of the queue worker and console signal trapping.",
1307
-
"ext-pdo": "Required to use all database features.",
1308
-
"ext-posix": "Required to use all features of the queue worker.",
1309
-
"ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).",
1310
-
"fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).",
1311
-
"filp/whoops": "Required for friendly error pages in development (^2.14.3).",
1312
-
"laravel/tinker": "Required to use the tinker console command (^2.0).",
1313
-
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).",
1314
-
"league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).",
1315
-
"league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).",
1316
-
"league/flysystem-read-only": "Required to use read-only disks (^3.25.1)",
1317
-
"league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).",
1318
-
"mockery/mockery": "Required to use mocking (^1.6).",
1319
-
"pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).",
1320
-
"php-http/discovery": "Required to use PSR-7 bridging features (^1.15).",
1321
-
"phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1).",
1322
-
"predis/predis": "Required to use the predis connector (^2.3).",
1323
-
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
1324
-
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",
1325
-
"resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).",
1326
-
"symfony/cache": "Required to PSR-6 cache bridge (^7.0).",
1327
-
"symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).",
1328
-
"symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).",
1329
-
"symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).",
1330
-
"symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).",
1331
-
"symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)."
1332
-
},
1333
-
"type": "library",
1334
-
"extra": {
1335
-
"branch-alias": {
1336
-
"dev-master": "11.x-dev"
1337
-
}
1338
-
},
1339
-
"autoload": {
1340
-
"files": [
1341
-
"src/Illuminate/Collections/functions.php",
1342
-
"src/Illuminate/Collections/helpers.php",
1343
-
"src/Illuminate/Events/functions.php",
1344
-
"src/Illuminate/Filesystem/functions.php",
1345
-
"src/Illuminate/Foundation/helpers.php",
1346
-
"src/Illuminate/Log/functions.php",
1347
-
"src/Illuminate/Support/functions.php",
1348
-
"src/Illuminate/Support/helpers.php"
1349
-
],
1350
-
"psr-4": {
1351
-
"Illuminate\\": "src/Illuminate/",
1352
-
"Illuminate\\Support\\": [
1353
-
"src/Illuminate/Macroable/",
1354
-
"src/Illuminate/Collections/",
1355
-
"src/Illuminate/Conditionable/"
1356
-
]
1357
-
}
1358
-
},
1359
-
"notification-url": "https://packagist.org/downloads/",
1360
-
"license": [
1361
-
"MIT"
1362
-
],
1363
-
"authors": [
1364
-
{
1365
-
"name": "Taylor Otwell",
1366
-
"email": "taylor@laravel.com"
1367
-
}
1368
-
],
1369
-
"description": "The Laravel Framework.",
1370
-
"homepage": "https://laravel.com",
1371
-
"keywords": [
1372
-
"framework",
1373
-
"laravel"
1374
-
],
1375
-
"support": {
1376
-
"issues": "https://github.com/laravel/framework/issues",
1377
-
"source": "https://github.com/laravel/framework"
1378
-
},
1379
-
"time": "2025-09-30T14:51:32+00:00"
1380
-
},
1381
-
{
1382
-
"name": "laravel/prompts",
1383
-
"version": "v0.3.7",
1384
-
"source": {
1385
-
"type": "git",
1386
-
"url": "https://github.com/laravel/prompts.git",
1387
-
"reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc"
1388
-
},
1389
-
"dist": {
1390
-
"type": "zip",
1391
-
"url": "https://api.github.com/repos/laravel/prompts/zipball/a1891d362714bc40c8d23b0b1d7090f022ea27cc",
1392
-
"reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc",
1393
-
"shasum": ""
1394
-
},
1395
-
"require": {
1396
-
"composer-runtime-api": "^2.2",
1397
-
"ext-mbstring": "*",
1398
-
"php": "^8.1",
1399
-
"symfony/console": "^6.2|^7.0"
1400
-
},
1401
-
"conflict": {
1402
-
"illuminate/console": ">=10.17.0 <10.25.0",
1403
-
"laravel/framework": ">=10.17.0 <10.25.0"
1404
-
},
1405
-
"require-dev": {
1406
-
"illuminate/collections": "^10.0|^11.0|^12.0",
1407
-
"mockery/mockery": "^1.5",
1408
-
"pestphp/pest": "^2.3|^3.4",
1409
-
"phpstan/phpstan": "^1.12.28",
1410
-
"phpstan/phpstan-mockery": "^1.1.3"
1411
-
},
1412
-
"suggest": {
1413
-
"ext-pcntl": "Required for the spinner to be animated."
1414
-
},
1415
-
"type": "library",
1416
-
"extra": {
1417
-
"branch-alias": {
1418
-
"dev-main": "0.3.x-dev"
1419
-
}
1420
-
},
1421
-
"autoload": {
1422
-
"files": [
1423
-
"src/helpers.php"
1424
-
],
1425
-
"psr-4": {
1426
-
"Laravel\\Prompts\\": "src/"
1427
-
}
1428
-
},
1429
-
"notification-url": "https://packagist.org/downloads/",
1430
-
"license": [
1431
-
"MIT"
1432
-
],
1433
-
"description": "Add beautiful and user-friendly forms to your command-line applications.",
1434
-
"support": {
1435
-
"issues": "https://github.com/laravel/prompts/issues",
1436
-
"source": "https://github.com/laravel/prompts/tree/v0.3.7"
1437
-
},
1438
-
"time": "2025-09-19T13:47:56+00:00"
1439
-
},
1440
-
{
1441
-
"name": "laravel/serializable-closure",
1442
-
"version": "v2.0.6",
1443
-
"source": {
1444
-
"type": "git",
1445
-
"url": "https://github.com/laravel/serializable-closure.git",
1446
-
"reference": "038ce42edee619599a1debb7e81d7b3759492819"
1447
-
},
1448
-
"dist": {
1449
-
"type": "zip",
1450
-
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819",
1451
-
"reference": "038ce42edee619599a1debb7e81d7b3759492819",
1452
-
"shasum": ""
1453
-
},
1454
-
"require": {
1455
-
"php": "^8.1"
1456
-
},
1457
-
"require-dev": {
1458
-
"illuminate/support": "^10.0|^11.0|^12.0",
1459
-
"nesbot/carbon": "^2.67|^3.0",
1460
-
"pestphp/pest": "^2.36|^3.0",
1461
-
"phpstan/phpstan": "^2.0",
1462
-
"symfony/var-dumper": "^6.2.0|^7.0.0"
1463
-
},
1464
-
"type": "library",
1465
-
"extra": {
1466
-
"branch-alias": {
1467
-
"dev-master": "2.x-dev"
1468
-
}
1469
-
},
1470
-
"autoload": {
1471
-
"psr-4": {
1472
-
"Laravel\\SerializableClosure\\": "src/"
1473
-
}
1474
-
},
1475
-
"notification-url": "https://packagist.org/downloads/",
1476
-
"license": [
1477
-
"MIT"
1478
-
],
1479
-
"authors": [
1480
-
{
1481
-
"name": "Taylor Otwell",
1482
-
"email": "taylor@laravel.com"
1483
-
},
1484
-
{
1485
-
"name": "Nuno Maduro",
1486
-
"email": "nuno@laravel.com"
1487
-
}
1488
-
],
1489
-
"description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
1490
-
"keywords": [
1491
-
"closure",
1492
-
"laravel",
1493
-
"serializable"
1494
-
],
1495
-
"support": {
1496
-
"issues": "https://github.com/laravel/serializable-closure/issues",
1497
-
"source": "https://github.com/laravel/serializable-closure"
1498
-
},
1499
-
"time": "2025-10-09T13:42:30+00:00"
1500
-
},
1501
-
{
1502
-
"name": "laravel/socialite",
1503
-
"version": "v5.23.1",
1504
-
"source": {
1505
-
"type": "git",
1506
-
"url": "https://github.com/laravel/socialite.git",
1507
-
"reference": "83d7523c97c1101d288126948947891319eef800"
1508
-
},
1509
-
"dist": {
1510
-
"type": "zip",
1511
-
"url": "https://api.github.com/repos/laravel/socialite/zipball/83d7523c97c1101d288126948947891319eef800",
1512
-
"reference": "83d7523c97c1101d288126948947891319eef800",
1513
-
"shasum": ""
1514
-
},
1515
-
"require": {
1516
-
"ext-json": "*",
1517
-
"firebase/php-jwt": "^6.4",
1518
-
"guzzlehttp/guzzle": "^6.0|^7.0",
1519
-
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
1520
-
"illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
1521
-
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
1522
-
"league/oauth1-client": "^1.11",
1523
-
"php": "^7.2|^8.0",
1524
-
"phpseclib/phpseclib": "^3.0"
1525
-
},
1526
-
"require-dev": {
1527
-
"mockery/mockery": "^1.0",
1528
-
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0",
1529
-
"phpstan/phpstan": "^1.12.23",
1530
-
"phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5"
1531
-
},
1532
-
"type": "library",
1533
-
"extra": {
1534
-
"laravel": {
1535
-
"aliases": {
1536
-
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
1537
-
},
1538
-
"providers": [
1539
-
"Laravel\\Socialite\\SocialiteServiceProvider"
1540
-
]
1541
-
},
1542
-
"branch-alias": {
1543
-
"dev-master": "5.x-dev"
1544
-
}
1545
-
},
1546
-
"autoload": {
1547
-
"psr-4": {
1548
-
"Laravel\\Socialite\\": "src/"
1549
-
}
1550
-
},
1551
-
"notification-url": "https://packagist.org/downloads/",
1552
-
"license": [
1553
-
"MIT"
1554
-
],
1555
-
"authors": [
1556
-
{
1557
-
"name": "Taylor Otwell",
1558
-
"email": "taylor@laravel.com"
1559
-
}
1560
-
],
1561
-
"description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
1562
-
"homepage": "https://laravel.com",
1563
-
"keywords": [
1564
-
"laravel",
1565
-
"oauth"
1566
-
],
1567
-
"support": {
1568
-
"issues": "https://github.com/laravel/socialite/issues",
1569
-
"source": "https://github.com/laravel/socialite"
1570
-
},
1571
-
"time": "2025-10-27T15:36:41+00:00"
1572
-
},
1573
-
{
1574
-
"name": "league/commonmark",
1575
-
"version": "2.7.1",
1576
-
"source": {
1577
-
"type": "git",
1578
-
"url": "https://github.com/thephpleague/commonmark.git",
1579
-
"reference": "10732241927d3971d28e7ea7b5712721fa2296ca"
1580
-
},
1581
-
"dist": {
1582
-
"type": "zip",
1583
-
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca",
1584
-
"reference": "10732241927d3971d28e7ea7b5712721fa2296ca",
1585
-
"shasum": ""
1586
-
},
1587
-
"require": {
1588
-
"ext-mbstring": "*",
1589
-
"league/config": "^1.1.1",
1590
-
"php": "^7.4 || ^8.0",
1591
-
"psr/event-dispatcher": "^1.0",
1592
-
"symfony/deprecation-contracts": "^2.1 || ^3.0",
1593
-
"symfony/polyfill-php80": "^1.16"
1594
-
},
1595
-
"require-dev": {
1596
-
"cebe/markdown": "^1.0",
1597
-
"commonmark/cmark": "0.31.1",
1598
-
"commonmark/commonmark.js": "0.31.1",
1599
-
"composer/package-versions-deprecated": "^1.8",
1600
-
"embed/embed": "^4.4",
1601
-
"erusev/parsedown": "^1.0",
1602
-
"ext-json": "*",
1603
-
"github/gfm": "0.29.0",
1604
-
"michelf/php-markdown": "^1.4 || ^2.0",
1605
-
"nyholm/psr7": "^1.5",
1606
-
"phpstan/phpstan": "^1.8.2",
1607
-
"phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
1608
-
"scrutinizer/ocular": "^1.8.1",
1609
-
"symfony/finder": "^5.3 | ^6.0 | ^7.0",
1610
-
"symfony/process": "^5.4 | ^6.0 | ^7.0",
1611
-
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
1612
-
"unleashedtech/php-coding-standard": "^3.1.1",
1613
-
"vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
1614
-
},
1615
-
"suggest": {
1616
-
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
1617
-
},
1618
-
"type": "library",
1619
-
"extra": {
1620
-
"branch-alias": {
1621
-
"dev-main": "2.8-dev"
1622
-
}
1623
-
},
1624
-
"autoload": {
1625
-
"psr-4": {
1626
-
"League\\CommonMark\\": "src"
1627
-
}
1628
-
},
1629
-
"notification-url": "https://packagist.org/downloads/",
1630
-
"license": [
1631
-
"BSD-3-Clause"
1632
-
],
1633
-
"authors": [
1634
-
{
1635
-
"name": "Colin O'Dell",
1636
-
"email": "colinodell@gmail.com",
1637
-
"homepage": "https://www.colinodell.com",
1638
-
"role": "Lead Developer"
1639
-
}
1640
-
],
1641
-
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
1642
-
"homepage": "https://commonmark.thephpleague.com",
1643
-
"keywords": [
1644
-
"commonmark",
1645
-
"flavored",
1646
-
"gfm",
1647
-
"github",
1648
-
"github-flavored",
1649
-
"markdown",
1650
-
"md",
1651
-
"parser"
1652
-
],
1653
-
"support": {
1654
-
"docs": "https://commonmark.thephpleague.com/",
1655
-
"forum": "https://github.com/thephpleague/commonmark/discussions",
1656
-
"issues": "https://github.com/thephpleague/commonmark/issues",
1657
-
"rss": "https://github.com/thephpleague/commonmark/releases.atom",
1658
-
"source": "https://github.com/thephpleague/commonmark"
1659
-
},
1660
-
"funding": [
1661
-
{
1662
-
"url": "https://www.colinodell.com/sponsor",
1663
-
"type": "custom"
1664
-
},
1665
-
{
1666
-
"url": "https://www.paypal.me/colinpodell/10.00",
1667
-
"type": "custom"
1668
-
},
1669
-
{
1670
-
"url": "https://github.com/colinodell",
1671
-
"type": "github"
1672
-
},
1673
-
{
1674
-
"url": "https://tidelift.com/funding/github/packagist/league/commonmark",
1675
-
"type": "tidelift"
1676
-
}
1677
-
],
1678
-
"time": "2025-07-20T12:47:49+00:00"
1679
-
},
1680
-
{
1681
-
"name": "league/config",
1682
-
"version": "v1.2.0",
1683
-
"source": {
1684
-
"type": "git",
1685
-
"url": "https://github.com/thephpleague/config.git",
1686
-
"reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3"
1687
-
},
1688
-
"dist": {
1689
-
"type": "zip",
1690
-
"url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
1691
-
"reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
1692
-
"shasum": ""
1693
-
},
1694
-
"require": {
1695
-
"dflydev/dot-access-data": "^3.0.1",
1696
-
"nette/schema": "^1.2",
1697
-
"php": "^7.4 || ^8.0"
1698
-
},
1699
-
"require-dev": {
1700
-
"phpstan/phpstan": "^1.8.2",
1701
-
"phpunit/phpunit": "^9.5.5",
1702
-
"scrutinizer/ocular": "^1.8.1",
1703
-
"unleashedtech/php-coding-standard": "^3.1",
1704
-
"vimeo/psalm": "^4.7.3"
1705
-
},
1706
-
"type": "library",
1707
-
"extra": {
1708
-
"branch-alias": {
1709
-
"dev-main": "1.2-dev"
1710
-
}
1711
-
},
1712
-
"autoload": {
1713
-
"psr-4": {
1714
-
"League\\Config\\": "src"
1715
-
}
1716
-
},
1717
-
"notification-url": "https://packagist.org/downloads/",
1718
-
"license": [
1719
-
"BSD-3-Clause"
1720
-
],
1721
-
"authors": [
1722
-
{
1723
-
"name": "Colin O'Dell",
1724
-
"email": "colinodell@gmail.com",
1725
-
"homepage": "https://www.colinodell.com",
1726
-
"role": "Lead Developer"
1727
-
}
1728
-
],
1729
-
"description": "Define configuration arrays with strict schemas and access values with dot notation",
1730
-
"homepage": "https://config.thephpleague.com",
1731
-
"keywords": [
1732
-
"array",
1733
-
"config",
1734
-
"configuration",
1735
-
"dot",
1736
-
"dot-access",
1737
-
"nested",
1738
-
"schema"
1739
-
],
1740
-
"support": {
1741
-
"docs": "https://config.thephpleague.com/",
1742
-
"issues": "https://github.com/thephpleague/config/issues",
1743
-
"rss": "https://github.com/thephpleague/config/releases.atom",
1744
-
"source": "https://github.com/thephpleague/config"
1745
-
},
1746
-
"funding": [
1747
-
{
1748
-
"url": "https://www.colinodell.com/sponsor",
1749
-
"type": "custom"
1750
-
},
1751
-
{
1752
-
"url": "https://www.paypal.me/colinpodell/10.00",
1753
-
"type": "custom"
1754
-
},
1755
-
{
1756
-
"url": "https://github.com/colinodell",
1757
-
"type": "github"
1758
-
}
1759
-
],
1760
-
"time": "2022-12-11T20:36:23+00:00"
1761
-
},
1762
-
{
1763
-
"name": "league/flysystem",
1764
-
"version": "3.30.1",
1765
-
"source": {
1766
-
"type": "git",
1767
-
"url": "https://github.com/thephpleague/flysystem.git",
1768
-
"reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da"
1769
-
},
1770
-
"dist": {
1771
-
"type": "zip",
1772
-
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/c139fd65c1f796b926f4aec0df37f6caa959a8da",
1773
-
"reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da",
1774
-
"shasum": ""
1775
-
},
1776
-
"require": {
1777
-
"league/flysystem-local": "^3.0.0",
1778
-
"league/mime-type-detection": "^1.0.0",
1779
-
"php": "^8.0.2"
1780
-
},
1781
-
"conflict": {
1782
-
"async-aws/core": "<1.19.0",
1783
-
"async-aws/s3": "<1.14.0",
1784
-
"aws/aws-sdk-php": "3.209.31 || 3.210.0",
1785
-
"guzzlehttp/guzzle": "<7.0",
1786
-
"guzzlehttp/ringphp": "<1.1.1",
1787
-
"phpseclib/phpseclib": "3.0.15",
1788
-
"symfony/http-client": "<5.2"
1789
-
},
1790
-
"require-dev": {
1791
-
"async-aws/s3": "^1.5 || ^2.0",
1792
-
"async-aws/simple-s3": "^1.1 || ^2.0",
1793
-
"aws/aws-sdk-php": "^3.295.10",
1794
-
"composer/semver": "^3.0",
1795
-
"ext-fileinfo": "*",
1796
-
"ext-ftp": "*",
1797
-
"ext-mongodb": "^1.3|^2",
1798
-
"ext-zip": "*",
1799
-
"friendsofphp/php-cs-fixer": "^3.5",
1800
-
"google/cloud-storage": "^1.23",
1801
-
"guzzlehttp/psr7": "^2.6",
1802
-
"microsoft/azure-storage-blob": "^1.1",
1803
-
"mongodb/mongodb": "^1.2|^2",
1804
-
"phpseclib/phpseclib": "^3.0.36",
1805
-
"phpstan/phpstan": "^1.10",
1806
-
"phpunit/phpunit": "^9.5.11|^10.0",
1807
-
"sabre/dav": "^4.6.0"
1808
-
},
1809
-
"type": "library",
1810
-
"autoload": {
1811
-
"psr-4": {
1812
-
"League\\Flysystem\\": "src"
1813
-
}
1814
-
},
1815
-
"notification-url": "https://packagist.org/downloads/",
1816
-
"license": [
1817
-
"MIT"
1818
-
],
1819
-
"authors": [
1820
-
{
1821
-
"name": "Frank de Jonge",
1822
-
"email": "info@frankdejonge.nl"
1823
-
}
1824
-
],
1825
-
"description": "File storage abstraction for PHP",
1826
-
"keywords": [
1827
-
"WebDAV",
1828
-
"aws",
1829
-
"cloud",
1830
-
"file",
1831
-
"files",
1832
-
"filesystem",
1833
-
"filesystems",
1834
-
"ftp",
1835
-
"s3",
1836
-
"sftp",
1837
-
"storage"
1838
-
],
1839
-
"support": {
1840
-
"issues": "https://github.com/thephpleague/flysystem/issues",
1841
-
"source": "https://github.com/thephpleague/flysystem/tree/3.30.1"
1842
-
},
1843
-
"time": "2025-10-20T15:35:26+00:00"
1844
-
},
1845
-
{
1846
-
"name": "league/flysystem-local",
1847
-
"version": "3.30.0",
1848
-
"source": {
1849
-
"type": "git",
1850
-
"url": "https://github.com/thephpleague/flysystem-local.git",
1851
-
"reference": "6691915f77c7fb69adfb87dcd550052dc184ee10"
1852
-
},
1853
-
"dist": {
1854
-
"type": "zip",
1855
-
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/6691915f77c7fb69adfb87dcd550052dc184ee10",
1856
-
"reference": "6691915f77c7fb69adfb87dcd550052dc184ee10",
1857
-
"shasum": ""
1858
-
},
1859
-
"require": {
1860
-
"ext-fileinfo": "*",
1861
-
"league/flysystem": "^3.0.0",
1862
-
"league/mime-type-detection": "^1.0.0",
1863
-
"php": "^8.0.2"
1864
-
},
1865
-
"type": "library",
1866
-
"autoload": {
1867
-
"psr-4": {
1868
-
"League\\Flysystem\\Local\\": ""
1869
-
}
1870
-
},
1871
-
"notification-url": "https://packagist.org/downloads/",
1872
-
"license": [
1873
-
"MIT"
1874
-
],
1875
-
"authors": [
1876
-
{
1877
-
"name": "Frank de Jonge",
1878
-
"email": "info@frankdejonge.nl"
1879
-
}
1880
-
],
1881
-
"description": "Local filesystem adapter for Flysystem.",
1882
-
"keywords": [
1883
-
"Flysystem",
1884
-
"file",
1885
-
"files",
1886
-
"filesystem",
1887
-
"local"
1888
-
],
1889
-
"support": {
1890
-
"source": "https://github.com/thephpleague/flysystem-local/tree/3.30.0"
1891
-
},
1892
-
"time": "2025-05-21T10:34:19+00:00"
1893
-
},
1894
-
{
1895
-
"name": "league/mime-type-detection",
1896
-
"version": "1.16.0",
1897
-
"source": {
1898
-
"type": "git",
1899
-
"url": "https://github.com/thephpleague/mime-type-detection.git",
1900
-
"reference": "2d6702ff215bf922936ccc1ad31007edc76451b9"
1901
-
},
1902
-
"dist": {
1903
-
"type": "zip",
1904
-
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9",
1905
-
"reference": "2d6702ff215bf922936ccc1ad31007edc76451b9",
1906
-
"shasum": ""
1907
-
},
1908
-
"require": {
1909
-
"ext-fileinfo": "*",
1910
-
"php": "^7.4 || ^8.0"
1911
-
},
1912
-
"require-dev": {
1913
-
"friendsofphp/php-cs-fixer": "^3.2",
1914
-
"phpstan/phpstan": "^0.12.68",
1915
-
"phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0"
1916
-
},
1917
-
"type": "library",
1918
-
"autoload": {
1919
-
"psr-4": {
1920
-
"League\\MimeTypeDetection\\": "src"
1921
-
}
1922
-
},
1923
-
"notification-url": "https://packagist.org/downloads/",
1924
-
"license": [
1925
-
"MIT"
1926
-
],
1927
-
"authors": [
1928
-
{
1929
-
"name": "Frank de Jonge",
1930
-
"email": "info@frankdejonge.nl"
1931
-
}
1932
-
],
1933
-
"description": "Mime-type detection for Flysystem",
1934
-
"support": {
1935
-
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
1936
-
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0"
1937
-
},
1938
-
"funding": [
1939
-
{
1940
-
"url": "https://github.com/frankdejonge",
1941
-
"type": "github"
1942
-
},
1943
-
{
1944
-
"url": "https://tidelift.com/funding/github/packagist/league/flysystem",
1945
-
"type": "tidelift"
1946
-
}
1947
-
],
1948
-
"time": "2024-09-21T08:32:55+00:00"
1949
-
},
1950
-
{
1951
-
"name": "league/oauth1-client",
1952
-
"version": "v1.11.0",
1953
-
"source": {
1954
-
"type": "git",
1955
-
"url": "https://github.com/thephpleague/oauth1-client.git",
1956
-
"reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055"
1957
-
},
1958
-
"dist": {
1959
-
"type": "zip",
1960
-
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/f9c94b088837eb1aae1ad7c4f23eb65cc6993055",
1961
-
"reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055",
1962
-
"shasum": ""
1963
-
},
1964
-
"require": {
1965
-
"ext-json": "*",
1966
-
"ext-openssl": "*",
1967
-
"guzzlehttp/guzzle": "^6.0|^7.0",
1968
-
"guzzlehttp/psr7": "^1.7|^2.0",
1969
-
"php": ">=7.1||>=8.0"
1970
-
},
1971
-
"require-dev": {
1972
-
"ext-simplexml": "*",
1973
-
"friendsofphp/php-cs-fixer": "^2.17",
1974
-
"mockery/mockery": "^1.3.3",
1975
-
"phpstan/phpstan": "^0.12.42",
1976
-
"phpunit/phpunit": "^7.5||9.5"
1977
-
},
1978
-
"suggest": {
1979
-
"ext-simplexml": "For decoding XML-based responses."
1980
-
},
1981
-
"type": "library",
1982
-
"extra": {
1983
-
"branch-alias": {
1984
-
"dev-master": "1.0-dev",
1985
-
"dev-develop": "2.0-dev"
1986
-
}
1987
-
},
1988
-
"autoload": {
1989
-
"psr-4": {
1990
-
"League\\OAuth1\\Client\\": "src/"
1991
-
}
1992
-
},
1993
-
"notification-url": "https://packagist.org/downloads/",
1994
-
"license": [
1995
-
"MIT"
1996
-
],
1997
-
"authors": [
1998
-
{
1999
-
"name": "Ben Corlett",
2000
-
"email": "bencorlett@me.com",
2001
-
"homepage": "http://www.webcomm.com.au",
2002
-
"role": "Developer"
2003
-
}
2004
-
],
2005
-
"description": "OAuth 1.0 Client Library",
2006
-
"keywords": [
2007
-
"Authentication",
2008
-
"SSO",
2009
-
"authorization",
2010
-
"bitbucket",
2011
-
"identity",
2012
-
"idp",
2013
-
"oauth",
2014
-
"oauth1",
2015
-
"single sign on",
2016
-
"trello",
2017
-
"tumblr",
2018
-
"twitter"
2019
-
],
2020
-
"support": {
2021
-
"issues": "https://github.com/thephpleague/oauth1-client/issues",
2022
-
"source": "https://github.com/thephpleague/oauth1-client/tree/v1.11.0"
2023
-
},
2024
-
"time": "2024-12-10T19:59:05+00:00"
2025
-
},
2026
-
{
2027
-
"name": "league/uri",
2028
-
"version": "7.5.1",
2029
-
"source": {
2030
-
"type": "git",
2031
-
"url": "https://github.com/thephpleague/uri.git",
2032
-
"reference": "81fb5145d2644324614cc532b28efd0215bda430"
2033
-
},
2034
-
"dist": {
2035
-
"type": "zip",
2036
-
"url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430",
2037
-
"reference": "81fb5145d2644324614cc532b28efd0215bda430",
2038
-
"shasum": ""
2039
-
},
2040
-
"require": {
2041
-
"league/uri-interfaces": "^7.5",
2042
-
"php": "^8.1"
2043
-
},
2044
-
"conflict": {
2045
-
"league/uri-schemes": "^1.0"
2046
-
},
2047
-
"suggest": {
2048
-
"ext-bcmath": "to improve IPV4 host parsing",
2049
-
"ext-fileinfo": "to create Data URI from file contennts",
2050
-
"ext-gmp": "to improve IPV4 host parsing",
2051
-
"ext-intl": "to handle IDN host with the best performance",
2052
-
"jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain",
2053
-
"league/uri-components": "Needed to easily manipulate URI objects components",
2054
-
"php-64bit": "to improve IPV4 host parsing",
2055
-
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
2056
-
},
2057
-
"type": "library",
2058
-
"extra": {
2059
-
"branch-alias": {
2060
-
"dev-master": "7.x-dev"
2061
-
}
2062
-
},
2063
-
"autoload": {
2064
-
"psr-4": {
2065
-
"League\\Uri\\": ""
2066
-
}
2067
-
},
2068
-
"notification-url": "https://packagist.org/downloads/",
2069
-
"license": [
2070
-
"MIT"
2071
-
],
2072
-
"authors": [
2073
-
{
2074
-
"name": "Ignace Nyamagana Butera",
2075
-
"email": "nyamsprod@gmail.com",
2076
-
"homepage": "https://nyamsprod.com"
2077
-
}
2078
-
],
2079
-
"description": "URI manipulation library",
2080
-
"homepage": "https://uri.thephpleague.com",
2081
-
"keywords": [
2082
-
"data-uri",
2083
-
"file-uri",
2084
-
"ftp",
2085
-
"hostname",
2086
-
"http",
2087
-
"https",
2088
-
"middleware",
2089
-
"parse_str",
2090
-
"parse_url",
2091
-
"psr-7",
2092
-
"query-string",
2093
-
"querystring",
2094
-
"rfc3986",
2095
-
"rfc3987",
2096
-
"rfc6570",
2097
-
"uri",
2098
-
"uri-template",
2099
-
"url",
2100
-
"ws"
2101
-
],
2102
-
"support": {
2103
-
"docs": "https://uri.thephpleague.com",
2104
-
"forum": "https://thephpleague.slack.com",
2105
-
"issues": "https://github.com/thephpleague/uri-src/issues",
2106
-
"source": "https://github.com/thephpleague/uri/tree/7.5.1"
2107
-
},
2108
-
"funding": [
2109
-
{
2110
-
"url": "https://github.com/sponsors/nyamsprod",
2111
-
"type": "github"
2112
-
}
2113
-
],
2114
-
"time": "2024-12-08T08:40:02+00:00"
2115
-
},
2116
-
{
2117
-
"name": "league/uri-interfaces",
2118
-
"version": "7.5.0",
2119
-
"source": {
2120
-
"type": "git",
2121
-
"url": "https://github.com/thephpleague/uri-interfaces.git",
2122
-
"reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742"
2123
-
},
2124
-
"dist": {
2125
-
"type": "zip",
2126
-
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742",
2127
-
"reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742",
2128
-
"shasum": ""
2129
-
},
2130
-
"require": {
2131
-
"ext-filter": "*",
2132
-
"php": "^8.1",
2133
-
"psr/http-factory": "^1",
2134
-
"psr/http-message": "^1.1 || ^2.0"
2135
-
},
2136
-
"suggest": {
2137
-
"ext-bcmath": "to improve IPV4 host parsing",
2138
-
"ext-gmp": "to improve IPV4 host parsing",
2139
-
"ext-intl": "to handle IDN host with the best performance",
2140
-
"php-64bit": "to improve IPV4 host parsing",
2141
-
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
2142
-
},
2143
-
"type": "library",
2144
-
"extra": {
2145
-
"branch-alias": {
2146
-
"dev-master": "7.x-dev"
2147
-
}
2148
-
},
2149
-
"autoload": {
2150
-
"psr-4": {
2151
-
"League\\Uri\\": ""
2152
-
}
2153
-
},
2154
-
"notification-url": "https://packagist.org/downloads/",
2155
-
"license": [
2156
-
"MIT"
2157
-
],
2158
-
"authors": [
2159
-
{
2160
-
"name": "Ignace Nyamagana Butera",
2161
-
"email": "nyamsprod@gmail.com",
2162
-
"homepage": "https://nyamsprod.com"
2163
-
}
2164
-
],
2165
-
"description": "Common interfaces and classes for URI representation and interaction",
2166
-
"homepage": "https://uri.thephpleague.com",
2167
-
"keywords": [
2168
-
"data-uri",
2169
-
"file-uri",
2170
-
"ftp",
2171
-
"hostname",
2172
-
"http",
2173
-
"https",
2174
-
"parse_str",
2175
-
"parse_url",
2176
-
"psr-7",
2177
-
"query-string",
2178
-
"querystring",
2179
-
"rfc3986",
2180
-
"rfc3987",
2181
-
"rfc6570",
2182
-
"uri",
2183
-
"url",
2184
-
"ws"
2185
-
],
2186
-
"support": {
2187
-
"docs": "https://uri.thephpleague.com",
2188
-
"forum": "https://thephpleague.slack.com",
2189
-
"issues": "https://github.com/thephpleague/uri-src/issues",
2190
-
"source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0"
2191
-
},
2192
-
"funding": [
2193
-
{
2194
-
"url": "https://github.com/sponsors/nyamsprod",
2195
-
"type": "github"
2196
-
}
2197
-
],
2198
-
"time": "2024-12-08T08:18:47+00:00"
2199
-
},
2200
-
{
2201
-
"name": "monolog/monolog",
2202
-
"version": "3.9.0",
2203
-
"source": {
2204
-
"type": "git",
2205
-
"url": "https://github.com/Seldaek/monolog.git",
2206
-
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
2207
-
},
2208
-
"dist": {
2209
-
"type": "zip",
2210
-
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
2211
-
"reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
2212
-
"shasum": ""
2213
-
},
2214
-
"require": {
2215
-
"php": ">=8.1",
2216
-
"psr/log": "^2.0 || ^3.0"
2217
-
},
2218
-
"provide": {
2219
-
"psr/log-implementation": "3.0.0"
2220
-
},
2221
-
"require-dev": {
2222
-
"aws/aws-sdk-php": "^3.0",
2223
-
"doctrine/couchdb": "~1.0@dev",
2224
-
"elasticsearch/elasticsearch": "^7 || ^8",
2225
-
"ext-json": "*",
2226
-
"graylog2/gelf-php": "^1.4.2 || ^2.0",
2227
-
"guzzlehttp/guzzle": "^7.4.5",
2228
-
"guzzlehttp/psr7": "^2.2",
2229
-
"mongodb/mongodb": "^1.8",
2230
-
"php-amqplib/php-amqplib": "~2.4 || ^3",
2231
-
"php-console/php-console": "^3.1.8",
2232
-
"phpstan/phpstan": "^2",
2233
-
"phpstan/phpstan-deprecation-rules": "^2",
2234
-
"phpstan/phpstan-strict-rules": "^2",
2235
-
"phpunit/phpunit": "^10.5.17 || ^11.0.7",
2236
-
"predis/predis": "^1.1 || ^2",
2237
-
"rollbar/rollbar": "^4.0",
2238
-
"ruflin/elastica": "^7 || ^8",
2239
-
"symfony/mailer": "^5.4 || ^6",
2240
-
"symfony/mime": "^5.4 || ^6"
2241
-
},
2242
-
"suggest": {
2243
-
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
2244
-
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
2245
-
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
2246
-
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
2247
-
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
2248
-
"ext-mbstring": "Allow to work properly with unicode symbols",
2249
-
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
2250
-
"ext-openssl": "Required to send log messages using SSL",
2251
-
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
2252
-
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
2253
-
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
2254
-
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
2255
-
"rollbar/rollbar": "Allow sending log messages to Rollbar",
2256
-
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
2257
-
},
2258
-
"type": "library",
2259
-
"extra": {
2260
-
"branch-alias": {
2261
-
"dev-main": "3.x-dev"
2262
-
}
2263
-
},
2264
-
"autoload": {
2265
-
"psr-4": {
2266
-
"Monolog\\": "src/Monolog"
2267
-
}
2268
-
},
2269
-
"notification-url": "https://packagist.org/downloads/",
2270
-
"license": [
2271
-
"MIT"
2272
-
],
2273
-
"authors": [
2274
-
{
2275
-
"name": "Jordi Boggiano",
2276
-
"email": "j.boggiano@seld.be",
2277
-
"homepage": "https://seld.be"
2278
-
}
2279
-
],
2280
-
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
2281
-
"homepage": "https://github.com/Seldaek/monolog",
2282
-
"keywords": [
2283
-
"log",
2284
-
"logging",
2285
-
"psr-3"
2286
-
],
2287
-
"support": {
2288
-
"issues": "https://github.com/Seldaek/monolog/issues",
2289
-
"source": "https://github.com/Seldaek/monolog/tree/3.9.0"
2290
-
},
2291
-
"funding": [
2292
-
{
2293
-
"url": "https://github.com/Seldaek",
2294
-
"type": "github"
2295
-
},
2296
-
{
2297
-
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
2298
-
"type": "tidelift"
2299
-
}
2300
-
],
2301
-
"time": "2025-03-24T10:02:05+00:00"
2302
-
},
2303
-
{
2304
-
"name": "nesbot/carbon",
2305
-
"version": "3.10.3",
2306
-
"source": {
2307
-
"type": "git",
2308
-
"url": "https://github.com/CarbonPHP/carbon.git",
2309
-
"reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f"
2310
-
},
2311
-
"dist": {
2312
-
"type": "zip",
2313
-
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f",
2314
-
"reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f",
2315
-
"shasum": ""
2316
-
},
2317
-
"require": {
2318
-
"carbonphp/carbon-doctrine-types": "<100.0",
2319
-
"ext-json": "*",
2320
-
"php": "^8.1",
2321
-
"psr/clock": "^1.0",
2322
-
"symfony/clock": "^6.3.12 || ^7.0",
2323
-
"symfony/polyfill-mbstring": "^1.0",
2324
-
"symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0"
2325
-
},
2326
-
"provide": {
2327
-
"psr/clock-implementation": "1.0"
2328
-
},
2329
-
"require-dev": {
2330
-
"doctrine/dbal": "^3.6.3 || ^4.0",
2331
-
"doctrine/orm": "^2.15.2 || ^3.0",
2332
-
"friendsofphp/php-cs-fixer": "^v3.87.1",
2333
-
"kylekatarnls/multi-tester": "^2.5.3",
2334
-
"phpmd/phpmd": "^2.15.0",
2335
-
"phpstan/extension-installer": "^1.4.3",
2336
-
"phpstan/phpstan": "^2.1.22",
2337
-
"phpunit/phpunit": "^10.5.53",
2338
-
"squizlabs/php_codesniffer": "^3.13.4"
2339
-
},
2340
-
"bin": [
2341
-
"bin/carbon"
2342
-
],
2343
-
"type": "library",
2344
-
"extra": {
2345
-
"laravel": {
2346
-
"providers": [
2347
-
"Carbon\\Laravel\\ServiceProvider"
2348
-
]
2349
-
},
2350
-
"phpstan": {
2351
-
"includes": [
2352
-
"extension.neon"
2353
-
]
2354
-
},
2355
-
"branch-alias": {
2356
-
"dev-2.x": "2.x-dev",
2357
-
"dev-master": "3.x-dev"
2358
-
}
2359
-
},
2360
-
"autoload": {
2361
-
"psr-4": {
2362
-
"Carbon\\": "src/Carbon/"
2363
-
}
2364
-
},
2365
-
"notification-url": "https://packagist.org/downloads/",
2366
-
"license": [
2367
-
"MIT"
2368
-
],
2369
-
"authors": [
2370
-
{
2371
-
"name": "Brian Nesbitt",
2372
-
"email": "brian@nesbot.com",
2373
-
"homepage": "https://markido.com"
2374
-
},
2375
-
{
2376
-
"name": "kylekatarnls",
2377
-
"homepage": "https://github.com/kylekatarnls"
2378
-
}
2379
-
],
2380
-
"description": "An API extension for DateTime that supports 281 different languages.",
2381
-
"homepage": "https://carbon.nesbot.com",
2382
-
"keywords": [
2383
-
"date",
2384
-
"datetime",
2385
-
"time"
2386
-
],
2387
-
"support": {
2388
-
"docs": "https://carbon.nesbot.com/docs",
2389
-
"issues": "https://github.com/CarbonPHP/carbon/issues",
2390
-
"source": "https://github.com/CarbonPHP/carbon"
2391
-
},
2392
-
"funding": [
2393
-
{
2394
-
"url": "https://github.com/sponsors/kylekatarnls",
2395
-
"type": "github"
2396
-
},
2397
-
{
2398
-
"url": "https://opencollective.com/Carbon#sponsor",
2399
-
"type": "opencollective"
2400
-
},
2401
-
{
2402
-
"url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
2403
-
"type": "tidelift"
2404
-
}
2405
-
],
2406
-
"time": "2025-09-06T13:39:36+00:00"
2407
-
},
2408
-
{
2409
-
"name": "nette/schema",
2410
-
"version": "v1.3.2",
2411
-
"source": {
2412
-
"type": "git",
2413
-
"url": "https://github.com/nette/schema.git",
2414
-
"reference": "da801d52f0354f70a638673c4a0f04e16529431d"
2415
-
},
2416
-
"dist": {
2417
-
"type": "zip",
2418
-
"url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
2419
-
"reference": "da801d52f0354f70a638673c4a0f04e16529431d",
2420
-
"shasum": ""
2421
-
},
2422
-
"require": {
2423
-
"nette/utils": "^4.0",
2424
-
"php": "8.1 - 8.4"
2425
-
},
2426
-
"require-dev": {
2427
-
"nette/tester": "^2.5.2",
2428
-
"phpstan/phpstan-nette": "^1.0",
2429
-
"tracy/tracy": "^2.8"
2430
-
},
2431
-
"type": "library",
2432
-
"extra": {
2433
-
"branch-alias": {
2434
-
"dev-master": "1.3-dev"
2435
-
}
2436
-
},
2437
-
"autoload": {
2438
-
"classmap": [
2439
-
"src/"
2440
-
]
2441
-
},
2442
-
"notification-url": "https://packagist.org/downloads/",
2443
-
"license": [
2444
-
"BSD-3-Clause",
2445
-
"GPL-2.0-only",
2446
-
"GPL-3.0-only"
2447
-
],
2448
-
"authors": [
2449
-
{
2450
-
"name": "David Grudl",
2451
-
"homepage": "https://davidgrudl.com"
2452
-
},
2453
-
{
2454
-
"name": "Nette Community",
2455
-
"homepage": "https://nette.org/contributors"
2456
-
}
2457
-
],
2458
-
"description": "๐ Nette Schema: validating data structures against a given Schema.",
2459
-
"homepage": "https://nette.org",
2460
-
"keywords": [
2461
-
"config",
2462
-
"nette"
2463
-
],
2464
-
"support": {
2465
-
"issues": "https://github.com/nette/schema/issues",
2466
-
"source": "https://github.com/nette/schema/tree/v1.3.2"
2467
-
},
2468
-
"time": "2024-10-06T23:10:23+00:00"
2469
-
},
2470
-
{
2471
-
"name": "nette/utils",
2472
-
"version": "v4.0.8",
2473
-
"source": {
2474
-
"type": "git",
2475
-
"url": "https://github.com/nette/utils.git",
2476
-
"reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede"
2477
-
},
2478
-
"dist": {
2479
-
"type": "zip",
2480
-
"url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede",
2481
-
"reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede",
2482
-
"shasum": ""
2483
-
},
2484
-
"require": {
2485
-
"php": "8.0 - 8.5"
2486
-
},
2487
-
"conflict": {
2488
-
"nette/finder": "<3",
2489
-
"nette/schema": "<1.2.2"
2490
-
},
2491
-
"require-dev": {
2492
-
"jetbrains/phpstorm-attributes": "^1.2",
2493
-
"nette/tester": "^2.5",
2494
-
"phpstan/phpstan-nette": "^2.0@stable",
2495
-
"tracy/tracy": "^2.9"
2496
-
},
2497
-
"suggest": {
2498
-
"ext-gd": "to use Image",
2499
-
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
2500
-
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
2501
-
"ext-json": "to use Nette\\Utils\\Json",
2502
-
"ext-mbstring": "to use Strings::lower() etc...",
2503
-
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
2504
-
},
2505
-
"type": "library",
2506
-
"extra": {
2507
-
"branch-alias": {
2508
-
"dev-master": "4.0-dev"
2509
-
}
2510
-
},
2511
-
"autoload": {
2512
-
"psr-4": {
2513
-
"Nette\\": "src"
2514
-
},
2515
-
"classmap": [
2516
-
"src/"
2517
-
]
2518
-
},
2519
-
"notification-url": "https://packagist.org/downloads/",
2520
-
"license": [
2521
-
"BSD-3-Clause",
2522
-
"GPL-2.0-only",
2523
-
"GPL-3.0-only"
2524
-
],
2525
-
"authors": [
2526
-
{
2527
-
"name": "David Grudl",
2528
-
"homepage": "https://davidgrudl.com"
2529
-
},
2530
-
{
2531
-
"name": "Nette Community",
2532
-
"homepage": "https://nette.org/contributors"
2533
-
}
2534
-
],
2535
-
"description": "๐ Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
2536
-
"homepage": "https://nette.org",
2537
-
"keywords": [
2538
-
"array",
2539
-
"core",
2540
-
"datetime",
2541
-
"images",
2542
-
"json",
2543
-
"nette",
2544
-
"paginator",
2545
-
"password",
2546
-
"slugify",
2547
-
"string",
2548
-
"unicode",
2549
-
"utf-8",
2550
-
"utility",
2551
-
"validation"
2552
-
],
2553
-
"support": {
2554
-
"issues": "https://github.com/nette/utils/issues",
2555
-
"source": "https://github.com/nette/utils/tree/v4.0.8"
2556
-
},
2557
-
"time": "2025-08-06T21:43:34+00:00"
2558
-
},
2559
-
{
2560
-
"name": "nunomaduro/termwind",
2561
-
"version": "v2.3.2",
2562
-
"source": {
2563
-
"type": "git",
2564
-
"url": "https://github.com/nunomaduro/termwind.git",
2565
-
"reference": "eb61920a53057a7debd718a5b89c2178032b52c0"
2566
-
},
2567
-
"dist": {
2568
-
"type": "zip",
2569
-
"url": "https://api.github.com/repos/nunomaduro/termwind/zipball/eb61920a53057a7debd718a5b89c2178032b52c0",
2570
-
"reference": "eb61920a53057a7debd718a5b89c2178032b52c0",
2571
-
"shasum": ""
2572
-
},
2573
-
"require": {
2574
-
"ext-mbstring": "*",
2575
-
"php": "^8.2",
2576
-
"symfony/console": "^7.3.4"
2577
-
},
2578
-
"require-dev": {
2579
-
"illuminate/console": "^11.46.1",
2580
-
"laravel/pint": "^1.25.1",
2581
-
"mockery/mockery": "^1.6.12",
2582
-
"pestphp/pest": "^2.36.0 || ^3.8.4",
2583
-
"phpstan/phpstan": "^1.12.32",
2584
-
"phpstan/phpstan-strict-rules": "^1.6.2",
2585
-
"symfony/var-dumper": "^7.3.4",
2586
-
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
2587
-
},
2588
-
"type": "library",
2589
-
"extra": {
2590
-
"laravel": {
2591
-
"providers": [
2592
-
"Termwind\\Laravel\\TermwindServiceProvider"
2593
-
]
2594
-
},
2595
-
"branch-alias": {
2596
-
"dev-2.x": "2.x-dev"
2597
-
}
2598
-
},
2599
-
"autoload": {
2600
-
"files": [
2601
-
"src/Functions.php"
2602
-
],
2603
-
"psr-4": {
2604
-
"Termwind\\": "src/"
2605
-
}
2606
-
},
2607
-
"notification-url": "https://packagist.org/downloads/",
2608
-
"license": [
2609
-
"MIT"
2610
-
],
2611
-
"authors": [
2612
-
{
2613
-
"name": "Nuno Maduro",
2614
-
"email": "enunomaduro@gmail.com"
2615
-
}
2616
-
],
2617
-
"description": "Its like Tailwind CSS, but for the console.",
2618
-
"keywords": [
2619
-
"cli",
2620
-
"console",
2621
-
"css",
2622
-
"package",
2623
-
"php",
2624
-
"style"
2625
-
],
2626
-
"support": {
2627
-
"issues": "https://github.com/nunomaduro/termwind/issues",
2628
-
"source": "https://github.com/nunomaduro/termwind/tree/v2.3.2"
2629
-
},
2630
-
"funding": [
2631
-
{
2632
-
"url": "https://www.paypal.com/paypalme/enunomaduro",
2633
-
"type": "custom"
2634
-
},
2635
-
{
2636
-
"url": "https://github.com/nunomaduro",
2637
-
"type": "github"
2638
-
},
2639
-
{
2640
-
"url": "https://github.com/xiCO2k",
2641
-
"type": "github"
2642
-
}
2643
-
],
2644
-
"time": "2025-10-18T11:10:27+00:00"
2645
-
},
2646
-
{
2647
-
"name": "paragonie/constant_time_encoding",
2648
-
"version": "v3.1.3",
2649
-
"source": {
2650
-
"type": "git",
2651
-
"url": "https://github.com/paragonie/constant_time_encoding.git",
2652
-
"reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
2653
-
},
2654
-
"dist": {
2655
-
"type": "zip",
2656
-
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
2657
-
"reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
2658
-
"shasum": ""
2659
-
},
2660
-
"require": {
2661
-
"php": "^8"
2662
-
},
2663
-
"require-dev": {
2664
-
"infection/infection": "^0",
2665
-
"nikic/php-fuzzer": "^0",
2666
-
"phpunit/phpunit": "^9|^10|^11",
2667
-
"vimeo/psalm": "^4|^5|^6"
2668
-
},
2669
-
"type": "library",
2670
-
"autoload": {
2671
-
"psr-4": {
2672
-
"ParagonIE\\ConstantTime\\": "src/"
2673
-
}
2674
-
},
2675
-
"notification-url": "https://packagist.org/downloads/",
2676
-
"license": [
2677
-
"MIT"
2678
-
],
2679
-
"authors": [
2680
-
{
2681
-
"name": "Paragon Initiative Enterprises",
2682
-
"email": "security@paragonie.com",
2683
-
"homepage": "https://paragonie.com",
2684
-
"role": "Maintainer"
2685
-
},
2686
-
{
2687
-
"name": "Steve 'Sc00bz' Thomas",
2688
-
"email": "steve@tobtu.com",
2689
-
"homepage": "https://www.tobtu.com",
2690
-
"role": "Original Developer"
2691
-
}
2692
-
],
2693
-
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
2694
-
"keywords": [
2695
-
"base16",
2696
-
"base32",
2697
-
"base32_decode",
2698
-
"base32_encode",
2699
-
"base64",
2700
-
"base64_decode",
2701
-
"base64_encode",
2702
-
"bin2hex",
2703
-
"encoding",
2704
-
"hex",
2705
-
"hex2bin",
2706
-
"rfc4648"
2707
-
],
2708
-
"support": {
2709
-
"email": "info@paragonie.com",
2710
-
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
2711
-
"source": "https://github.com/paragonie/constant_time_encoding"
2712
-
},
2713
-
"time": "2025-09-24T15:06:41+00:00"
2714
-
},
2715
-
{
2716
-
"name": "paragonie/random_compat",
2717
-
"version": "v9.99.100",
2718
-
"source": {
2719
-
"type": "git",
2720
-
"url": "https://github.com/paragonie/random_compat.git",
2721
-
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
2722
-
},
2723
-
"dist": {
2724
-
"type": "zip",
2725
-
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
2726
-
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
2727
-
"shasum": ""
2728
-
},
2729
-
"require": {
2730
-
"php": ">= 7"
2731
-
},
2732
-
"require-dev": {
2733
-
"phpunit/phpunit": "4.*|5.*",
2734
-
"vimeo/psalm": "^1"
2735
-
},
2736
-
"suggest": {
2737
-
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
2738
-
},
2739
-
"type": "library",
2740
-
"notification-url": "https://packagist.org/downloads/",
2741
-
"license": [
2742
-
"MIT"
2743
-
],
2744
-
"authors": [
2745
-
{
2746
-
"name": "Paragon Initiative Enterprises",
2747
-
"email": "security@paragonie.com",
2748
-
"homepage": "https://paragonie.com"
2749
-
}
2750
-
],
2751
-
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
2752
-
"keywords": [
2753
-
"csprng",
2754
-
"polyfill",
2755
-
"pseudorandom",
2756
-
"random"
2757
-
],
2758
-
"support": {
2759
-
"email": "info@paragonie.com",
2760
-
"issues": "https://github.com/paragonie/random_compat/issues",
2761
-
"source": "https://github.com/paragonie/random_compat"
2762
-
},
2763
-
"time": "2020-10-15T08:29:30+00:00"
2764
-
},
2765
-
{
2766
-
"name": "phpoption/phpoption",
2767
-
"version": "1.9.4",
2768
-
"source": {
2769
-
"type": "git",
2770
-
"url": "https://github.com/schmittjoh/php-option.git",
2771
-
"reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d"
2772
-
},
2773
-
"dist": {
2774
-
"type": "zip",
2775
-
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d",
2776
-
"reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d",
2777
-
"shasum": ""
2778
-
},
2779
-
"require": {
2780
-
"php": "^7.2.5 || ^8.0"
2781
-
},
2782
-
"require-dev": {
2783
-
"bamarni/composer-bin-plugin": "^1.8.2",
2784
-
"phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
2785
-
},
2786
-
"type": "library",
2787
-
"extra": {
2788
-
"bamarni-bin": {
2789
-
"bin-links": true,
2790
-
"forward-command": false
2791
-
},
2792
-
"branch-alias": {
2793
-
"dev-master": "1.9-dev"
2794
-
}
2795
-
},
2796
-
"autoload": {
2797
-
"psr-4": {
2798
-
"PhpOption\\": "src/PhpOption/"
2799
-
}
2800
-
},
2801
-
"notification-url": "https://packagist.org/downloads/",
2802
-
"license": [
2803
-
"Apache-2.0"
2804
-
],
2805
-
"authors": [
2806
-
{
2807
-
"name": "Johannes M. Schmitt",
2808
-
"email": "schmittjoh@gmail.com",
2809
-
"homepage": "https://github.com/schmittjoh"
2810
-
},
2811
-
{
2812
-
"name": "Graham Campbell",
2813
-
"email": "hello@gjcampbell.co.uk",
2814
-
"homepage": "https://github.com/GrahamCampbell"
2815
-
}
2816
-
],
2817
-
"description": "Option Type for PHP",
2818
-
"keywords": [
2819
-
"language",
2820
-
"option",
2821
-
"php",
2822
-
"type"
2823
-
],
2824
-
"support": {
2825
-
"issues": "https://github.com/schmittjoh/php-option/issues",
2826
-
"source": "https://github.com/schmittjoh/php-option/tree/1.9.4"
2827
-
},
2828
-
"funding": [
2829
-
{
2830
-
"url": "https://github.com/GrahamCampbell",
2831
-
"type": "github"
2832
-
},
2833
-
{
2834
-
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
2835
-
"type": "tidelift"
2836
-
}
2837
-
],
2838
-
"time": "2025-08-21T11:53:16+00:00"
2839
-
},
2840
-
{
2841
-
"name": "phpseclib/phpseclib",
2842
-
"version": "3.0.47",
2843
-
"source": {
2844
-
"type": "git",
2845
-
"url": "https://github.com/phpseclib/phpseclib.git",
2846
-
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d"
2847
-
},
2848
-
"dist": {
2849
-
"type": "zip",
2850
-
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d",
2851
-
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d",
2852
-
"shasum": ""
2853
-
},
2854
-
"require": {
2855
-
"paragonie/constant_time_encoding": "^1|^2|^3",
2856
-
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
2857
-
"php": ">=5.6.1"
2858
-
},
2859
-
"require-dev": {
2860
-
"phpunit/phpunit": "*"
2861
-
},
2862
-
"suggest": {
2863
-
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
2864
-
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
2865
-
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
2866
-
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
2867
-
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
2868
-
},
2869
-
"type": "library",
2870
-
"autoload": {
2871
-
"files": [
2872
-
"phpseclib/bootstrap.php"
2873
-
],
2874
-
"psr-4": {
2875
-
"phpseclib3\\": "phpseclib/"
2876
-
}
2877
-
},
2878
-
"notification-url": "https://packagist.org/downloads/",
2879
-
"license": [
2880
-
"MIT"
2881
-
],
2882
-
"authors": [
2883
-
{
2884
-
"name": "Jim Wigginton",
2885
-
"email": "terrafrost@php.net",
2886
-
"role": "Lead Developer"
2887
-
},
2888
-
{
2889
-
"name": "Patrick Monnerat",
2890
-
"email": "pm@datasphere.ch",
2891
-
"role": "Developer"
2892
-
},
2893
-
{
2894
-
"name": "Andreas Fischer",
2895
-
"email": "bantu@phpbb.com",
2896
-
"role": "Developer"
2897
-
},
2898
-
{
2899
-
"name": "Hans-Jรผrgen Petrich",
2900
-
"email": "petrich@tronic-media.com",
2901
-
"role": "Developer"
2902
-
},
2903
-
{
2904
-
"name": "Graham Campbell",
2905
-
"email": "graham@alt-three.com",
2906
-
"role": "Developer"
2907
-
}
2908
-
],
2909
-
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
2910
-
"homepage": "http://phpseclib.sourceforge.net",
2911
-
"keywords": [
2912
-
"BigInteger",
2913
-
"aes",
2914
-
"asn.1",
2915
-
"asn1",
2916
-
"blowfish",
2917
-
"crypto",
2918
-
"cryptography",
2919
-
"encryption",
2920
-
"rsa",
2921
-
"security",
2922
-
"sftp",
2923
-
"signature",
2924
-
"signing",
2925
-
"ssh",
2926
-
"twofish",
2927
-
"x.509",
2928
-
"x509"
2929
-
],
2930
-
"support": {
2931
-
"issues": "https://github.com/phpseclib/phpseclib/issues",
2932
-
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.47"
2933
-
},
2934
-
"funding": [
2935
-
{
2936
-
"url": "https://github.com/terrafrost",
2937
-
"type": "github"
2938
-
},
2939
-
{
2940
-
"url": "https://www.patreon.com/phpseclib",
2941
-
"type": "patreon"
2942
-
},
2943
-
{
2944
-
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
2945
-
"type": "tidelift"
2946
-
}
2947
-
],
2948
-
"time": "2025-10-06T01:07:24+00:00"
2949
-
},
2950
-
{
2951
-
"name": "psr/clock",
2952
-
"version": "1.0.0",
2953
-
"source": {
2954
-
"type": "git",
2955
-
"url": "https://github.com/php-fig/clock.git",
2956
-
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
2957
-
},
2958
-
"dist": {
2959
-
"type": "zip",
2960
-
"url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
2961
-
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
2962
-
"shasum": ""
2963
-
},
2964
-
"require": {
2965
-
"php": "^7.0 || ^8.0"
2966
-
},
2967
-
"type": "library",
2968
-
"autoload": {
2969
-
"psr-4": {
2970
-
"Psr\\Clock\\": "src/"
2971
-
}
2972
-
},
2973
-
"notification-url": "https://packagist.org/downloads/",
2974
-
"license": [
2975
-
"MIT"
2976
-
],
2977
-
"authors": [
2978
-
{
2979
-
"name": "PHP-FIG",
2980
-
"homepage": "https://www.php-fig.org/"
2981
-
}
2982
-
],
2983
-
"description": "Common interface for reading the clock.",
2984
-
"homepage": "https://github.com/php-fig/clock",
2985
-
"keywords": [
2986
-
"clock",
2987
-
"now",
2988
-
"psr",
2989
-
"psr-20",
2990
-
"time"
2991
-
],
2992
-
"support": {
2993
-
"issues": "https://github.com/php-fig/clock/issues",
2994
-
"source": "https://github.com/php-fig/clock/tree/1.0.0"
2995
-
},
2996
-
"time": "2022-11-25T14:36:26+00:00"
2997
-
},
2998
-
{
2999
-
"name": "psr/container",
3000
-
"version": "2.0.2",
3001
-
"source": {
3002
-
"type": "git",
3003
-
"url": "https://github.com/php-fig/container.git",
3004
-
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
3005
-
},
3006
-
"dist": {
3007
-
"type": "zip",
3008
-
"url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
3009
-
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
3010
-
"shasum": ""
3011
-
},
3012
-
"require": {
3013
-
"php": ">=7.4.0"
3014
-
},
3015
-
"type": "library",
3016
-
"extra": {
3017
-
"branch-alias": {
3018
-
"dev-master": "2.0.x-dev"
3019
-
}
3020
-
},
3021
-
"autoload": {
3022
-
"psr-4": {
3023
-
"Psr\\Container\\": "src/"
3024
-
}
3025
-
},
3026
-
"notification-url": "https://packagist.org/downloads/",
3027
-
"license": [
3028
-
"MIT"
3029
-
],
3030
-
"authors": [
3031
-
{
3032
-
"name": "PHP-FIG",
3033
-
"homepage": "https://www.php-fig.org/"
3034
-
}
3035
-
],
3036
-
"description": "Common Container Interface (PHP FIG PSR-11)",
3037
-
"homepage": "https://github.com/php-fig/container",
3038
-
"keywords": [
3039
-
"PSR-11",
3040
-
"container",
3041
-
"container-interface",
3042
-
"container-interop",
3043
-
"psr"
3044
-
],
3045
-
"support": {
3046
-
"issues": "https://github.com/php-fig/container/issues",
3047
-
"source": "https://github.com/php-fig/container/tree/2.0.2"
3048
-
},
3049
-
"time": "2021-11-05T16:47:00+00:00"
3050
-
},
3051
-
{
3052
-
"name": "psr/event-dispatcher",
3053
-
"version": "1.0.0",
3054
-
"source": {
3055
-
"type": "git",
3056
-
"url": "https://github.com/php-fig/event-dispatcher.git",
3057
-
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
3058
-
},
3059
-
"dist": {
3060
-
"type": "zip",
3061
-
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
3062
-
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
3063
-
"shasum": ""
3064
-
},
3065
-
"require": {
3066
-
"php": ">=7.2.0"
3067
-
},
3068
-
"type": "library",
3069
-
"extra": {
3070
-
"branch-alias": {
3071
-
"dev-master": "1.0.x-dev"
3072
-
}
3073
-
},
3074
-
"autoload": {
3075
-
"psr-4": {
3076
-
"Psr\\EventDispatcher\\": "src/"
3077
-
}
3078
-
},
3079
-
"notification-url": "https://packagist.org/downloads/",
3080
-
"license": [
3081
-
"MIT"
3082
-
],
3083
-
"authors": [
3084
-
{
3085
-
"name": "PHP-FIG",
3086
-
"homepage": "http://www.php-fig.org/"
3087
-
}
3088
-
],
3089
-
"description": "Standard interfaces for event handling.",
3090
-
"keywords": [
3091
-
"events",
3092
-
"psr",
3093
-
"psr-14"
3094
-
],
3095
-
"support": {
3096
-
"issues": "https://github.com/php-fig/event-dispatcher/issues",
3097
-
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
3098
-
},
3099
-
"time": "2019-01-08T18:20:26+00:00"
3100
-
},
3101
-
{
3102
-
"name": "psr/http-client",
3103
-
"version": "1.0.3",
3104
-
"source": {
3105
-
"type": "git",
3106
-
"url": "https://github.com/php-fig/http-client.git",
3107
-
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
3108
-
},
3109
-
"dist": {
3110
-
"type": "zip",
3111
-
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
3112
-
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
3113
-
"shasum": ""
3114
-
},
3115
-
"require": {
3116
-
"php": "^7.0 || ^8.0",
3117
-
"psr/http-message": "^1.0 || ^2.0"
3118
-
},
3119
-
"type": "library",
3120
-
"extra": {
3121
-
"branch-alias": {
3122
-
"dev-master": "1.0.x-dev"
3123
-
}
3124
-
},
3125
-
"autoload": {
3126
-
"psr-4": {
3127
-
"Psr\\Http\\Client\\": "src/"
3128
-
}
3129
-
},
3130
-
"notification-url": "https://packagist.org/downloads/",
3131
-
"license": [
3132
-
"MIT"
3133
-
],
3134
-
"authors": [
3135
-
{
3136
-
"name": "PHP-FIG",
3137
-
"homepage": "https://www.php-fig.org/"
3138
-
}
3139
-
],
3140
-
"description": "Common interface for HTTP clients",
3141
-
"homepage": "https://github.com/php-fig/http-client",
3142
-
"keywords": [
3143
-
"http",
3144
-
"http-client",
3145
-
"psr",
3146
-
"psr-18"
3147
-
],
3148
-
"support": {
3149
-
"source": "https://github.com/php-fig/http-client"
3150
-
},
3151
-
"time": "2023-09-23T14:17:50+00:00"
3152
-
},
3153
-
{
3154
-
"name": "psr/http-factory",
3155
-
"version": "1.1.0",
3156
-
"source": {
3157
-
"type": "git",
3158
-
"url": "https://github.com/php-fig/http-factory.git",
3159
-
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
3160
-
},
3161
-
"dist": {
3162
-
"type": "zip",
3163
-
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
3164
-
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
3165
-
"shasum": ""
3166
-
},
3167
-
"require": {
3168
-
"php": ">=7.1",
3169
-
"psr/http-message": "^1.0 || ^2.0"
3170
-
},
3171
-
"type": "library",
3172
-
"extra": {
3173
-
"branch-alias": {
3174
-
"dev-master": "1.0.x-dev"
3175
-
}
3176
-
},
3177
-
"autoload": {
3178
-
"psr-4": {
3179
-
"Psr\\Http\\Message\\": "src/"
3180
-
}
3181
-
},
3182
-
"notification-url": "https://packagist.org/downloads/",
3183
-
"license": [
3184
-
"MIT"
3185
-
],
3186
-
"authors": [
3187
-
{
3188
-
"name": "PHP-FIG",
3189
-
"homepage": "https://www.php-fig.org/"
3190
-
}
3191
-
],
3192
-
"description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
3193
-
"keywords": [
3194
-
"factory",
3195
-
"http",
3196
-
"message",
3197
-
"psr",
3198
-
"psr-17",
3199
-
"psr-7",
3200
-
"request",
3201
-
"response"
3202
-
],
3203
-
"support": {
3204
-
"source": "https://github.com/php-fig/http-factory"
3205
-
},
3206
-
"time": "2024-04-15T12:06:14+00:00"
3207
-
},
3208
-
{
3209
-
"name": "psr/http-message",
3210
-
"version": "2.0",
3211
-
"source": {
3212
-
"type": "git",
3213
-
"url": "https://github.com/php-fig/http-message.git",
3214
-
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
3215
-
},
3216
-
"dist": {
3217
-
"type": "zip",
3218
-
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
3219
-
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
3220
-
"shasum": ""
3221
-
},
3222
-
"require": {
3223
-
"php": "^7.2 || ^8.0"
3224
-
},
3225
-
"type": "library",
3226
-
"extra": {
3227
-
"branch-alias": {
3228
-
"dev-master": "2.0.x-dev"
3229
-
}
3230
-
},
3231
-
"autoload": {
3232
-
"psr-4": {
3233
-
"Psr\\Http\\Message\\": "src/"
3234
-
}
3235
-
},
3236
-
"notification-url": "https://packagist.org/downloads/",
3237
-
"license": [
3238
-
"MIT"
3239
-
],
3240
-
"authors": [
3241
-
{
3242
-
"name": "PHP-FIG",
3243
-
"homepage": "https://www.php-fig.org/"
3244
-
}
3245
-
],
3246
-
"description": "Common interface for HTTP messages",
3247
-
"homepage": "https://github.com/php-fig/http-message",
3248
-
"keywords": [
3249
-
"http",
3250
-
"http-message",
3251
-
"psr",
3252
-
"psr-7",
3253
-
"request",
3254
-
"response"
3255
-
],
3256
-
"support": {
3257
-
"source": "https://github.com/php-fig/http-message/tree/2.0"
3258
-
},
3259
-
"time": "2023-04-04T09:54:51+00:00"
3260
-
},
3261
-
{
3262
-
"name": "psr/log",
3263
-
"version": "3.0.2",
3264
-
"source": {
3265
-
"type": "git",
3266
-
"url": "https://github.com/php-fig/log.git",
3267
-
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
3268
-
},
3269
-
"dist": {
3270
-
"type": "zip",
3271
-
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
3272
-
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
3273
-
"shasum": ""
3274
-
},
3275
-
"require": {
3276
-
"php": ">=8.0.0"
3277
-
},
3278
-
"type": "library",
3279
-
"extra": {
3280
-
"branch-alias": {
3281
-
"dev-master": "3.x-dev"
3282
-
}
3283
-
},
3284
-
"autoload": {
3285
-
"psr-4": {
3286
-
"Psr\\Log\\": "src"
3287
-
}
3288
-
},
3289
-
"notification-url": "https://packagist.org/downloads/",
3290
-
"license": [
3291
-
"MIT"
3292
-
],
3293
-
"authors": [
3294
-
{
3295
-
"name": "PHP-FIG",
3296
-
"homepage": "https://www.php-fig.org/"
3297
-
}
3298
-
],
3299
-
"description": "Common interface for logging libraries",
3300
-
"homepage": "https://github.com/php-fig/log",
3301
-
"keywords": [
3302
-
"log",
3303
-
"psr",
3304
-
"psr-3"
3305
-
],
3306
-
"support": {
3307
-
"source": "https://github.com/php-fig/log/tree/3.0.2"
3308
-
},
3309
-
"time": "2024-09-11T13:17:53+00:00"
3310
-
},
3311
-
{
3312
-
"name": "psr/simple-cache",
3313
-
"version": "3.0.0",
3314
-
"source": {
3315
-
"type": "git",
3316
-
"url": "https://github.com/php-fig/simple-cache.git",
3317
-
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
3318
-
},
3319
-
"dist": {
3320
-
"type": "zip",
3321
-
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
3322
-
"reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
3323
-
"shasum": ""
3324
-
},
3325
-
"require": {
3326
-
"php": ">=8.0.0"
3327
-
},
3328
-
"type": "library",
3329
-
"extra": {
3330
-
"branch-alias": {
3331
-
"dev-master": "3.0.x-dev"
3332
-
}
3333
-
},
3334
-
"autoload": {
3335
-
"psr-4": {
3336
-
"Psr\\SimpleCache\\": "src/"
3337
-
}
3338
-
},
3339
-
"notification-url": "https://packagist.org/downloads/",
3340
-
"license": [
3341
-
"MIT"
3342
-
],
3343
-
"authors": [
3344
-
{
3345
-
"name": "PHP-FIG",
3346
-
"homepage": "https://www.php-fig.org/"
3347
-
}
3348
-
],
3349
-
"description": "Common interfaces for simple caching",
3350
-
"keywords": [
3351
-
"cache",
3352
-
"caching",
3353
-
"psr",
3354
-
"psr-16",
3355
-
"simple-cache"
3356
-
],
3357
-
"support": {
3358
-
"source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
3359
-
},
3360
-
"time": "2021-10-29T13:26:27+00:00"
3361
-
},
3362
-
{
3363
-
"name": "ralouphie/getallheaders",
3364
-
"version": "3.0.3",
3365
-
"source": {
3366
-
"type": "git",
3367
-
"url": "https://github.com/ralouphie/getallheaders.git",
3368
-
"reference": "120b605dfeb996808c31b6477290a714d356e822"
3369
-
},
3370
-
"dist": {
3371
-
"type": "zip",
3372
-
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
3373
-
"reference": "120b605dfeb996808c31b6477290a714d356e822",
3374
-
"shasum": ""
3375
-
},
3376
-
"require": {
3377
-
"php": ">=5.6"
3378
-
},
3379
-
"require-dev": {
3380
-
"php-coveralls/php-coveralls": "^2.1",
3381
-
"phpunit/phpunit": "^5 || ^6.5"
3382
-
},
3383
-
"type": "library",
3384
-
"autoload": {
3385
-
"files": [
3386
-
"src/getallheaders.php"
3387
-
]
3388
-
},
3389
-
"notification-url": "https://packagist.org/downloads/",
3390
-
"license": [
3391
-
"MIT"
3392
-
],
3393
-
"authors": [
3394
-
{
3395
-
"name": "Ralph Khattar",
3396
-
"email": "ralph.khattar@gmail.com"
3397
-
}
3398
-
],
3399
-
"description": "A polyfill for getallheaders.",
3400
-
"support": {
3401
-
"issues": "https://github.com/ralouphie/getallheaders/issues",
3402
-
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
3403
-
},
3404
-
"time": "2019-03-08T08:55:37+00:00"
3405
-
},
3406
-
{
3407
-
"name": "ramsey/collection",
3408
-
"version": "2.1.1",
3409
-
"source": {
3410
-
"type": "git",
3411
-
"url": "https://github.com/ramsey/collection.git",
3412
-
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
3413
-
},
3414
-
"dist": {
3415
-
"type": "zip",
3416
-
"url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
3417
-
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
3418
-
"shasum": ""
3419
-
},
3420
-
"require": {
3421
-
"php": "^8.1"
3422
-
},
3423
-
"require-dev": {
3424
-
"captainhook/plugin-composer": "^5.3",
3425
-
"ergebnis/composer-normalize": "^2.45",
3426
-
"fakerphp/faker": "^1.24",
3427
-
"hamcrest/hamcrest-php": "^2.0",
3428
-
"jangregor/phpstan-prophecy": "^2.1",
3429
-
"mockery/mockery": "^1.6",
3430
-
"php-parallel-lint/php-console-highlighter": "^1.0",
3431
-
"php-parallel-lint/php-parallel-lint": "^1.4",
3432
-
"phpspec/prophecy-phpunit": "^2.3",
3433
-
"phpstan/extension-installer": "^1.4",
3434
-
"phpstan/phpstan": "^2.1",
3435
-
"phpstan/phpstan-mockery": "^2.0",
3436
-
"phpstan/phpstan-phpunit": "^2.0",
3437
-
"phpunit/phpunit": "^10.5",
3438
-
"ramsey/coding-standard": "^2.3",
3439
-
"ramsey/conventional-commits": "^1.6",
3440
-
"roave/security-advisories": "dev-latest"
3441
-
},
3442
-
"type": "library",
3443
-
"extra": {
3444
-
"captainhook": {
3445
-
"force-install": true
3446
-
},
3447
-
"ramsey/conventional-commits": {
3448
-
"configFile": "conventional-commits.json"
3449
-
}
3450
-
},
3451
-
"autoload": {
3452
-
"psr-4": {
3453
-
"Ramsey\\Collection\\": "src/"
3454
-
}
3455
-
},
3456
-
"notification-url": "https://packagist.org/downloads/",
3457
-
"license": [
3458
-
"MIT"
3459
-
],
3460
-
"authors": [
3461
-
{
3462
-
"name": "Ben Ramsey",
3463
-
"email": "ben@benramsey.com",
3464
-
"homepage": "https://benramsey.com"
3465
-
}
3466
-
],
3467
-
"description": "A PHP library for representing and manipulating collections.",
3468
-
"keywords": [
3469
-
"array",
3470
-
"collection",
3471
-
"hash",
3472
-
"map",
3473
-
"queue",
3474
-
"set"
3475
-
],
3476
-
"support": {
3477
-
"issues": "https://github.com/ramsey/collection/issues",
3478
-
"source": "https://github.com/ramsey/collection/tree/2.1.1"
3479
-
},
3480
-
"time": "2025-03-22T05:38:12+00:00"
3481
-
},
3482
-
{
3483
-
"name": "ramsey/uuid",
3484
-
"version": "4.9.1",
3485
-
"source": {
3486
-
"type": "git",
3487
-
"url": "https://github.com/ramsey/uuid.git",
3488
-
"reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440"
3489
-
},
3490
-
"dist": {
3491
-
"type": "zip",
3492
-
"url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440",
3493
-
"reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440",
3494
-
"shasum": ""
3495
-
},
3496
-
"require": {
3497
-
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
3498
-
"php": "^8.0",
3499
-
"ramsey/collection": "^1.2 || ^2.0"
3500
-
},
3501
-
"replace": {
3502
-
"rhumsaa/uuid": "self.version"
3503
-
},
3504
-
"require-dev": {
3505
-
"captainhook/captainhook": "^5.25",
3506
-
"captainhook/plugin-composer": "^5.3",
3507
-
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
3508
-
"ergebnis/composer-normalize": "^2.47",
3509
-
"mockery/mockery": "^1.6",
3510
-
"paragonie/random-lib": "^2",
3511
-
"php-mock/php-mock": "^2.6",
3512
-
"php-mock/php-mock-mockery": "^1.5",
3513
-
"php-parallel-lint/php-parallel-lint": "^1.4.0",
3514
-
"phpbench/phpbench": "^1.2.14",
3515
-
"phpstan/extension-installer": "^1.4",
3516
-
"phpstan/phpstan": "^2.1",
3517
-
"phpstan/phpstan-mockery": "^2.0",
3518
-
"phpstan/phpstan-phpunit": "^2.0",
3519
-
"phpunit/phpunit": "^9.6",
3520
-
"slevomat/coding-standard": "^8.18",
3521
-
"squizlabs/php_codesniffer": "^3.13"
3522
-
},
3523
-
"suggest": {
3524
-
"ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
3525
-
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
3526
-
"ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
3527
-
"paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
3528
-
"ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
3529
-
},
3530
-
"type": "library",
3531
-
"extra": {
3532
-
"captainhook": {
3533
-
"force-install": true
3534
-
}
3535
-
},
3536
-
"autoload": {
3537
-
"files": [
3538
-
"src/functions.php"
3539
-
],
3540
-
"psr-4": {
3541
-
"Ramsey\\Uuid\\": "src/"
3542
-
}
3543
-
},
3544
-
"notification-url": "https://packagist.org/downloads/",
3545
-
"license": [
3546
-
"MIT"
3547
-
],
3548
-
"description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
3549
-
"keywords": [
3550
-
"guid",
3551
-
"identifier",
3552
-
"uuid"
3553
-
],
3554
-
"support": {
3555
-
"issues": "https://github.com/ramsey/uuid/issues",
3556
-
"source": "https://github.com/ramsey/uuid/tree/4.9.1"
3557
-
},
3558
-
"time": "2025-09-04T20:59:21+00:00"
3559
-
},
3560
-
{
3561
-
"name": "ratchet/pawl",
3562
-
"version": "v0.4.3",
3563
-
"source": {
3564
-
"type": "git",
3565
-
"url": "https://github.com/ratchetphp/Pawl.git",
3566
-
"reference": "2c582373c78271de32cb04c755c4c0db7e09c9c0"
3567
-
},
3568
-
"dist": {
3569
-
"type": "zip",
3570
-
"url": "https://api.github.com/repos/ratchetphp/Pawl/zipball/2c582373c78271de32cb04c755c4c0db7e09c9c0",
3571
-
"reference": "2c582373c78271de32cb04c755c4c0db7e09c9c0",
3572
-
"shasum": ""
3573
-
},
3574
-
"require": {
3575
-
"evenement/evenement": "^3.0 || ^2.0",
3576
-
"guzzlehttp/psr7": "^2.0",
3577
-
"php": ">=7.4",
3578
-
"ratchet/rfc6455": "^0.3.1 || ^0.4.0",
3579
-
"react/socket": "^1.9"
3580
-
},
3581
-
"require-dev": {
3582
-
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8"
3583
-
},
3584
-
"suggest": {
3585
-
"reactivex/rxphp": "~2.0"
3586
-
},
3587
-
"type": "library",
3588
-
"autoload": {
3589
-
"files": [
3590
-
"src/functions_include.php"
3591
-
],
3592
-
"psr-4": {
3593
-
"Ratchet\\Client\\": "src"
3594
-
}
3595
-
},
3596
-
"notification-url": "https://packagist.org/downloads/",
3597
-
"license": [
3598
-
"MIT"
3599
-
],
3600
-
"description": "Asynchronous WebSocket client",
3601
-
"keywords": [
3602
-
"Ratchet",
3603
-
"async",
3604
-
"client",
3605
-
"websocket",
3606
-
"websocket client"
3607
-
],
3608
-
"support": {
3609
-
"issues": "https://github.com/ratchetphp/Pawl/issues",
3610
-
"source": "https://github.com/ratchetphp/Pawl/tree/v0.4.3"
3611
-
},
3612
-
"time": "2025-03-19T16:47:38+00:00"
3613
-
},
3614
-
{
3615
-
"name": "ratchet/rfc6455",
3616
-
"version": "v0.4.0",
3617
-
"source": {
3618
-
"type": "git",
3619
-
"url": "https://github.com/ratchetphp/RFC6455.git",
3620
-
"reference": "859d95f85dda0912c6d5b936d036d044e3af47ef"
3621
-
},
3622
-
"dist": {
3623
-
"type": "zip",
3624
-
"url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/859d95f85dda0912c6d5b936d036d044e3af47ef",
3625
-
"reference": "859d95f85dda0912c6d5b936d036d044e3af47ef",
3626
-
"shasum": ""
3627
-
},
3628
-
"require": {
3629
-
"php": ">=7.4",
3630
-
"psr/http-factory-implementation": "^1.0",
3631
-
"symfony/polyfill-php80": "^1.15"
3632
-
},
3633
-
"require-dev": {
3634
-
"guzzlehttp/psr7": "^2.7",
3635
-
"phpunit/phpunit": "^9.5",
3636
-
"react/socket": "^1.3"
3637
-
},
3638
-
"type": "library",
3639
-
"autoload": {
3640
-
"psr-4": {
3641
-
"Ratchet\\RFC6455\\": "src"
3642
-
}
3643
-
},
3644
-
"notification-url": "https://packagist.org/downloads/",
3645
-
"license": [
3646
-
"MIT"
3647
-
],
3648
-
"authors": [
3649
-
{
3650
-
"name": "Chris Boden",
3651
-
"email": "cboden@gmail.com",
3652
-
"role": "Developer"
3653
-
},
3654
-
{
3655
-
"name": "Matt Bonneau",
3656
-
"role": "Developer"
3657
-
}
3658
-
],
3659
-
"description": "RFC6455 WebSocket protocol handler",
3660
-
"homepage": "http://socketo.me",
3661
-
"keywords": [
3662
-
"WebSockets",
3663
-
"rfc6455",
3664
-
"websocket"
3665
-
],
3666
-
"support": {
3667
-
"chat": "https://gitter.im/reactphp/reactphp",
3668
-
"issues": "https://github.com/ratchetphp/RFC6455/issues",
3669
-
"source": "https://github.com/ratchetphp/RFC6455/tree/v0.4.0"
3670
-
},
3671
-
"time": "2025-02-24T01:18:22+00:00"
3672
-
},
3673
-
{
3674
-
"name": "react/cache",
3675
-
"version": "v1.2.0",
3676
-
"source": {
3677
-
"type": "git",
3678
-
"url": "https://github.com/reactphp/cache.git",
3679
-
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
3680
-
},
3681
-
"dist": {
3682
-
"type": "zip",
3683
-
"url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
3684
-
"reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
3685
-
"shasum": ""
3686
-
},
3687
-
"require": {
3688
-
"php": ">=5.3.0",
3689
-
"react/promise": "^3.0 || ^2.0 || ^1.1"
3690
-
},
3691
-
"require-dev": {
3692
-
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
3693
-
},
3694
-
"type": "library",
3695
-
"autoload": {
3696
-
"psr-4": {
3697
-
"React\\Cache\\": "src/"
3698
-
}
3699
-
},
3700
-
"notification-url": "https://packagist.org/downloads/",
3701
-
"license": [
3702
-
"MIT"
3703
-
],
3704
-
"authors": [
3705
-
{
3706
-
"name": "Christian Lรผck",
3707
-
"email": "christian@clue.engineering",
3708
-
"homepage": "https://clue.engineering/"
3709
-
},
3710
-
{
3711
-
"name": "Cees-Jan Kiewiet",
3712
-
"email": "reactphp@ceesjankiewiet.nl",
3713
-
"homepage": "https://wyrihaximus.net/"
3714
-
},
3715
-
{
3716
-
"name": "Jan Sorgalla",
3717
-
"email": "jsorgalla@gmail.com",
3718
-
"homepage": "https://sorgalla.com/"
3719
-
},
3720
-
{
3721
-
"name": "Chris Boden",
3722
-
"email": "cboden@gmail.com",
3723
-
"homepage": "https://cboden.dev/"
3724
-
}
3725
-
],
3726
-
"description": "Async, Promise-based cache interface for ReactPHP",
3727
-
"keywords": [
3728
-
"cache",
3729
-
"caching",
3730
-
"promise",
3731
-
"reactphp"
3732
-
],
3733
-
"support": {
3734
-
"issues": "https://github.com/reactphp/cache/issues",
3735
-
"source": "https://github.com/reactphp/cache/tree/v1.2.0"
3736
-
},
3737
-
"funding": [
3738
-
{
3739
-
"url": "https://opencollective.com/reactphp",
3740
-
"type": "open_collective"
3741
-
}
3742
-
],
3743
-
"time": "2022-11-30T15:59:55+00:00"
3744
-
},
3745
-
{
3746
-
"name": "react/dns",
3747
-
"version": "v1.13.0",
3748
-
"source": {
3749
-
"type": "git",
3750
-
"url": "https://github.com/reactphp/dns.git",
3751
-
"reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5"
3752
-
},
3753
-
"dist": {
3754
-
"type": "zip",
3755
-
"url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5",
3756
-
"reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5",
3757
-
"shasum": ""
3758
-
},
3759
-
"require": {
3760
-
"php": ">=5.3.0",
3761
-
"react/cache": "^1.0 || ^0.6 || ^0.5",
3762
-
"react/event-loop": "^1.2",
3763
-
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
3764
-
},
3765
-
"require-dev": {
3766
-
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
3767
-
"react/async": "^4.3 || ^3 || ^2",
3768
-
"react/promise-timer": "^1.11"
3769
-
},
3770
-
"type": "library",
3771
-
"autoload": {
3772
-
"psr-4": {
3773
-
"React\\Dns\\": "src/"
3774
-
}
3775
-
},
3776
-
"notification-url": "https://packagist.org/downloads/",
3777
-
"license": [
3778
-
"MIT"
3779
-
],
3780
-
"authors": [
3781
-
{
3782
-
"name": "Christian Lรผck",
3783
-
"email": "christian@clue.engineering",
3784
-
"homepage": "https://clue.engineering/"
3785
-
},
3786
-
{
3787
-
"name": "Cees-Jan Kiewiet",
3788
-
"email": "reactphp@ceesjankiewiet.nl",
3789
-
"homepage": "https://wyrihaximus.net/"
3790
-
},
3791
-
{
3792
-
"name": "Jan Sorgalla",
3793
-
"email": "jsorgalla@gmail.com",
3794
-
"homepage": "https://sorgalla.com/"
3795
-
},
3796
-
{
3797
-
"name": "Chris Boden",
3798
-
"email": "cboden@gmail.com",
3799
-
"homepage": "https://cboden.dev/"
3800
-
}
3801
-
],
3802
-
"description": "Async DNS resolver for ReactPHP",
3803
-
"keywords": [
3804
-
"async",
3805
-
"dns",
3806
-
"dns-resolver",
3807
-
"reactphp"
3808
-
],
3809
-
"support": {
3810
-
"issues": "https://github.com/reactphp/dns/issues",
3811
-
"source": "https://github.com/reactphp/dns/tree/v1.13.0"
3812
-
},
3813
-
"funding": [
3814
-
{
3815
-
"url": "https://opencollective.com/reactphp",
3816
-
"type": "open_collective"
3817
-
}
3818
-
],
3819
-
"time": "2024-06-13T14:18:03+00:00"
3820
-
},
3821
-
{
3822
-
"name": "react/event-loop",
3823
-
"version": "v1.5.0",
3824
-
"source": {
3825
-
"type": "git",
3826
-
"url": "https://github.com/reactphp/event-loop.git",
3827
-
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354"
3828
-
},
3829
-
"dist": {
3830
-
"type": "zip",
3831
-
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
3832
-
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
3833
-
"shasum": ""
3834
-
},
3835
-
"require": {
3836
-
"php": ">=5.3.0"
3837
-
},
3838
-
"require-dev": {
3839
-
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
3840
-
},
3841
-
"suggest": {
3842
-
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
3843
-
},
3844
-
"type": "library",
3845
-
"autoload": {
3846
-
"psr-4": {
3847
-
"React\\EventLoop\\": "src/"
3848
-
}
3849
-
},
3850
-
"notification-url": "https://packagist.org/downloads/",
3851
-
"license": [
3852
-
"MIT"
3853
-
],
3854
-
"authors": [
3855
-
{
3856
-
"name": "Christian Lรผck",
3857
-
"email": "christian@clue.engineering",
3858
-
"homepage": "https://clue.engineering/"
3859
-
},
3860
-
{
3861
-
"name": "Cees-Jan Kiewiet",
3862
-
"email": "reactphp@ceesjankiewiet.nl",
3863
-
"homepage": "https://wyrihaximus.net/"
3864
-
},
3865
-
{
3866
-
"name": "Jan Sorgalla",
3867
-
"email": "jsorgalla@gmail.com",
3868
-
"homepage": "https://sorgalla.com/"
3869
-
},
3870
-
{
3871
-
"name": "Chris Boden",
3872
-
"email": "cboden@gmail.com",
3873
-
"homepage": "https://cboden.dev/"
3874
-
}
3875
-
],
3876
-
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
3877
-
"keywords": [
3878
-
"asynchronous",
3879
-
"event-loop"
3880
-
],
3881
-
"support": {
3882
-
"issues": "https://github.com/reactphp/event-loop/issues",
3883
-
"source": "https://github.com/reactphp/event-loop/tree/v1.5.0"
3884
-
},
3885
-
"funding": [
3886
-
{
3887
-
"url": "https://opencollective.com/reactphp",
3888
-
"type": "open_collective"
3889
-
}
3890
-
],
3891
-
"time": "2023-11-13T13:48:05+00:00"
3892
-
},
3893
-
{
3894
-
"name": "react/promise",
3895
-
"version": "v3.3.0",
3896
-
"source": {
3897
-
"type": "git",
3898
-
"url": "https://github.com/reactphp/promise.git",
3899
-
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
3900
-
},
3901
-
"dist": {
3902
-
"type": "zip",
3903
-
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
3904
-
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
3905
-
"shasum": ""
3906
-
},
3907
-
"require": {
3908
-
"php": ">=7.1.0"
3909
-
},
3910
-
"require-dev": {
3911
-
"phpstan/phpstan": "1.12.28 || 1.4.10",
3912
-
"phpunit/phpunit": "^9.6 || ^7.5"
3913
-
},
3914
-
"type": "library",
3915
-
"autoload": {
3916
-
"files": [
3917
-
"src/functions_include.php"
3918
-
],
3919
-
"psr-4": {
3920
-
"React\\Promise\\": "src/"
3921
-
}
3922
-
},
3923
-
"notification-url": "https://packagist.org/downloads/",
3924
-
"license": [
3925
-
"MIT"
3926
-
],
3927
-
"authors": [
3928
-
{
3929
-
"name": "Jan Sorgalla",
3930
-
"email": "jsorgalla@gmail.com",
3931
-
"homepage": "https://sorgalla.com/"
3932
-
},
3933
-
{
3934
-
"name": "Christian Lรผck",
3935
-
"email": "christian@clue.engineering",
3936
-
"homepage": "https://clue.engineering/"
3937
-
},
3938
-
{
3939
-
"name": "Cees-Jan Kiewiet",
3940
-
"email": "reactphp@ceesjankiewiet.nl",
3941
-
"homepage": "https://wyrihaximus.net/"
3942
-
},
3943
-
{
3944
-
"name": "Chris Boden",
3945
-
"email": "cboden@gmail.com",
3946
-
"homepage": "https://cboden.dev/"
3947
-
}
3948
-
],
3949
-
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
3950
-
"keywords": [
3951
-
"promise",
3952
-
"promises"
3953
-
],
3954
-
"support": {
3955
-
"issues": "https://github.com/reactphp/promise/issues",
3956
-
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
3957
-
},
3958
-
"funding": [
3959
-
{
3960
-
"url": "https://opencollective.com/reactphp",
3961
-
"type": "open_collective"
3962
-
}
3963
-
],
3964
-
"time": "2025-08-19T18:57:03+00:00"
3965
-
},
3966
-
{
3967
-
"name": "react/socket",
3968
-
"version": "v1.16.0",
3969
-
"source": {
3970
-
"type": "git",
3971
-
"url": "https://github.com/reactphp/socket.git",
3972
-
"reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1"
3973
-
},
3974
-
"dist": {
3975
-
"type": "zip",
3976
-
"url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1",
3977
-
"reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1",
3978
-
"shasum": ""
3979
-
},
3980
-
"require": {
3981
-
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
3982
-
"php": ">=5.3.0",
3983
-
"react/dns": "^1.13",
3984
-
"react/event-loop": "^1.2",
3985
-
"react/promise": "^3.2 || ^2.6 || ^1.2.1",
3986
-
"react/stream": "^1.4"
3987
-
},
3988
-
"require-dev": {
3989
-
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
3990
-
"react/async": "^4.3 || ^3.3 || ^2",
3991
-
"react/promise-stream": "^1.4",
3992
-
"react/promise-timer": "^1.11"
3993
-
},
3994
-
"type": "library",
3995
-
"autoload": {
3996
-
"psr-4": {
3997
-
"React\\Socket\\": "src/"
3998
-
}
3999
-
},
4000
-
"notification-url": "https://packagist.org/downloads/",
4001
-
"license": [
4002
-
"MIT"
4003
-
],
4004
-
"authors": [
4005
-
{
4006
-
"name": "Christian Lรผck",
4007
-
"email": "christian@clue.engineering",
4008
-
"homepage": "https://clue.engineering/"
4009
-
},
4010
-
{
4011
-
"name": "Cees-Jan Kiewiet",
4012
-
"email": "reactphp@ceesjankiewiet.nl",
4013
-
"homepage": "https://wyrihaximus.net/"
4014
-
},
4015
-
{
4016
-
"name": "Jan Sorgalla",
4017
-
"email": "jsorgalla@gmail.com",
4018
-
"homepage": "https://sorgalla.com/"
4019
-
},
4020
-
{
4021
-
"name": "Chris Boden",
4022
-
"email": "cboden@gmail.com",
4023
-
"homepage": "https://cboden.dev/"
4024
-
}
4025
-
],
4026
-
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
4027
-
"keywords": [
4028
-
"Connection",
4029
-
"Socket",
4030
-
"async",
4031
-
"reactphp",
4032
-
"stream"
4033
-
],
4034
-
"support": {
4035
-
"issues": "https://github.com/reactphp/socket/issues",
4036
-
"source": "https://github.com/reactphp/socket/tree/v1.16.0"
4037
-
},
4038
-
"funding": [
4039
-
{
4040
-
"url": "https://opencollective.com/reactphp",
4041
-
"type": "open_collective"
4042
-
}
4043
-
],
4044
-
"time": "2024-07-26T10:38:09+00:00"
4045
-
},
4046
-
{
4047
-
"name": "react/stream",
4048
-
"version": "v1.4.0",
4049
-
"source": {
4050
-
"type": "git",
4051
-
"url": "https://github.com/reactphp/stream.git",
4052
-
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
4053
-
},
4054
-
"dist": {
4055
-
"type": "zip",
4056
-
"url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
4057
-
"reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
4058
-
"shasum": ""
4059
-
},
4060
-
"require": {
4061
-
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
4062
-
"php": ">=5.3.8",
4063
-
"react/event-loop": "^1.2"
4064
-
},
4065
-
"require-dev": {
4066
-
"clue/stream-filter": "~1.2",
4067
-
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
4068
-
},
4069
-
"type": "library",
4070
-
"autoload": {
4071
-
"psr-4": {
4072
-
"React\\Stream\\": "src/"
4073
-
}
4074
-
},
4075
-
"notification-url": "https://packagist.org/downloads/",
4076
-
"license": [
4077
-
"MIT"
4078
-
],
4079
-
"authors": [
4080
-
{
4081
-
"name": "Christian Lรผck",
4082
-
"email": "christian@clue.engineering",
4083
-
"homepage": "https://clue.engineering/"
4084
-
},
4085
-
{
4086
-
"name": "Cees-Jan Kiewiet",
4087
-
"email": "reactphp@ceesjankiewiet.nl",
4088
-
"homepage": "https://wyrihaximus.net/"
4089
-
},
4090
-
{
4091
-
"name": "Jan Sorgalla",
4092
-
"email": "jsorgalla@gmail.com",
4093
-
"homepage": "https://sorgalla.com/"
4094
-
},
4095
-
{
4096
-
"name": "Chris Boden",
4097
-
"email": "cboden@gmail.com",
4098
-
"homepage": "https://cboden.dev/"
4099
-
}
4100
-
],
4101
-
"description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
4102
-
"keywords": [
4103
-
"event-driven",
4104
-
"io",
4105
-
"non-blocking",
4106
-
"pipe",
4107
-
"reactphp",
4108
-
"readable",
4109
-
"stream",
4110
-
"writable"
4111
-
],
4112
-
"support": {
4113
-
"issues": "https://github.com/reactphp/stream/issues",
4114
-
"source": "https://github.com/reactphp/stream/tree/v1.4.0"
4115
-
},
4116
-
"funding": [
4117
-
{
4118
-
"url": "https://opencollective.com/reactphp",
4119
-
"type": "open_collective"
4120
-
}
4121
-
],
4122
-
"time": "2024-06-11T12:45:25+00:00"
4123
-
},
4124
-
{
4125
-
"name": "revolution/atproto-lexicon-contracts",
4126
-
"version": "1.0.82",
4127
-
"source": {
4128
-
"type": "git",
4129
-
"url": "https://github.com/invokable/atproto-lexicon-contracts.git",
4130
-
"reference": "07110f78d88ce49547d664140b83165ddfb5b5cf"
4131
-
},
4132
-
"dist": {
4133
-
"type": "zip",
4134
-
"url": "https://api.github.com/repos/invokable/atproto-lexicon-contracts/zipball/07110f78d88ce49547d664140b83165ddfb5b5cf",
4135
-
"reference": "07110f78d88ce49547d664140b83165ddfb5b5cf",
4136
-
"shasum": ""
4137
-
},
4138
-
"require": {
4139
-
"php": "^8.2"
4140
-
},
4141
-
"require-dev": {
4142
-
"guzzlehttp/guzzle": "^7.9",
4143
-
"laravel/pint": "^1.22"
4144
-
},
4145
-
"type": "library",
4146
-
"autoload": {
4147
-
"psr-4": {
4148
-
"Revolution\\AtProto\\Lexicon\\": "src/"
4149
-
}
4150
-
},
4151
-
"notification-url": "https://packagist.org/downloads/",
4152
-
"license": [
4153
-
"MIT"
4154
-
],
4155
-
"authors": [
4156
-
{
4157
-
"name": "kawax",
4158
-
"email": "kawaxbiz@gmail.com"
4159
-
}
4160
-
],
4161
-
"description": "Auto generated pure PHP interface and enum",
4162
-
"keywords": [
4163
-
"Lexicon",
4164
-
"atproto",
4165
-
"bluesky",
4166
-
"contracts"
4167
-
],
4168
-
"support": {
4169
-
"source": "https://github.com/invokable/atproto-lexicon-contracts/tree/1.0.82"
4170
-
},
4171
-
"funding": [
4172
-
{
4173
-
"url": "https://github.com/invokable",
4174
-
"type": "github"
4175
-
}
4176
-
],
4177
-
"time": "2025-09-03T04:11:59+00:00"
4178
-
},
4179
-
{
4180
-
"name": "revolution/laravel-bluesky",
4181
-
"version": "1.1.3",
4182
-
"source": {
4183
-
"type": "git",
4184
-
"url": "https://github.com/invokable/laravel-bluesky.git",
4185
-
"reference": "bdee3d69d4b95388696864559e5cd1c506b94b73"
4186
-
},
4187
-
"dist": {
4188
-
"type": "zip",
4189
-
"url": "https://api.github.com/repos/invokable/laravel-bluesky/zipball/bdee3d69d4b95388696864559e5cd1c506b94b73",
4190
-
"reference": "bdee3d69d4b95388696864559e5cd1c506b94b73",
4191
-
"shasum": ""
4192
-
},
4193
-
"require": {
4194
-
"firebase/php-jwt": "^6.10",
4195
-
"guzzlehttp/guzzle": "^7.8",
4196
-
"illuminate/support": "^11.30||^12.0",
4197
-
"laravel/socialite": "^5.16",
4198
-
"php": "^8.2",
4199
-
"phpseclib/phpseclib": "^3.0",
4200
-
"revolution/atproto-lexicon-contracts": "1.0.82",
4201
-
"yocto/yoclib-multibase": "^1.2"
4202
-
},
4203
-
"require-dev": {
4204
-
"laravel/pint": "^1.22",
4205
-
"orchestra/testbench": "^9.0||^10.0",
4206
-
"revolt/event-loop": "^1.0",
4207
-
"workerman/workerman": "^5.0"
4208
-
},
4209
-
"suggest": {
4210
-
"ext-gmp": "*",
4211
-
"ext-pcntl": "*",
4212
-
"revolt/event-loop": "Required to use WebSocket.",
4213
-
"workerman/workerman": "Required to use WebSocket."
4214
-
},
4215
-
"type": "library",
4216
-
"extra": {
4217
-
"laravel": {
4218
-
"providers": [
4219
-
"Revolution\\Bluesky\\Providers\\BlueskyServiceProvider"
4220
-
]
4221
-
}
4222
-
},
4223
-
"autoload": {
4224
-
"psr-4": {
4225
-
"Revolution\\Bluesky\\": "src/"
4226
-
}
4227
-
},
4228
-
"notification-url": "https://packagist.org/downloads/",
4229
-
"license": [
4230
-
"MIT"
4231
-
],
4232
-
"authors": [
4233
-
{
4234
-
"name": "kawax",
4235
-
"email": "kawaxbiz@gmail.com"
4236
-
}
4237
-
],
4238
-
"description": "Bluesky(AT Protocol) for Laravel",
4239
-
"keywords": [
4240
-
"atproto",
4241
-
"bluesky",
4242
-
"feed-generator",
4243
-
"labeler",
4244
-
"laravel",
4245
-
"notifications",
4246
-
"socialite"
4247
-
],
4248
-
"support": {
4249
-
"source": "https://github.com/invokable/laravel-bluesky/tree/1.1.3"
4250
-
},
4251
-
"funding": [
4252
-
{
4253
-
"url": "https://github.com/invokable",
4254
-
"type": "github"
4255
-
}
4256
-
],
4257
-
"time": "2025-09-03T05:23:09+00:00"
4258
-
},
4259
-
{
4260
-
"name": "symfony/clock",
4261
-
"version": "v7.3.0",
4262
-
"source": {
4263
-
"type": "git",
4264
-
"url": "https://github.com/symfony/clock.git",
4265
-
"reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24"
4266
-
},
4267
-
"dist": {
4268
-
"type": "zip",
4269
-
"url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24",
4270
-
"reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24",
4271
-
"shasum": ""
4272
-
},
4273
-
"require": {
4274
-
"php": ">=8.2",
4275
-
"psr/clock": "^1.0",
4276
-
"symfony/polyfill-php83": "^1.28"
4277
-
},
4278
-
"provide": {
4279
-
"psr/clock-implementation": "1.0"
4280
-
},
4281
-
"type": "library",
4282
-
"autoload": {
4283
-
"files": [
4284
-
"Resources/now.php"
4285
-
],
4286
-
"psr-4": {
4287
-
"Symfony\\Component\\Clock\\": ""
4288
-
},
4289
-
"exclude-from-classmap": [
4290
-
"/Tests/"
4291
-
]
4292
-
},
4293
-
"notification-url": "https://packagist.org/downloads/",
4294
-
"license": [
4295
-
"MIT"
4296
-
],
4297
-
"authors": [
4298
-
{
4299
-
"name": "Nicolas Grekas",
4300
-
"email": "p@tchwork.com"
4301
-
},
4302
-
{
4303
-
"name": "Symfony Community",
4304
-
"homepage": "https://symfony.com/contributors"
4305
-
}
4306
-
],
4307
-
"description": "Decouples applications from the system clock",
4308
-
"homepage": "https://symfony.com",
4309
-
"keywords": [
4310
-
"clock",
4311
-
"psr20",
4312
-
"time"
4313
-
],
4314
-
"support": {
4315
-
"source": "https://github.com/symfony/clock/tree/v7.3.0"
4316
-
},
4317
-
"funding": [
4318
-
{
4319
-
"url": "https://symfony.com/sponsor",
4320
-
"type": "custom"
4321
-
},
4322
-
{
4323
-
"url": "https://github.com/fabpot",
4324
-
"type": "github"
4325
-
},
4326
-
{
4327
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4328
-
"type": "tidelift"
4329
-
}
4330
-
],
4331
-
"time": "2024-09-25T14:21:43+00:00"
4332
-
},
4333
-
{
4334
-
"name": "symfony/console",
4335
-
"version": "v7.3.5",
4336
-
"source": {
4337
-
"type": "git",
4338
-
"url": "https://github.com/symfony/console.git",
4339
-
"reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7"
4340
-
},
4341
-
"dist": {
4342
-
"type": "zip",
4343
-
"url": "https://api.github.com/repos/symfony/console/zipball/cdb80fa5869653c83cfe1a9084a673b6daf57ea7",
4344
-
"reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7",
4345
-
"shasum": ""
4346
-
},
4347
-
"require": {
4348
-
"php": ">=8.2",
4349
-
"symfony/deprecation-contracts": "^2.5|^3",
4350
-
"symfony/polyfill-mbstring": "~1.0",
4351
-
"symfony/service-contracts": "^2.5|^3",
4352
-
"symfony/string": "^7.2"
4353
-
},
4354
-
"conflict": {
4355
-
"symfony/dependency-injection": "<6.4",
4356
-
"symfony/dotenv": "<6.4",
4357
-
"symfony/event-dispatcher": "<6.4",
4358
-
"symfony/lock": "<6.4",
4359
-
"symfony/process": "<6.4"
4360
-
},
4361
-
"provide": {
4362
-
"psr/log-implementation": "1.0|2.0|3.0"
4363
-
},
4364
-
"require-dev": {
4365
-
"psr/log": "^1|^2|^3",
4366
-
"symfony/config": "^6.4|^7.0",
4367
-
"symfony/dependency-injection": "^6.4|^7.0",
4368
-
"symfony/event-dispatcher": "^6.4|^7.0",
4369
-
"symfony/http-foundation": "^6.4|^7.0",
4370
-
"symfony/http-kernel": "^6.4|^7.0",
4371
-
"symfony/lock": "^6.4|^7.0",
4372
-
"symfony/messenger": "^6.4|^7.0",
4373
-
"symfony/process": "^6.4|^7.0",
4374
-
"symfony/stopwatch": "^6.4|^7.0",
4375
-
"symfony/var-dumper": "^6.4|^7.0"
4376
-
},
4377
-
"type": "library",
4378
-
"autoload": {
4379
-
"psr-4": {
4380
-
"Symfony\\Component\\Console\\": ""
4381
-
},
4382
-
"exclude-from-classmap": [
4383
-
"/Tests/"
4384
-
]
4385
-
},
4386
-
"notification-url": "https://packagist.org/downloads/",
4387
-
"license": [
4388
-
"MIT"
4389
-
],
4390
-
"authors": [
4391
-
{
4392
-
"name": "Fabien Potencier",
4393
-
"email": "fabien@symfony.com"
4394
-
},
4395
-
{
4396
-
"name": "Symfony Community",
4397
-
"homepage": "https://symfony.com/contributors"
4398
-
}
4399
-
],
4400
-
"description": "Eases the creation of beautiful and testable command line interfaces",
4401
-
"homepage": "https://symfony.com",
4402
-
"keywords": [
4403
-
"cli",
4404
-
"command-line",
4405
-
"console",
4406
-
"terminal"
4407
-
],
4408
-
"support": {
4409
-
"source": "https://github.com/symfony/console/tree/v7.3.5"
4410
-
},
4411
-
"funding": [
4412
-
{
4413
-
"url": "https://symfony.com/sponsor",
4414
-
"type": "custom"
4415
-
},
4416
-
{
4417
-
"url": "https://github.com/fabpot",
4418
-
"type": "github"
4419
-
},
4420
-
{
4421
-
"url": "https://github.com/nicolas-grekas",
4422
-
"type": "github"
4423
-
},
4424
-
{
4425
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4426
-
"type": "tidelift"
4427
-
}
4428
-
],
4429
-
"time": "2025-10-14T15:46:26+00:00"
4430
-
},
4431
-
{
4432
-
"name": "symfony/css-selector",
4433
-
"version": "v7.3.0",
4434
-
"source": {
4435
-
"type": "git",
4436
-
"url": "https://github.com/symfony/css-selector.git",
4437
-
"reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2"
4438
-
},
4439
-
"dist": {
4440
-
"type": "zip",
4441
-
"url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2",
4442
-
"reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2",
4443
-
"shasum": ""
4444
-
},
4445
-
"require": {
4446
-
"php": ">=8.2"
4447
-
},
4448
-
"type": "library",
4449
-
"autoload": {
4450
-
"psr-4": {
4451
-
"Symfony\\Component\\CssSelector\\": ""
4452
-
},
4453
-
"exclude-from-classmap": [
4454
-
"/Tests/"
4455
-
]
4456
-
},
4457
-
"notification-url": "https://packagist.org/downloads/",
4458
-
"license": [
4459
-
"MIT"
4460
-
],
4461
-
"authors": [
4462
-
{
4463
-
"name": "Fabien Potencier",
4464
-
"email": "fabien@symfony.com"
4465
-
},
4466
-
{
4467
-
"name": "Jean-Franรงois Simon",
4468
-
"email": "jeanfrancois.simon@sensiolabs.com"
4469
-
},
4470
-
{
4471
-
"name": "Symfony Community",
4472
-
"homepage": "https://symfony.com/contributors"
4473
-
}
4474
-
],
4475
-
"description": "Converts CSS selectors to XPath expressions",
4476
-
"homepage": "https://symfony.com",
4477
-
"support": {
4478
-
"source": "https://github.com/symfony/css-selector/tree/v7.3.0"
4479
-
},
4480
-
"funding": [
4481
-
{
4482
-
"url": "https://symfony.com/sponsor",
4483
-
"type": "custom"
4484
-
},
4485
-
{
4486
-
"url": "https://github.com/fabpot",
4487
-
"type": "github"
4488
-
},
4489
-
{
4490
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4491
-
"type": "tidelift"
4492
-
}
4493
-
],
4494
-
"time": "2024-09-25T14:21:43+00:00"
4495
-
},
4496
-
{
4497
-
"name": "symfony/deprecation-contracts",
4498
-
"version": "v3.6.0",
4499
-
"source": {
4500
-
"type": "git",
4501
-
"url": "https://github.com/symfony/deprecation-contracts.git",
4502
-
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
4503
-
},
4504
-
"dist": {
4505
-
"type": "zip",
4506
-
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
4507
-
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
4508
-
"shasum": ""
4509
-
},
4510
-
"require": {
4511
-
"php": ">=8.1"
4512
-
},
4513
-
"type": "library",
4514
-
"extra": {
4515
-
"thanks": {
4516
-
"url": "https://github.com/symfony/contracts",
4517
-
"name": "symfony/contracts"
4518
-
},
4519
-
"branch-alias": {
4520
-
"dev-main": "3.6-dev"
4521
-
}
4522
-
},
4523
-
"autoload": {
4524
-
"files": [
4525
-
"function.php"
4526
-
]
4527
-
},
4528
-
"notification-url": "https://packagist.org/downloads/",
4529
-
"license": [
4530
-
"MIT"
4531
-
],
4532
-
"authors": [
4533
-
{
4534
-
"name": "Nicolas Grekas",
4535
-
"email": "p@tchwork.com"
4536
-
},
4537
-
{
4538
-
"name": "Symfony Community",
4539
-
"homepage": "https://symfony.com/contributors"
4540
-
}
4541
-
],
4542
-
"description": "A generic function and convention to trigger deprecation notices",
4543
-
"homepage": "https://symfony.com",
4544
-
"support": {
4545
-
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
4546
-
},
4547
-
"funding": [
4548
-
{
4549
-
"url": "https://symfony.com/sponsor",
4550
-
"type": "custom"
4551
-
},
4552
-
{
4553
-
"url": "https://github.com/fabpot",
4554
-
"type": "github"
4555
-
},
4556
-
{
4557
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4558
-
"type": "tidelift"
4559
-
}
4560
-
],
4561
-
"time": "2024-09-25T14:21:43+00:00"
4562
-
},
4563
-
{
4564
-
"name": "symfony/error-handler",
4565
-
"version": "v7.3.4",
4566
-
"source": {
4567
-
"type": "git",
4568
-
"url": "https://github.com/symfony/error-handler.git",
4569
-
"reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4"
4570
-
},
4571
-
"dist": {
4572
-
"type": "zip",
4573
-
"url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4",
4574
-
"reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4",
4575
-
"shasum": ""
4576
-
},
4577
-
"require": {
4578
-
"php": ">=8.2",
4579
-
"psr/log": "^1|^2|^3",
4580
-
"symfony/var-dumper": "^6.4|^7.0"
4581
-
},
4582
-
"conflict": {
4583
-
"symfony/deprecation-contracts": "<2.5",
4584
-
"symfony/http-kernel": "<6.4"
4585
-
},
4586
-
"require-dev": {
4587
-
"symfony/console": "^6.4|^7.0",
4588
-
"symfony/deprecation-contracts": "^2.5|^3",
4589
-
"symfony/http-kernel": "^6.4|^7.0",
4590
-
"symfony/serializer": "^6.4|^7.0",
4591
-
"symfony/webpack-encore-bundle": "^1.0|^2.0"
4592
-
},
4593
-
"bin": [
4594
-
"Resources/bin/patch-type-declarations"
4595
-
],
4596
-
"type": "library",
4597
-
"autoload": {
4598
-
"psr-4": {
4599
-
"Symfony\\Component\\ErrorHandler\\": ""
4600
-
},
4601
-
"exclude-from-classmap": [
4602
-
"/Tests/"
4603
-
]
4604
-
},
4605
-
"notification-url": "https://packagist.org/downloads/",
4606
-
"license": [
4607
-
"MIT"
4608
-
],
4609
-
"authors": [
4610
-
{
4611
-
"name": "Fabien Potencier",
4612
-
"email": "fabien@symfony.com"
4613
-
},
4614
-
{
4615
-
"name": "Symfony Community",
4616
-
"homepage": "https://symfony.com/contributors"
4617
-
}
4618
-
],
4619
-
"description": "Provides tools to manage errors and ease debugging PHP code",
4620
-
"homepage": "https://symfony.com",
4621
-
"support": {
4622
-
"source": "https://github.com/symfony/error-handler/tree/v7.3.4"
4623
-
},
4624
-
"funding": [
4625
-
{
4626
-
"url": "https://symfony.com/sponsor",
4627
-
"type": "custom"
4628
-
},
4629
-
{
4630
-
"url": "https://github.com/fabpot",
4631
-
"type": "github"
4632
-
},
4633
-
{
4634
-
"url": "https://github.com/nicolas-grekas",
4635
-
"type": "github"
4636
-
},
4637
-
{
4638
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4639
-
"type": "tidelift"
4640
-
}
4641
-
],
4642
-
"time": "2025-09-11T10:12:26+00:00"
4643
-
},
4644
-
{
4645
-
"name": "symfony/event-dispatcher",
4646
-
"version": "v7.3.3",
4647
-
"source": {
4648
-
"type": "git",
4649
-
"url": "https://github.com/symfony/event-dispatcher.git",
4650
-
"reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191"
4651
-
},
4652
-
"dist": {
4653
-
"type": "zip",
4654
-
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191",
4655
-
"reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191",
4656
-
"shasum": ""
4657
-
},
4658
-
"require": {
4659
-
"php": ">=8.2",
4660
-
"symfony/event-dispatcher-contracts": "^2.5|^3"
4661
-
},
4662
-
"conflict": {
4663
-
"symfony/dependency-injection": "<6.4",
4664
-
"symfony/service-contracts": "<2.5"
4665
-
},
4666
-
"provide": {
4667
-
"psr/event-dispatcher-implementation": "1.0",
4668
-
"symfony/event-dispatcher-implementation": "2.0|3.0"
4669
-
},
4670
-
"require-dev": {
4671
-
"psr/log": "^1|^2|^3",
4672
-
"symfony/config": "^6.4|^7.0",
4673
-
"symfony/dependency-injection": "^6.4|^7.0",
4674
-
"symfony/error-handler": "^6.4|^7.0",
4675
-
"symfony/expression-language": "^6.4|^7.0",
4676
-
"symfony/http-foundation": "^6.4|^7.0",
4677
-
"symfony/service-contracts": "^2.5|^3",
4678
-
"symfony/stopwatch": "^6.4|^7.0"
4679
-
},
4680
-
"type": "library",
4681
-
"autoload": {
4682
-
"psr-4": {
4683
-
"Symfony\\Component\\EventDispatcher\\": ""
4684
-
},
4685
-
"exclude-from-classmap": [
4686
-
"/Tests/"
4687
-
]
4688
-
},
4689
-
"notification-url": "https://packagist.org/downloads/",
4690
-
"license": [
4691
-
"MIT"
4692
-
],
4693
-
"authors": [
4694
-
{
4695
-
"name": "Fabien Potencier",
4696
-
"email": "fabien@symfony.com"
4697
-
},
4698
-
{
4699
-
"name": "Symfony Community",
4700
-
"homepage": "https://symfony.com/contributors"
4701
-
}
4702
-
],
4703
-
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
4704
-
"homepage": "https://symfony.com",
4705
-
"support": {
4706
-
"source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3"
4707
-
},
4708
-
"funding": [
4709
-
{
4710
-
"url": "https://symfony.com/sponsor",
4711
-
"type": "custom"
4712
-
},
4713
-
{
4714
-
"url": "https://github.com/fabpot",
4715
-
"type": "github"
4716
-
},
4717
-
{
4718
-
"url": "https://github.com/nicolas-grekas",
4719
-
"type": "github"
4720
-
},
4721
-
{
4722
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4723
-
"type": "tidelift"
4724
-
}
4725
-
],
4726
-
"time": "2025-08-13T11:49:31+00:00"
4727
-
},
4728
-
{
4729
-
"name": "symfony/event-dispatcher-contracts",
4730
-
"version": "v3.6.0",
4731
-
"source": {
4732
-
"type": "git",
4733
-
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
4734
-
"reference": "59eb412e93815df44f05f342958efa9f46b1e586"
4735
-
},
4736
-
"dist": {
4737
-
"type": "zip",
4738
-
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586",
4739
-
"reference": "59eb412e93815df44f05f342958efa9f46b1e586",
4740
-
"shasum": ""
4741
-
},
4742
-
"require": {
4743
-
"php": ">=8.1",
4744
-
"psr/event-dispatcher": "^1"
4745
-
},
4746
-
"type": "library",
4747
-
"extra": {
4748
-
"thanks": {
4749
-
"url": "https://github.com/symfony/contracts",
4750
-
"name": "symfony/contracts"
4751
-
},
4752
-
"branch-alias": {
4753
-
"dev-main": "3.6-dev"
4754
-
}
4755
-
},
4756
-
"autoload": {
4757
-
"psr-4": {
4758
-
"Symfony\\Contracts\\EventDispatcher\\": ""
4759
-
}
4760
-
},
4761
-
"notification-url": "https://packagist.org/downloads/",
4762
-
"license": [
4763
-
"MIT"
4764
-
],
4765
-
"authors": [
4766
-
{
4767
-
"name": "Nicolas Grekas",
4768
-
"email": "p@tchwork.com"
4769
-
},
4770
-
{
4771
-
"name": "Symfony Community",
4772
-
"homepage": "https://symfony.com/contributors"
4773
-
}
4774
-
],
4775
-
"description": "Generic abstractions related to dispatching event",
4776
-
"homepage": "https://symfony.com",
4777
-
"keywords": [
4778
-
"abstractions",
4779
-
"contracts",
4780
-
"decoupling",
4781
-
"interfaces",
4782
-
"interoperability",
4783
-
"standards"
4784
-
],
4785
-
"support": {
4786
-
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0"
4787
-
},
4788
-
"funding": [
4789
-
{
4790
-
"url": "https://symfony.com/sponsor",
4791
-
"type": "custom"
4792
-
},
4793
-
{
4794
-
"url": "https://github.com/fabpot",
4795
-
"type": "github"
4796
-
},
4797
-
{
4798
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4799
-
"type": "tidelift"
4800
-
}
4801
-
],
4802
-
"time": "2024-09-25T14:21:43+00:00"
4803
-
},
4804
-
{
4805
-
"name": "symfony/finder",
4806
-
"version": "v7.3.5",
4807
-
"source": {
4808
-
"type": "git",
4809
-
"url": "https://github.com/symfony/finder.git",
4810
-
"reference": "9f696d2f1e340484b4683f7853b273abff94421f"
4811
-
},
4812
-
"dist": {
4813
-
"type": "zip",
4814
-
"url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f",
4815
-
"reference": "9f696d2f1e340484b4683f7853b273abff94421f",
4816
-
"shasum": ""
4817
-
},
4818
-
"require": {
4819
-
"php": ">=8.2"
4820
-
},
4821
-
"require-dev": {
4822
-
"symfony/filesystem": "^6.4|^7.0"
4823
-
},
4824
-
"type": "library",
4825
-
"autoload": {
4826
-
"psr-4": {
4827
-
"Symfony\\Component\\Finder\\": ""
4828
-
},
4829
-
"exclude-from-classmap": [
4830
-
"/Tests/"
4831
-
]
4832
-
},
4833
-
"notification-url": "https://packagist.org/downloads/",
4834
-
"license": [
4835
-
"MIT"
4836
-
],
4837
-
"authors": [
4838
-
{
4839
-
"name": "Fabien Potencier",
4840
-
"email": "fabien@symfony.com"
4841
-
},
4842
-
{
4843
-
"name": "Symfony Community",
4844
-
"homepage": "https://symfony.com/contributors"
4845
-
}
4846
-
],
4847
-
"description": "Finds files and directories via an intuitive fluent interface",
4848
-
"homepage": "https://symfony.com",
4849
-
"support": {
4850
-
"source": "https://github.com/symfony/finder/tree/v7.3.5"
4851
-
},
4852
-
"funding": [
4853
-
{
4854
-
"url": "https://symfony.com/sponsor",
4855
-
"type": "custom"
4856
-
},
4857
-
{
4858
-
"url": "https://github.com/fabpot",
4859
-
"type": "github"
4860
-
},
4861
-
{
4862
-
"url": "https://github.com/nicolas-grekas",
4863
-
"type": "github"
4864
-
},
4865
-
{
4866
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4867
-
"type": "tidelift"
4868
-
}
4869
-
],
4870
-
"time": "2025-10-15T18:45:57+00:00"
4871
-
},
4872
-
{
4873
-
"name": "symfony/http-foundation",
4874
-
"version": "v7.3.5",
4875
-
"source": {
4876
-
"type": "git",
4877
-
"url": "https://github.com/symfony/http-foundation.git",
4878
-
"reference": "ce31218c7cac92eab280762c4375fb70a6f4f897"
4879
-
},
4880
-
"dist": {
4881
-
"type": "zip",
4882
-
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/ce31218c7cac92eab280762c4375fb70a6f4f897",
4883
-
"reference": "ce31218c7cac92eab280762c4375fb70a6f4f897",
4884
-
"shasum": ""
4885
-
},
4886
-
"require": {
4887
-
"php": ">=8.2",
4888
-
"symfony/deprecation-contracts": "^2.5|^3.0",
4889
-
"symfony/polyfill-mbstring": "~1.1",
4890
-
"symfony/polyfill-php83": "^1.27"
4891
-
},
4892
-
"conflict": {
4893
-
"doctrine/dbal": "<3.6",
4894
-
"symfony/cache": "<6.4.12|>=7.0,<7.1.5"
4895
-
},
4896
-
"require-dev": {
4897
-
"doctrine/dbal": "^3.6|^4",
4898
-
"predis/predis": "^1.1|^2.0",
4899
-
"symfony/cache": "^6.4.12|^7.1.5",
4900
-
"symfony/clock": "^6.4|^7.0",
4901
-
"symfony/dependency-injection": "^6.4|^7.0",
4902
-
"symfony/expression-language": "^6.4|^7.0",
4903
-
"symfony/http-kernel": "^6.4|^7.0",
4904
-
"symfony/mime": "^6.4|^7.0",
4905
-
"symfony/rate-limiter": "^6.4|^7.0"
4906
-
},
4907
-
"type": "library",
4908
-
"autoload": {
4909
-
"psr-4": {
4910
-
"Symfony\\Component\\HttpFoundation\\": ""
4911
-
},
4912
-
"exclude-from-classmap": [
4913
-
"/Tests/"
4914
-
]
4915
-
},
4916
-
"notification-url": "https://packagist.org/downloads/",
4917
-
"license": [
4918
-
"MIT"
4919
-
],
4920
-
"authors": [
4921
-
{
4922
-
"name": "Fabien Potencier",
4923
-
"email": "fabien@symfony.com"
4924
-
},
4925
-
{
4926
-
"name": "Symfony Community",
4927
-
"homepage": "https://symfony.com/contributors"
4928
-
}
4929
-
],
4930
-
"description": "Defines an object-oriented layer for the HTTP specification",
4931
-
"homepage": "https://symfony.com",
4932
-
"support": {
4933
-
"source": "https://github.com/symfony/http-foundation/tree/v7.3.5"
4934
-
},
4935
-
"funding": [
4936
-
{
4937
-
"url": "https://symfony.com/sponsor",
4938
-
"type": "custom"
4939
-
},
4940
-
{
4941
-
"url": "https://github.com/fabpot",
4942
-
"type": "github"
4943
-
},
4944
-
{
4945
-
"url": "https://github.com/nicolas-grekas",
4946
-
"type": "github"
4947
-
},
4948
-
{
4949
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
4950
-
"type": "tidelift"
4951
-
}
4952
-
],
4953
-
"time": "2025-10-24T21:42:11+00:00"
4954
-
},
4955
-
{
4956
-
"name": "symfony/http-kernel",
4957
-
"version": "v7.3.5",
4958
-
"source": {
4959
-
"type": "git",
4960
-
"url": "https://github.com/symfony/http-kernel.git",
4961
-
"reference": "24fd3f123532e26025f49f1abefcc01a69ef15ab"
4962
-
},
4963
-
"dist": {
4964
-
"type": "zip",
4965
-
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/24fd3f123532e26025f49f1abefcc01a69ef15ab",
4966
-
"reference": "24fd3f123532e26025f49f1abefcc01a69ef15ab",
4967
-
"shasum": ""
4968
-
},
4969
-
"require": {
4970
-
"php": ">=8.2",
4971
-
"psr/log": "^1|^2|^3",
4972
-
"symfony/deprecation-contracts": "^2.5|^3",
4973
-
"symfony/error-handler": "^6.4|^7.0",
4974
-
"symfony/event-dispatcher": "^7.3",
4975
-
"symfony/http-foundation": "^7.3",
4976
-
"symfony/polyfill-ctype": "^1.8"
4977
-
},
4978
-
"conflict": {
4979
-
"symfony/browser-kit": "<6.4",
4980
-
"symfony/cache": "<6.4",
4981
-
"symfony/config": "<6.4",
4982
-
"symfony/console": "<6.4",
4983
-
"symfony/dependency-injection": "<6.4",
4984
-
"symfony/doctrine-bridge": "<6.4",
4985
-
"symfony/form": "<6.4",
4986
-
"symfony/http-client": "<6.4",
4987
-
"symfony/http-client-contracts": "<2.5",
4988
-
"symfony/mailer": "<6.4",
4989
-
"symfony/messenger": "<6.4",
4990
-
"symfony/translation": "<6.4",
4991
-
"symfony/translation-contracts": "<2.5",
4992
-
"symfony/twig-bridge": "<6.4",
4993
-
"symfony/validator": "<6.4",
4994
-
"symfony/var-dumper": "<6.4",
4995
-
"twig/twig": "<3.12"
4996
-
},
4997
-
"provide": {
4998
-
"psr/log-implementation": "1.0|2.0|3.0"
4999
-
},
5000
-
"require-dev": {
5001
-
"psr/cache": "^1.0|^2.0|^3.0",
5002
-
"symfony/browser-kit": "^6.4|^7.0",
5003
-
"symfony/clock": "^6.4|^7.0",
5004
-
"symfony/config": "^6.4|^7.0",
5005
-
"symfony/console": "^6.4|^7.0",
5006
-
"symfony/css-selector": "^6.4|^7.0",
5007
-
"symfony/dependency-injection": "^6.4|^7.0",
5008
-
"symfony/dom-crawler": "^6.4|^7.0",
5009
-
"symfony/expression-language": "^6.4|^7.0",
5010
-
"symfony/finder": "^6.4|^7.0",
5011
-
"symfony/http-client-contracts": "^2.5|^3",
5012
-
"symfony/process": "^6.4|^7.0",
5013
-
"symfony/property-access": "^7.1",
5014
-
"symfony/routing": "^6.4|^7.0",
5015
-
"symfony/serializer": "^7.1",
5016
-
"symfony/stopwatch": "^6.4|^7.0",
5017
-
"symfony/translation": "^6.4|^7.0",
5018
-
"symfony/translation-contracts": "^2.5|^3",
5019
-
"symfony/uid": "^6.4|^7.0",
5020
-
"symfony/validator": "^6.4|^7.0",
5021
-
"symfony/var-dumper": "^6.4|^7.0",
5022
-
"symfony/var-exporter": "^6.4|^7.0",
5023
-
"twig/twig": "^3.12"
5024
-
},
5025
-
"type": "library",
5026
-
"autoload": {
5027
-
"psr-4": {
5028
-
"Symfony\\Component\\HttpKernel\\": ""
5029
-
},
5030
-
"exclude-from-classmap": [
5031
-
"/Tests/"
5032
-
]
5033
-
},
5034
-
"notification-url": "https://packagist.org/downloads/",
5035
-
"license": [
5036
-
"MIT"
5037
-
],
5038
-
"authors": [
5039
-
{
5040
-
"name": "Fabien Potencier",
5041
-
"email": "fabien@symfony.com"
5042
-
},
5043
-
{
5044
-
"name": "Symfony Community",
5045
-
"homepage": "https://symfony.com/contributors"
5046
-
}
5047
-
],
5048
-
"description": "Provides a structured process for converting a Request into a Response",
5049
-
"homepage": "https://symfony.com",
5050
-
"support": {
5051
-
"source": "https://github.com/symfony/http-kernel/tree/v7.3.5"
5052
-
},
5053
-
"funding": [
5054
-
{
5055
-
"url": "https://symfony.com/sponsor",
5056
-
"type": "custom"
5057
-
},
5058
-
{
5059
-
"url": "https://github.com/fabpot",
5060
-
"type": "github"
5061
-
},
5062
-
{
5063
-
"url": "https://github.com/nicolas-grekas",
5064
-
"type": "github"
5065
-
},
5066
-
{
5067
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5068
-
"type": "tidelift"
5069
-
}
5070
-
],
5071
-
"time": "2025-10-28T10:19:01+00:00"
5072
-
},
5073
-
{
5074
-
"name": "symfony/mailer",
5075
-
"version": "v7.3.5",
5076
-
"source": {
5077
-
"type": "git",
5078
-
"url": "https://github.com/symfony/mailer.git",
5079
-
"reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba"
5080
-
},
5081
-
"dist": {
5082
-
"type": "zip",
5083
-
"url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba",
5084
-
"reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba",
5085
-
"shasum": ""
5086
-
},
5087
-
"require": {
5088
-
"egulias/email-validator": "^2.1.10|^3|^4",
5089
-
"php": ">=8.2",
5090
-
"psr/event-dispatcher": "^1",
5091
-
"psr/log": "^1|^2|^3",
5092
-
"symfony/event-dispatcher": "^6.4|^7.0",
5093
-
"symfony/mime": "^7.2",
5094
-
"symfony/service-contracts": "^2.5|^3"
5095
-
},
5096
-
"conflict": {
5097
-
"symfony/http-client-contracts": "<2.5",
5098
-
"symfony/http-kernel": "<6.4",
5099
-
"symfony/messenger": "<6.4",
5100
-
"symfony/mime": "<6.4",
5101
-
"symfony/twig-bridge": "<6.4"
5102
-
},
5103
-
"require-dev": {
5104
-
"symfony/console": "^6.4|^7.0",
5105
-
"symfony/http-client": "^6.4|^7.0",
5106
-
"symfony/messenger": "^6.4|^7.0",
5107
-
"symfony/twig-bridge": "^6.4|^7.0"
5108
-
},
5109
-
"type": "library",
5110
-
"autoload": {
5111
-
"psr-4": {
5112
-
"Symfony\\Component\\Mailer\\": ""
5113
-
},
5114
-
"exclude-from-classmap": [
5115
-
"/Tests/"
5116
-
]
5117
-
},
5118
-
"notification-url": "https://packagist.org/downloads/",
5119
-
"license": [
5120
-
"MIT"
5121
-
],
5122
-
"authors": [
5123
-
{
5124
-
"name": "Fabien Potencier",
5125
-
"email": "fabien@symfony.com"
5126
-
},
5127
-
{
5128
-
"name": "Symfony Community",
5129
-
"homepage": "https://symfony.com/contributors"
5130
-
}
5131
-
],
5132
-
"description": "Helps sending emails",
5133
-
"homepage": "https://symfony.com",
5134
-
"support": {
5135
-
"source": "https://github.com/symfony/mailer/tree/v7.3.5"
5136
-
},
5137
-
"funding": [
5138
-
{
5139
-
"url": "https://symfony.com/sponsor",
5140
-
"type": "custom"
5141
-
},
5142
-
{
5143
-
"url": "https://github.com/fabpot",
5144
-
"type": "github"
5145
-
},
5146
-
{
5147
-
"url": "https://github.com/nicolas-grekas",
5148
-
"type": "github"
5149
-
},
5150
-
{
5151
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5152
-
"type": "tidelift"
5153
-
}
5154
-
],
5155
-
"time": "2025-10-24T14:27:20+00:00"
5156
-
},
5157
-
{
5158
-
"name": "symfony/mime",
5159
-
"version": "v7.3.4",
5160
-
"source": {
5161
-
"type": "git",
5162
-
"url": "https://github.com/symfony/mime.git",
5163
-
"reference": "b1b828f69cbaf887fa835a091869e55df91d0e35"
5164
-
},
5165
-
"dist": {
5166
-
"type": "zip",
5167
-
"url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35",
5168
-
"reference": "b1b828f69cbaf887fa835a091869e55df91d0e35",
5169
-
"shasum": ""
5170
-
},
5171
-
"require": {
5172
-
"php": ">=8.2",
5173
-
"symfony/polyfill-intl-idn": "^1.10",
5174
-
"symfony/polyfill-mbstring": "^1.0"
5175
-
},
5176
-
"conflict": {
5177
-
"egulias/email-validator": "~3.0.0",
5178
-
"phpdocumentor/reflection-docblock": "<3.2.2",
5179
-
"phpdocumentor/type-resolver": "<1.4.0",
5180
-
"symfony/mailer": "<6.4",
5181
-
"symfony/serializer": "<6.4.3|>7.0,<7.0.3"
5182
-
},
5183
-
"require-dev": {
5184
-
"egulias/email-validator": "^2.1.10|^3.1|^4",
5185
-
"league/html-to-markdown": "^5.0",
5186
-
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
5187
-
"symfony/dependency-injection": "^6.4|^7.0",
5188
-
"symfony/process": "^6.4|^7.0",
5189
-
"symfony/property-access": "^6.4|^7.0",
5190
-
"symfony/property-info": "^6.4|^7.0",
5191
-
"symfony/serializer": "^6.4.3|^7.0.3"
5192
-
},
5193
-
"type": "library",
5194
-
"autoload": {
5195
-
"psr-4": {
5196
-
"Symfony\\Component\\Mime\\": ""
5197
-
},
5198
-
"exclude-from-classmap": [
5199
-
"/Tests/"
5200
-
]
5201
-
},
5202
-
"notification-url": "https://packagist.org/downloads/",
5203
-
"license": [
5204
-
"MIT"
5205
-
],
5206
-
"authors": [
5207
-
{
5208
-
"name": "Fabien Potencier",
5209
-
"email": "fabien@symfony.com"
5210
-
},
5211
-
{
5212
-
"name": "Symfony Community",
5213
-
"homepage": "https://symfony.com/contributors"
5214
-
}
5215
-
],
5216
-
"description": "Allows manipulating MIME messages",
5217
-
"homepage": "https://symfony.com",
5218
-
"keywords": [
5219
-
"mime",
5220
-
"mime-type"
5221
-
],
5222
-
"support": {
5223
-
"source": "https://github.com/symfony/mime/tree/v7.3.4"
5224
-
},
5225
-
"funding": [
5226
-
{
5227
-
"url": "https://symfony.com/sponsor",
5228
-
"type": "custom"
5229
-
},
5230
-
{
5231
-
"url": "https://github.com/fabpot",
5232
-
"type": "github"
5233
-
},
5234
-
{
5235
-
"url": "https://github.com/nicolas-grekas",
5236
-
"type": "github"
5237
-
},
5238
-
{
5239
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5240
-
"type": "tidelift"
5241
-
}
5242
-
],
5243
-
"time": "2025-09-16T08:38:17+00:00"
5244
-
},
5245
-
{
5246
-
"name": "symfony/polyfill-ctype",
5247
-
"version": "v1.33.0",
5248
-
"source": {
5249
-
"type": "git",
5250
-
"url": "https://github.com/symfony/polyfill-ctype.git",
5251
-
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
5252
-
},
5253
-
"dist": {
5254
-
"type": "zip",
5255
-
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
5256
-
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
5257
-
"shasum": ""
5258
-
},
5259
-
"require": {
5260
-
"php": ">=7.2"
5261
-
},
5262
-
"provide": {
5263
-
"ext-ctype": "*"
5264
-
},
5265
-
"suggest": {
5266
-
"ext-ctype": "For best performance"
5267
-
},
5268
-
"type": "library",
5269
-
"extra": {
5270
-
"thanks": {
5271
-
"url": "https://github.com/symfony/polyfill",
5272
-
"name": "symfony/polyfill"
5273
-
}
5274
-
},
5275
-
"autoload": {
5276
-
"files": [
5277
-
"bootstrap.php"
5278
-
],
5279
-
"psr-4": {
5280
-
"Symfony\\Polyfill\\Ctype\\": ""
5281
-
}
5282
-
},
5283
-
"notification-url": "https://packagist.org/downloads/",
5284
-
"license": [
5285
-
"MIT"
5286
-
],
5287
-
"authors": [
5288
-
{
5289
-
"name": "Gert de Pagter",
5290
-
"email": "BackEndTea@gmail.com"
5291
-
},
5292
-
{
5293
-
"name": "Symfony Community",
5294
-
"homepage": "https://symfony.com/contributors"
5295
-
}
5296
-
],
5297
-
"description": "Symfony polyfill for ctype functions",
5298
-
"homepage": "https://symfony.com",
5299
-
"keywords": [
5300
-
"compatibility",
5301
-
"ctype",
5302
-
"polyfill",
5303
-
"portable"
5304
-
],
5305
-
"support": {
5306
-
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
5307
-
},
5308
-
"funding": [
5309
-
{
5310
-
"url": "https://symfony.com/sponsor",
5311
-
"type": "custom"
5312
-
},
5313
-
{
5314
-
"url": "https://github.com/fabpot",
5315
-
"type": "github"
5316
-
},
5317
-
{
5318
-
"url": "https://github.com/nicolas-grekas",
5319
-
"type": "github"
5320
-
},
5321
-
{
5322
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5323
-
"type": "tidelift"
5324
-
}
5325
-
],
5326
-
"time": "2024-09-09T11:45:10+00:00"
5327
-
},
5328
-
{
5329
-
"name": "symfony/polyfill-intl-grapheme",
5330
-
"version": "v1.33.0",
5331
-
"source": {
5332
-
"type": "git",
5333
-
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
5334
-
"reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
5335
-
},
5336
-
"dist": {
5337
-
"type": "zip",
5338
-
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
5339
-
"reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
5340
-
"shasum": ""
5341
-
},
5342
-
"require": {
5343
-
"php": ">=7.2"
5344
-
},
5345
-
"suggest": {
5346
-
"ext-intl": "For best performance"
5347
-
},
5348
-
"type": "library",
5349
-
"extra": {
5350
-
"thanks": {
5351
-
"url": "https://github.com/symfony/polyfill",
5352
-
"name": "symfony/polyfill"
5353
-
}
5354
-
},
5355
-
"autoload": {
5356
-
"files": [
5357
-
"bootstrap.php"
5358
-
],
5359
-
"psr-4": {
5360
-
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
5361
-
}
5362
-
},
5363
-
"notification-url": "https://packagist.org/downloads/",
5364
-
"license": [
5365
-
"MIT"
5366
-
],
5367
-
"authors": [
5368
-
{
5369
-
"name": "Nicolas Grekas",
5370
-
"email": "p@tchwork.com"
5371
-
},
5372
-
{
5373
-
"name": "Symfony Community",
5374
-
"homepage": "https://symfony.com/contributors"
5375
-
}
5376
-
],
5377
-
"description": "Symfony polyfill for intl's grapheme_* functions",
5378
-
"homepage": "https://symfony.com",
5379
-
"keywords": [
5380
-
"compatibility",
5381
-
"grapheme",
5382
-
"intl",
5383
-
"polyfill",
5384
-
"portable",
5385
-
"shim"
5386
-
],
5387
-
"support": {
5388
-
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
5389
-
},
5390
-
"funding": [
5391
-
{
5392
-
"url": "https://symfony.com/sponsor",
5393
-
"type": "custom"
5394
-
},
5395
-
{
5396
-
"url": "https://github.com/fabpot",
5397
-
"type": "github"
5398
-
},
5399
-
{
5400
-
"url": "https://github.com/nicolas-grekas",
5401
-
"type": "github"
5402
-
},
5403
-
{
5404
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5405
-
"type": "tidelift"
5406
-
}
5407
-
],
5408
-
"time": "2025-06-27T09:58:17+00:00"
5409
-
},
5410
-
{
5411
-
"name": "symfony/polyfill-intl-idn",
5412
-
"version": "v1.33.0",
5413
-
"source": {
5414
-
"type": "git",
5415
-
"url": "https://github.com/symfony/polyfill-intl-idn.git",
5416
-
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3"
5417
-
},
5418
-
"dist": {
5419
-
"type": "zip",
5420
-
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3",
5421
-
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3",
5422
-
"shasum": ""
5423
-
},
5424
-
"require": {
5425
-
"php": ">=7.2",
5426
-
"symfony/polyfill-intl-normalizer": "^1.10"
5427
-
},
5428
-
"suggest": {
5429
-
"ext-intl": "For best performance"
5430
-
},
5431
-
"type": "library",
5432
-
"extra": {
5433
-
"thanks": {
5434
-
"url": "https://github.com/symfony/polyfill",
5435
-
"name": "symfony/polyfill"
5436
-
}
5437
-
},
5438
-
"autoload": {
5439
-
"files": [
5440
-
"bootstrap.php"
5441
-
],
5442
-
"psr-4": {
5443
-
"Symfony\\Polyfill\\Intl\\Idn\\": ""
5444
-
}
5445
-
},
5446
-
"notification-url": "https://packagist.org/downloads/",
5447
-
"license": [
5448
-
"MIT"
5449
-
],
5450
-
"authors": [
5451
-
{
5452
-
"name": "Laurent Bassin",
5453
-
"email": "laurent@bassin.info"
5454
-
},
5455
-
{
5456
-
"name": "Trevor Rowbotham",
5457
-
"email": "trevor.rowbotham@pm.me"
5458
-
},
5459
-
{
5460
-
"name": "Symfony Community",
5461
-
"homepage": "https://symfony.com/contributors"
5462
-
}
5463
-
],
5464
-
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
5465
-
"homepage": "https://symfony.com",
5466
-
"keywords": [
5467
-
"compatibility",
5468
-
"idn",
5469
-
"intl",
5470
-
"polyfill",
5471
-
"portable",
5472
-
"shim"
5473
-
],
5474
-
"support": {
5475
-
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
5476
-
},
5477
-
"funding": [
5478
-
{
5479
-
"url": "https://symfony.com/sponsor",
5480
-
"type": "custom"
5481
-
},
5482
-
{
5483
-
"url": "https://github.com/fabpot",
5484
-
"type": "github"
5485
-
},
5486
-
{
5487
-
"url": "https://github.com/nicolas-grekas",
5488
-
"type": "github"
5489
-
},
5490
-
{
5491
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5492
-
"type": "tidelift"
5493
-
}
5494
-
],
5495
-
"time": "2024-09-10T14:38:51+00:00"
5496
-
},
5497
-
{
5498
-
"name": "symfony/polyfill-intl-normalizer",
5499
-
"version": "v1.33.0",
5500
-
"source": {
5501
-
"type": "git",
5502
-
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
5503
-
"reference": "3833d7255cc303546435cb650316bff708a1c75c"
5504
-
},
5505
-
"dist": {
5506
-
"type": "zip",
5507
-
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
5508
-
"reference": "3833d7255cc303546435cb650316bff708a1c75c",
5509
-
"shasum": ""
5510
-
},
5511
-
"require": {
5512
-
"php": ">=7.2"
5513
-
},
5514
-
"suggest": {
5515
-
"ext-intl": "For best performance"
5516
-
},
5517
-
"type": "library",
5518
-
"extra": {
5519
-
"thanks": {
5520
-
"url": "https://github.com/symfony/polyfill",
5521
-
"name": "symfony/polyfill"
5522
-
}
5523
-
},
5524
-
"autoload": {
5525
-
"files": [
5526
-
"bootstrap.php"
5527
-
],
5528
-
"psr-4": {
5529
-
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
5530
-
},
5531
-
"classmap": [
5532
-
"Resources/stubs"
5533
-
]
5534
-
},
5535
-
"notification-url": "https://packagist.org/downloads/",
5536
-
"license": [
5537
-
"MIT"
5538
-
],
5539
-
"authors": [
5540
-
{
5541
-
"name": "Nicolas Grekas",
5542
-
"email": "p@tchwork.com"
5543
-
},
5544
-
{
5545
-
"name": "Symfony Community",
5546
-
"homepage": "https://symfony.com/contributors"
5547
-
}
5548
-
],
5549
-
"description": "Symfony polyfill for intl's Normalizer class and related functions",
5550
-
"homepage": "https://symfony.com",
5551
-
"keywords": [
5552
-
"compatibility",
5553
-
"intl",
5554
-
"normalizer",
5555
-
"polyfill",
5556
-
"portable",
5557
-
"shim"
5558
-
],
5559
-
"support": {
5560
-
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
5561
-
},
5562
-
"funding": [
5563
-
{
5564
-
"url": "https://symfony.com/sponsor",
5565
-
"type": "custom"
5566
-
},
5567
-
{
5568
-
"url": "https://github.com/fabpot",
5569
-
"type": "github"
5570
-
},
5571
-
{
5572
-
"url": "https://github.com/nicolas-grekas",
5573
-
"type": "github"
5574
-
},
5575
-
{
5576
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5577
-
"type": "tidelift"
5578
-
}
5579
-
],
5580
-
"time": "2024-09-09T11:45:10+00:00"
5581
-
},
5582
-
{
5583
-
"name": "symfony/polyfill-mbstring",
5584
-
"version": "v1.33.0",
5585
-
"source": {
5586
-
"type": "git",
5587
-
"url": "https://github.com/symfony/polyfill-mbstring.git",
5588
-
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
5589
-
},
5590
-
"dist": {
5591
-
"type": "zip",
5592
-
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
5593
-
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
5594
-
"shasum": ""
5595
-
},
5596
-
"require": {
5597
-
"ext-iconv": "*",
5598
-
"php": ">=7.2"
5599
-
},
5600
-
"provide": {
5601
-
"ext-mbstring": "*"
5602
-
},
5603
-
"suggest": {
5604
-
"ext-mbstring": "For best performance"
5605
-
},
5606
-
"type": "library",
5607
-
"extra": {
5608
-
"thanks": {
5609
-
"url": "https://github.com/symfony/polyfill",
5610
-
"name": "symfony/polyfill"
5611
-
}
5612
-
},
5613
-
"autoload": {
5614
-
"files": [
5615
-
"bootstrap.php"
5616
-
],
5617
-
"psr-4": {
5618
-
"Symfony\\Polyfill\\Mbstring\\": ""
5619
-
}
5620
-
},
5621
-
"notification-url": "https://packagist.org/downloads/",
5622
-
"license": [
5623
-
"MIT"
5624
-
],
5625
-
"authors": [
5626
-
{
5627
-
"name": "Nicolas Grekas",
5628
-
"email": "p@tchwork.com"
5629
-
},
5630
-
{
5631
-
"name": "Symfony Community",
5632
-
"homepage": "https://symfony.com/contributors"
5633
-
}
5634
-
],
5635
-
"description": "Symfony polyfill for the Mbstring extension",
5636
-
"homepage": "https://symfony.com",
5637
-
"keywords": [
5638
-
"compatibility",
5639
-
"mbstring",
5640
-
"polyfill",
5641
-
"portable",
5642
-
"shim"
5643
-
],
5644
-
"support": {
5645
-
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
5646
-
},
5647
-
"funding": [
5648
-
{
5649
-
"url": "https://symfony.com/sponsor",
5650
-
"type": "custom"
5651
-
},
5652
-
{
5653
-
"url": "https://github.com/fabpot",
5654
-
"type": "github"
5655
-
},
5656
-
{
5657
-
"url": "https://github.com/nicolas-grekas",
5658
-
"type": "github"
5659
-
},
5660
-
{
5661
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5662
-
"type": "tidelift"
5663
-
}
5664
-
],
5665
-
"time": "2024-12-23T08:48:59+00:00"
5666
-
},
5667
-
{
5668
-
"name": "symfony/polyfill-php80",
5669
-
"version": "v1.33.0",
5670
-
"source": {
5671
-
"type": "git",
5672
-
"url": "https://github.com/symfony/polyfill-php80.git",
5673
-
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
5674
-
},
5675
-
"dist": {
5676
-
"type": "zip",
5677
-
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
5678
-
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
5679
-
"shasum": ""
5680
-
},
5681
-
"require": {
5682
-
"php": ">=7.2"
5683
-
},
5684
-
"type": "library",
5685
-
"extra": {
5686
-
"thanks": {
5687
-
"url": "https://github.com/symfony/polyfill",
5688
-
"name": "symfony/polyfill"
5689
-
}
5690
-
},
5691
-
"autoload": {
5692
-
"files": [
5693
-
"bootstrap.php"
5694
-
],
5695
-
"psr-4": {
5696
-
"Symfony\\Polyfill\\Php80\\": ""
5697
-
},
5698
-
"classmap": [
5699
-
"Resources/stubs"
5700
-
]
5701
-
},
5702
-
"notification-url": "https://packagist.org/downloads/",
5703
-
"license": [
5704
-
"MIT"
5705
-
],
5706
-
"authors": [
5707
-
{
5708
-
"name": "Ion Bazan",
5709
-
"email": "ion.bazan@gmail.com"
5710
-
},
5711
-
{
5712
-
"name": "Nicolas Grekas",
5713
-
"email": "p@tchwork.com"
5714
-
},
5715
-
{
5716
-
"name": "Symfony Community",
5717
-
"homepage": "https://symfony.com/contributors"
5718
-
}
5719
-
],
5720
-
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
5721
-
"homepage": "https://symfony.com",
5722
-
"keywords": [
5723
-
"compatibility",
5724
-
"polyfill",
5725
-
"portable",
5726
-
"shim"
5727
-
],
5728
-
"support": {
5729
-
"source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
5730
-
},
5731
-
"funding": [
5732
-
{
5733
-
"url": "https://symfony.com/sponsor",
5734
-
"type": "custom"
5735
-
},
5736
-
{
5737
-
"url": "https://github.com/fabpot",
5738
-
"type": "github"
5739
-
},
5740
-
{
5741
-
"url": "https://github.com/nicolas-grekas",
5742
-
"type": "github"
5743
-
},
5744
-
{
5745
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5746
-
"type": "tidelift"
5747
-
}
5748
-
],
5749
-
"time": "2025-01-02T08:10:11+00:00"
5750
-
},
5751
-
{
5752
-
"name": "symfony/polyfill-php83",
5753
-
"version": "v1.33.0",
5754
-
"source": {
5755
-
"type": "git",
5756
-
"url": "https://github.com/symfony/polyfill-php83.git",
5757
-
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
5758
-
},
5759
-
"dist": {
5760
-
"type": "zip",
5761
-
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
5762
-
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
5763
-
"shasum": ""
5764
-
},
5765
-
"require": {
5766
-
"php": ">=7.2"
5767
-
},
5768
-
"type": "library",
5769
-
"extra": {
5770
-
"thanks": {
5771
-
"url": "https://github.com/symfony/polyfill",
5772
-
"name": "symfony/polyfill"
5773
-
}
5774
-
},
5775
-
"autoload": {
5776
-
"files": [
5777
-
"bootstrap.php"
5778
-
],
5779
-
"psr-4": {
5780
-
"Symfony\\Polyfill\\Php83\\": ""
5781
-
},
5782
-
"classmap": [
5783
-
"Resources/stubs"
5784
-
]
5785
-
},
5786
-
"notification-url": "https://packagist.org/downloads/",
5787
-
"license": [
5788
-
"MIT"
5789
-
],
5790
-
"authors": [
5791
-
{
5792
-
"name": "Nicolas Grekas",
5793
-
"email": "p@tchwork.com"
5794
-
},
5795
-
{
5796
-
"name": "Symfony Community",
5797
-
"homepage": "https://symfony.com/contributors"
5798
-
}
5799
-
],
5800
-
"description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
5801
-
"homepage": "https://symfony.com",
5802
-
"keywords": [
5803
-
"compatibility",
5804
-
"polyfill",
5805
-
"portable",
5806
-
"shim"
5807
-
],
5808
-
"support": {
5809
-
"source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
5810
-
},
5811
-
"funding": [
5812
-
{
5813
-
"url": "https://symfony.com/sponsor",
5814
-
"type": "custom"
5815
-
},
5816
-
{
5817
-
"url": "https://github.com/fabpot",
5818
-
"type": "github"
5819
-
},
5820
-
{
5821
-
"url": "https://github.com/nicolas-grekas",
5822
-
"type": "github"
5823
-
},
5824
-
{
5825
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5826
-
"type": "tidelift"
5827
-
}
5828
-
],
5829
-
"time": "2025-07-08T02:45:35+00:00"
5830
-
},
5831
-
{
5832
-
"name": "symfony/polyfill-uuid",
5833
-
"version": "v1.33.0",
5834
-
"source": {
5835
-
"type": "git",
5836
-
"url": "https://github.com/symfony/polyfill-uuid.git",
5837
-
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2"
5838
-
},
5839
-
"dist": {
5840
-
"type": "zip",
5841
-
"url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
5842
-
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
5843
-
"shasum": ""
5844
-
},
5845
-
"require": {
5846
-
"php": ">=7.2"
5847
-
},
5848
-
"provide": {
5849
-
"ext-uuid": "*"
5850
-
},
5851
-
"suggest": {
5852
-
"ext-uuid": "For best performance"
5853
-
},
5854
-
"type": "library",
5855
-
"extra": {
5856
-
"thanks": {
5857
-
"url": "https://github.com/symfony/polyfill",
5858
-
"name": "symfony/polyfill"
5859
-
}
5860
-
},
5861
-
"autoload": {
5862
-
"files": [
5863
-
"bootstrap.php"
5864
-
],
5865
-
"psr-4": {
5866
-
"Symfony\\Polyfill\\Uuid\\": ""
5867
-
}
5868
-
},
5869
-
"notification-url": "https://packagist.org/downloads/",
5870
-
"license": [
5871
-
"MIT"
5872
-
],
5873
-
"authors": [
5874
-
{
5875
-
"name": "Grรฉgoire Pineau",
5876
-
"email": "lyrixx@lyrixx.info"
5877
-
},
5878
-
{
5879
-
"name": "Symfony Community",
5880
-
"homepage": "https://symfony.com/contributors"
5881
-
}
5882
-
],
5883
-
"description": "Symfony polyfill for uuid functions",
5884
-
"homepage": "https://symfony.com",
5885
-
"keywords": [
5886
-
"compatibility",
5887
-
"polyfill",
5888
-
"portable",
5889
-
"uuid"
5890
-
],
5891
-
"support": {
5892
-
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0"
5893
-
},
5894
-
"funding": [
5895
-
{
5896
-
"url": "https://symfony.com/sponsor",
5897
-
"type": "custom"
5898
-
},
5899
-
{
5900
-
"url": "https://github.com/fabpot",
5901
-
"type": "github"
5902
-
},
5903
-
{
5904
-
"url": "https://github.com/nicolas-grekas",
5905
-
"type": "github"
5906
-
},
5907
-
{
5908
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5909
-
"type": "tidelift"
5910
-
}
5911
-
],
5912
-
"time": "2024-09-09T11:45:10+00:00"
5913
-
},
5914
-
{
5915
-
"name": "symfony/process",
5916
-
"version": "v7.3.4",
5917
-
"source": {
5918
-
"type": "git",
5919
-
"url": "https://github.com/symfony/process.git",
5920
-
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
5921
-
},
5922
-
"dist": {
5923
-
"type": "zip",
5924
-
"url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
5925
-
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
5926
-
"shasum": ""
5927
-
},
5928
-
"require": {
5929
-
"php": ">=8.2"
5930
-
},
5931
-
"type": "library",
5932
-
"autoload": {
5933
-
"psr-4": {
5934
-
"Symfony\\Component\\Process\\": ""
5935
-
},
5936
-
"exclude-from-classmap": [
5937
-
"/Tests/"
5938
-
]
5939
-
},
5940
-
"notification-url": "https://packagist.org/downloads/",
5941
-
"license": [
5942
-
"MIT"
5943
-
],
5944
-
"authors": [
5945
-
{
5946
-
"name": "Fabien Potencier",
5947
-
"email": "fabien@symfony.com"
5948
-
},
5949
-
{
5950
-
"name": "Symfony Community",
5951
-
"homepage": "https://symfony.com/contributors"
5952
-
}
5953
-
],
5954
-
"description": "Executes commands in sub-processes",
5955
-
"homepage": "https://symfony.com",
5956
-
"support": {
5957
-
"source": "https://github.com/symfony/process/tree/v7.3.4"
5958
-
},
5959
-
"funding": [
5960
-
{
5961
-
"url": "https://symfony.com/sponsor",
5962
-
"type": "custom"
5963
-
},
5964
-
{
5965
-
"url": "https://github.com/fabpot",
5966
-
"type": "github"
5967
-
},
5968
-
{
5969
-
"url": "https://github.com/nicolas-grekas",
5970
-
"type": "github"
5971
-
},
5972
-
{
5973
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
5974
-
"type": "tidelift"
5975
-
}
5976
-
],
5977
-
"time": "2025-09-11T10:12:26+00:00"
5978
-
},
5979
-
{
5980
-
"name": "symfony/routing",
5981
-
"version": "v7.3.4",
5982
-
"source": {
5983
-
"type": "git",
5984
-
"url": "https://github.com/symfony/routing.git",
5985
-
"reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c"
5986
-
},
5987
-
"dist": {
5988
-
"type": "zip",
5989
-
"url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c",
5990
-
"reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c",
5991
-
"shasum": ""
5992
-
},
5993
-
"require": {
5994
-
"php": ">=8.2",
5995
-
"symfony/deprecation-contracts": "^2.5|^3"
5996
-
},
5997
-
"conflict": {
5998
-
"symfony/config": "<6.4",
5999
-
"symfony/dependency-injection": "<6.4",
6000
-
"symfony/yaml": "<6.4"
6001
-
},
6002
-
"require-dev": {
6003
-
"psr/log": "^1|^2|^3",
6004
-
"symfony/config": "^6.4|^7.0",
6005
-
"symfony/dependency-injection": "^6.4|^7.0",
6006
-
"symfony/expression-language": "^6.4|^7.0",
6007
-
"symfony/http-foundation": "^6.4|^7.0",
6008
-
"symfony/yaml": "^6.4|^7.0"
6009
-
},
6010
-
"type": "library",
6011
-
"autoload": {
6012
-
"psr-4": {
6013
-
"Symfony\\Component\\Routing\\": ""
6014
-
},
6015
-
"exclude-from-classmap": [
6016
-
"/Tests/"
6017
-
]
6018
-
},
6019
-
"notification-url": "https://packagist.org/downloads/",
6020
-
"license": [
6021
-
"MIT"
6022
-
],
6023
-
"authors": [
6024
-
{
6025
-
"name": "Fabien Potencier",
6026
-
"email": "fabien@symfony.com"
6027
-
},
6028
-
{
6029
-
"name": "Symfony Community",
6030
-
"homepage": "https://symfony.com/contributors"
6031
-
}
6032
-
],
6033
-
"description": "Maps an HTTP request to a set of configuration variables",
6034
-
"homepage": "https://symfony.com",
6035
-
"keywords": [
6036
-
"router",
6037
-
"routing",
6038
-
"uri",
6039
-
"url"
6040
-
],
6041
-
"support": {
6042
-
"source": "https://github.com/symfony/routing/tree/v7.3.4"
6043
-
},
6044
-
"funding": [
6045
-
{
6046
-
"url": "https://symfony.com/sponsor",
6047
-
"type": "custom"
6048
-
},
6049
-
{
6050
-
"url": "https://github.com/fabpot",
6051
-
"type": "github"
6052
-
},
6053
-
{
6054
-
"url": "https://github.com/nicolas-grekas",
6055
-
"type": "github"
6056
-
},
6057
-
{
6058
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
6059
-
"type": "tidelift"
6060
-
}
6061
-
],
6062
-
"time": "2025-09-11T10:12:26+00:00"
6063
-
},
6064
-
{
6065
-
"name": "symfony/service-contracts",
6066
-
"version": "v3.6.0",
6067
-
"source": {
6068
-
"type": "git",
6069
-
"url": "https://github.com/symfony/service-contracts.git",
6070
-
"reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
6071
-
},
6072
-
"dist": {
6073
-
"type": "zip",
6074
-
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
6075
-
"reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
6076
-
"shasum": ""
6077
-
},
6078
-
"require": {
6079
-
"php": ">=8.1",
6080
-
"psr/container": "^1.1|^2.0",
6081
-
"symfony/deprecation-contracts": "^2.5|^3"
6082
-
},
6083
-
"conflict": {
6084
-
"ext-psr": "<1.1|>=2"
6085
-
},
6086
-
"type": "library",
6087
-
"extra": {
6088
-
"thanks": {
6089
-
"url": "https://github.com/symfony/contracts",
6090
-
"name": "symfony/contracts"
6091
-
},
6092
-
"branch-alias": {
6093
-
"dev-main": "3.6-dev"
6094
-
}
6095
-
},
6096
-
"autoload": {
6097
-
"psr-4": {
6098
-
"Symfony\\Contracts\\Service\\": ""
6099
-
},
6100
-
"exclude-from-classmap": [
6101
-
"/Test/"
6102
-
]
6103
-
},
6104
-
"notification-url": "https://packagist.org/downloads/",
6105
-
"license": [
6106
-
"MIT"
6107
-
],
6108
-
"authors": [
6109
-
{
6110
-
"name": "Nicolas Grekas",
6111
-
"email": "p@tchwork.com"
6112
-
},
6113
-
{
6114
-
"name": "Symfony Community",
6115
-
"homepage": "https://symfony.com/contributors"
6116
-
}
6117
-
],
6118
-
"description": "Generic abstractions related to writing services",
6119
-
"homepage": "https://symfony.com",
6120
-
"keywords": [
6121
-
"abstractions",
6122
-
"contracts",
6123
-
"decoupling",
6124
-
"interfaces",
6125
-
"interoperability",
6126
-
"standards"
6127
-
],
6128
-
"support": {
6129
-
"source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
6130
-
},
6131
-
"funding": [
6132
-
{
6133
-
"url": "https://symfony.com/sponsor",
6134
-
"type": "custom"
6135
-
},
6136
-
{
6137
-
"url": "https://github.com/fabpot",
6138
-
"type": "github"
6139
-
},
6140
-
{
6141
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
6142
-
"type": "tidelift"
6143
-
}
6144
-
],
6145
-
"time": "2025-04-25T09:37:31+00:00"
6146
-
},
6147
-
{
6148
-
"name": "symfony/string",
6149
-
"version": "v7.3.4",
6150
-
"source": {
6151
-
"type": "git",
6152
-
"url": "https://github.com/symfony/string.git",
6153
-
"reference": "f96476035142921000338bad71e5247fbc138872"
6154
-
},
6155
-
"dist": {
6156
-
"type": "zip",
6157
-
"url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
6158
-
"reference": "f96476035142921000338bad71e5247fbc138872",
6159
-
"shasum": ""
6160
-
},
6161
-
"require": {
6162
-
"php": ">=8.2",
6163
-
"symfony/polyfill-ctype": "~1.8",
6164
-
"symfony/polyfill-intl-grapheme": "~1.0",
6165
-
"symfony/polyfill-intl-normalizer": "~1.0",
6166
-
"symfony/polyfill-mbstring": "~1.0"
6167
-
},
6168
-
"conflict": {
6169
-
"symfony/translation-contracts": "<2.5"
6170
-
},
6171
-
"require-dev": {
6172
-
"symfony/emoji": "^7.1",
6173
-
"symfony/http-client": "^6.4|^7.0",
6174
-
"symfony/intl": "^6.4|^7.0",
6175
-
"symfony/translation-contracts": "^2.5|^3.0",
6176
-
"symfony/var-exporter": "^6.4|^7.0"
6177
-
},
6178
-
"type": "library",
6179
-
"autoload": {
6180
-
"files": [
6181
-
"Resources/functions.php"
6182
-
],
6183
-
"psr-4": {
6184
-
"Symfony\\Component\\String\\": ""
6185
-
},
6186
-
"exclude-from-classmap": [
6187
-
"/Tests/"
6188
-
]
6189
-
},
6190
-
"notification-url": "https://packagist.org/downloads/",
6191
-
"license": [
6192
-
"MIT"
6193
-
],
6194
-
"authors": [
6195
-
{
6196
-
"name": "Nicolas Grekas",
6197
-
"email": "p@tchwork.com"
6198
-
},
6199
-
{
6200
-
"name": "Symfony Community",
6201
-
"homepage": "https://symfony.com/contributors"
6202
-
}
6203
-
],
6204
-
"description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
6205
-
"homepage": "https://symfony.com",
6206
-
"keywords": [
6207
-
"grapheme",
6208
-
"i18n",
6209
-
"string",
6210
-
"unicode",
6211
-
"utf-8",
6212
-
"utf8"
6213
-
],
6214
-
"support": {
6215
-
"source": "https://github.com/symfony/string/tree/v7.3.4"
6216
-
},
6217
-
"funding": [
6218
-
{
6219
-
"url": "https://symfony.com/sponsor",
6220
-
"type": "custom"
6221
-
},
6222
-
{
6223
-
"url": "https://github.com/fabpot",
6224
-
"type": "github"
6225
-
},
6226
-
{
6227
-
"url": "https://github.com/nicolas-grekas",
6228
-
"type": "github"
6229
-
},
6230
-
{
6231
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
6232
-
"type": "tidelift"
6233
-
}
6234
-
],
6235
-
"time": "2025-09-11T14:36:48+00:00"
6236
-
},
6237
-
{
6238
-
"name": "symfony/translation",
6239
-
"version": "v7.3.4",
6240
-
"source": {
6241
-
"type": "git",
6242
-
"url": "https://github.com/symfony/translation.git",
6243
-
"reference": "ec25870502d0c7072d086e8ffba1420c85965174"
6244
-
},
6245
-
"dist": {
6246
-
"type": "zip",
6247
-
"url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174",
6248
-
"reference": "ec25870502d0c7072d086e8ffba1420c85965174",
6249
-
"shasum": ""
6250
-
},
6251
-
"require": {
6252
-
"php": ">=8.2",
6253
-
"symfony/deprecation-contracts": "^2.5|^3",
6254
-
"symfony/polyfill-mbstring": "~1.0",
6255
-
"symfony/translation-contracts": "^2.5|^3.0"
6256
-
},
6257
-
"conflict": {
6258
-
"nikic/php-parser": "<5.0",
6259
-
"symfony/config": "<6.4",
6260
-
"symfony/console": "<6.4",
6261
-
"symfony/dependency-injection": "<6.4",
6262
-
"symfony/http-client-contracts": "<2.5",
6263
-
"symfony/http-kernel": "<6.4",
6264
-
"symfony/service-contracts": "<2.5",
6265
-
"symfony/twig-bundle": "<6.4",
6266
-
"symfony/yaml": "<6.4"
6267
-
},
6268
-
"provide": {
6269
-
"symfony/translation-implementation": "2.3|3.0"
6270
-
},
6271
-
"require-dev": {
6272
-
"nikic/php-parser": "^5.0",
6273
-
"psr/log": "^1|^2|^3",
6274
-
"symfony/config": "^6.4|^7.0",
6275
-
"symfony/console": "^6.4|^7.0",
6276
-
"symfony/dependency-injection": "^6.4|^7.0",
6277
-
"symfony/finder": "^6.4|^7.0",
6278
-
"symfony/http-client-contracts": "^2.5|^3.0",
6279
-
"symfony/http-kernel": "^6.4|^7.0",
6280
-
"symfony/intl": "^6.4|^7.0",
6281
-
"symfony/polyfill-intl-icu": "^1.21",
6282
-
"symfony/routing": "^6.4|^7.0",
6283
-
"symfony/service-contracts": "^2.5|^3",
6284
-
"symfony/yaml": "^6.4|^7.0"
6285
-
},
6286
-
"type": "library",
6287
-
"autoload": {
6288
-
"files": [
6289
-
"Resources/functions.php"
6290
-
],
6291
-
"psr-4": {
6292
-
"Symfony\\Component\\Translation\\": ""
6293
-
},
6294
-
"exclude-from-classmap": [
6295
-
"/Tests/"
6296
-
]
6297
-
},
6298
-
"notification-url": "https://packagist.org/downloads/",
6299
-
"license": [
6300
-
"MIT"
6301
-
],
6302
-
"authors": [
6303
-
{
6304
-
"name": "Fabien Potencier",
6305
-
"email": "fabien@symfony.com"
6306
-
},
6307
-
{
6308
-
"name": "Symfony Community",
6309
-
"homepage": "https://symfony.com/contributors"
6310
-
}
6311
-
],
6312
-
"description": "Provides tools to internationalize your application",
6313
-
"homepage": "https://symfony.com",
6314
-
"support": {
6315
-
"source": "https://github.com/symfony/translation/tree/v7.3.4"
6316
-
},
6317
-
"funding": [
6318
-
{
6319
-
"url": "https://symfony.com/sponsor",
6320
-
"type": "custom"
6321
-
},
6322
-
{
6323
-
"url": "https://github.com/fabpot",
6324
-
"type": "github"
6325
-
},
6326
-
{
6327
-
"url": "https://github.com/nicolas-grekas",
6328
-
"type": "github"
6329
-
},
6330
-
{
6331
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
6332
-
"type": "tidelift"
6333
-
}
6334
-
],
6335
-
"time": "2025-09-07T11:39:36+00:00"
6336
-
},
6337
-
{
6338
-
"name": "symfony/translation-contracts",
6339
-
"version": "v3.6.0",
6340
-
"source": {
6341
-
"type": "git",
6342
-
"url": "https://github.com/symfony/translation-contracts.git",
6343
-
"reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d"
6344
-
},
6345
-
"dist": {
6346
-
"type": "zip",
6347
-
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
6348
-
"reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
6349
-
"shasum": ""
6350
-
},
6351
-
"require": {
6352
-
"php": ">=8.1"
6353
-
},
6354
-
"type": "library",
6355
-
"extra": {
6356
-
"thanks": {
6357
-
"url": "https://github.com/symfony/contracts",
6358
-
"name": "symfony/contracts"
6359
-
},
6360
-
"branch-alias": {
6361
-
"dev-main": "3.6-dev"
6362
-
}
6363
-
},
6364
-
"autoload": {
6365
-
"psr-4": {
6366
-
"Symfony\\Contracts\\Translation\\": ""
6367
-
},
6368
-
"exclude-from-classmap": [
6369
-
"/Test/"
6370
-
]
6371
-
},
6372
-
"notification-url": "https://packagist.org/downloads/",
6373
-
"license": [
6374
-
"MIT"
6375
-
],
6376
-
"authors": [
6377
-
{
6378
-
"name": "Nicolas Grekas",
6379
-
"email": "p@tchwork.com"
6380
-
},
6381
-
{
6382
-
"name": "Symfony Community",
6383
-
"homepage": "https://symfony.com/contributors"
6384
-
}
6385
-
],
6386
-
"description": "Generic abstractions related to translation",
6387
-
"homepage": "https://symfony.com",
6388
-
"keywords": [
6389
-
"abstractions",
6390
-
"contracts",
6391
-
"decoupling",
6392
-
"interfaces",
6393
-
"interoperability",
6394
-
"standards"
6395
-
],
6396
-
"support": {
6397
-
"source": "https://github.com/symfony/translation-contracts/tree/v3.6.0"
6398
-
},
6399
-
"funding": [
6400
-
{
6401
-
"url": "https://symfony.com/sponsor",
6402
-
"type": "custom"
6403
-
},
6404
-
{
6405
-
"url": "https://github.com/fabpot",
6406
-
"type": "github"
6407
-
},
6408
-
{
6409
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
6410
-
"type": "tidelift"
6411
-
}
6412
-
],
6413
-
"time": "2024-09-27T08:32:26+00:00"
6414
-
},
6415
-
{
6416
-
"name": "symfony/uid",
6417
-
"version": "v7.3.1",
6418
-
"source": {
6419
-
"type": "git",
6420
-
"url": "https://github.com/symfony/uid.git",
6421
-
"reference": "a69f69f3159b852651a6bf45a9fdd149520525bb"
6422
-
},
6423
-
"dist": {
6424
-
"type": "zip",
6425
-
"url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb",
6426
-
"reference": "a69f69f3159b852651a6bf45a9fdd149520525bb",
6427
-
"shasum": ""
6428
-
},
6429
-
"require": {
6430
-
"php": ">=8.2",
6431
-
"symfony/polyfill-uuid": "^1.15"
6432
-
},
6433
-
"require-dev": {
6434
-
"symfony/console": "^6.4|^7.0"
6435
-
},
6436
-
"type": "library",
6437
-
"autoload": {
6438
-
"psr-4": {
6439
-
"Symfony\\Component\\Uid\\": ""
6440
-
},
6441
-
"exclude-from-classmap": [
6442
-
"/Tests/"
6443
-
]
6444
-
},
6445
-
"notification-url": "https://packagist.org/downloads/",
6446
-
"license": [
6447
-
"MIT"
6448
-
],
6449
-
"authors": [
6450
-
{
6451
-
"name": "Grรฉgoire Pineau",
6452
-
"email": "lyrixx@lyrixx.info"
6453
-
},
6454
-
{
6455
-
"name": "Nicolas Grekas",
6456
-
"email": "p@tchwork.com"
6457
-
},
6458
-
{
6459
-
"name": "Symfony Community",
6460
-
"homepage": "https://symfony.com/contributors"
6461
-
}
6462
-
],
6463
-
"description": "Provides an object-oriented API to generate and represent UIDs",
6464
-
"homepage": "https://symfony.com",
6465
-
"keywords": [
6466
-
"UID",
6467
-
"ulid",
6468
-
"uuid"
6469
-
],
6470
-
"support": {
6471
-
"source": "https://github.com/symfony/uid/tree/v7.3.1"
6472
-
},
6473
-
"funding": [
6474
-
{
6475
-
"url": "https://symfony.com/sponsor",
6476
-
"type": "custom"
6477
-
},
6478
-
{
6479
-
"url": "https://github.com/fabpot",
6480
-
"type": "github"
6481
-
},
6482
-
{
6483
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
6484
-
"type": "tidelift"
6485
-
}
6486
-
],
6487
-
"time": "2025-06-27T19:55:54+00:00"
6488
-
},
6489
-
{
6490
-
"name": "symfony/var-dumper",
6491
-
"version": "v7.3.5",
6492
-
"source": {
6493
-
"type": "git",
6494
-
"url": "https://github.com/symfony/var-dumper.git",
6495
-
"reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d"
6496
-
},
6497
-
"dist": {
6498
-
"type": "zip",
6499
-
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d",
6500
-
"reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d",
6501
-
"shasum": ""
6502
-
},
6503
-
"require": {
6504
-
"php": ">=8.2",
6505
-
"symfony/deprecation-contracts": "^2.5|^3",
6506
-
"symfony/polyfill-mbstring": "~1.0"
6507
-
},
6508
-
"conflict": {
6509
-
"symfony/console": "<6.4"
6510
-
},
6511
-
"require-dev": {
6512
-
"symfony/console": "^6.4|^7.0",
6513
-
"symfony/http-kernel": "^6.4|^7.0",
6514
-
"symfony/process": "^6.4|^7.0",
6515
-
"symfony/uid": "^6.4|^7.0",
6516
-
"twig/twig": "^3.12"
6517
-
},
6518
-
"bin": [
6519
-
"Resources/bin/var-dump-server"
6520
-
],
6521
-
"type": "library",
6522
-
"autoload": {
6523
-
"files": [
6524
-
"Resources/functions/dump.php"
6525
-
],
6526
-
"psr-4": {
6527
-
"Symfony\\Component\\VarDumper\\": ""
6528
-
},
6529
-
"exclude-from-classmap": [
6530
-
"/Tests/"
6531
-
]
6532
-
},
6533
-
"notification-url": "https://packagist.org/downloads/",
6534
-
"license": [
6535
-
"MIT"
6536
-
],
6537
-
"authors": [
6538
-
{
6539
-
"name": "Nicolas Grekas",
6540
-
"email": "p@tchwork.com"
6541
-
},
6542
-
{
6543
-
"name": "Symfony Community",
6544
-
"homepage": "https://symfony.com/contributors"
6545
-
}
6546
-
],
6547
-
"description": "Provides mechanisms for walking through any arbitrary PHP variable",
6548
-
"homepage": "https://symfony.com",
6549
-
"keywords": [
6550
-
"debug",
6551
-
"dump"
6552
-
],
6553
-
"support": {
6554
-
"source": "https://github.com/symfony/var-dumper/tree/v7.3.5"
6555
-
},
6556
-
"funding": [
6557
-
{
6558
-
"url": "https://symfony.com/sponsor",
6559
-
"type": "custom"
6560
-
},
6561
-
{
6562
-
"url": "https://github.com/fabpot",
6563
-
"type": "github"
6564
-
},
6565
-
{
6566
-
"url": "https://github.com/nicolas-grekas",
6567
-
"type": "github"
6568
-
},
6569
-
{
6570
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
6571
-
"type": "tidelift"
6572
-
}
6573
-
],
6574
-
"time": "2025-09-27T09:00:46+00:00"
6575
-
},
6576
-
{
6577
-
"name": "tijsverkoyen/css-to-inline-styles",
6578
-
"version": "v2.3.0",
6579
-
"source": {
6580
-
"type": "git",
6581
-
"url": "https://github.com/tijsverkoyen/CssToInlineStyles.git",
6582
-
"reference": "0d72ac1c00084279c1816675284073c5a337c20d"
6583
-
},
6584
-
"dist": {
6585
-
"type": "zip",
6586
-
"url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d",
6587
-
"reference": "0d72ac1c00084279c1816675284073c5a337c20d",
6588
-
"shasum": ""
6589
-
},
6590
-
"require": {
6591
-
"ext-dom": "*",
6592
-
"ext-libxml": "*",
6593
-
"php": "^7.4 || ^8.0",
6594
-
"symfony/css-selector": "^5.4 || ^6.0 || ^7.0"
6595
-
},
6596
-
"require-dev": {
6597
-
"phpstan/phpstan": "^2.0",
6598
-
"phpstan/phpstan-phpunit": "^2.0",
6599
-
"phpunit/phpunit": "^8.5.21 || ^9.5.10"
6600
-
},
6601
-
"type": "library",
6602
-
"extra": {
6603
-
"branch-alias": {
6604
-
"dev-master": "2.x-dev"
6605
-
}
6606
-
},
6607
-
"autoload": {
6608
-
"psr-4": {
6609
-
"TijsVerkoyen\\CssToInlineStyles\\": "src"
6610
-
}
6611
-
},
6612
-
"notification-url": "https://packagist.org/downloads/",
6613
-
"license": [
6614
-
"BSD-3-Clause"
6615
-
],
6616
-
"authors": [
6617
-
{
6618
-
"name": "Tijs Verkoyen",
6619
-
"email": "css_to_inline_styles@verkoyen.eu",
6620
-
"role": "Developer"
6621
-
}
6622
-
],
6623
-
"description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.",
6624
-
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
6625
-
"support": {
6626
-
"issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues",
6627
-
"source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0"
6628
-
},
6629
-
"time": "2024-12-21T16:25:41+00:00"
6630
-
},
6631
-
{
6632
-
"name": "vlucas/phpdotenv",
6633
-
"version": "v5.6.2",
6634
-
"source": {
6635
-
"type": "git",
6636
-
"url": "https://github.com/vlucas/phpdotenv.git",
6637
-
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
6638
-
},
6639
-
"dist": {
6640
-
"type": "zip",
6641
-
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
6642
-
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
6643
-
"shasum": ""
6644
-
},
6645
-
"require": {
6646
-
"ext-pcre": "*",
6647
-
"graham-campbell/result-type": "^1.1.3",
6648
-
"php": "^7.2.5 || ^8.0",
6649
-
"phpoption/phpoption": "^1.9.3",
6650
-
"symfony/polyfill-ctype": "^1.24",
6651
-
"symfony/polyfill-mbstring": "^1.24",
6652
-
"symfony/polyfill-php80": "^1.24"
6653
-
},
6654
-
"require-dev": {
6655
-
"bamarni/composer-bin-plugin": "^1.8.2",
6656
-
"ext-filter": "*",
6657
-
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
6658
-
},
6659
-
"suggest": {
6660
-
"ext-filter": "Required to use the boolean validator."
6661
-
},
6662
-
"type": "library",
6663
-
"extra": {
6664
-
"bamarni-bin": {
6665
-
"bin-links": true,
6666
-
"forward-command": false
6667
-
},
6668
-
"branch-alias": {
6669
-
"dev-master": "5.6-dev"
6670
-
}
6671
-
},
6672
-
"autoload": {
6673
-
"psr-4": {
6674
-
"Dotenv\\": "src/"
6675
-
}
6676
-
},
6677
-
"notification-url": "https://packagist.org/downloads/",
6678
-
"license": [
6679
-
"BSD-3-Clause"
6680
-
],
6681
-
"authors": [
6682
-
{
6683
-
"name": "Graham Campbell",
6684
-
"email": "hello@gjcampbell.co.uk",
6685
-
"homepage": "https://github.com/GrahamCampbell"
6686
-
},
6687
-
{
6688
-
"name": "Vance Lucas",
6689
-
"email": "vance@vancelucas.com",
6690
-
"homepage": "https://github.com/vlucas"
6691
-
}
6692
-
],
6693
-
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
6694
-
"keywords": [
6695
-
"dotenv",
6696
-
"env",
6697
-
"environment"
6698
-
],
6699
-
"support": {
6700
-
"issues": "https://github.com/vlucas/phpdotenv/issues",
6701
-
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
6702
-
},
6703
-
"funding": [
6704
-
{
6705
-
"url": "https://github.com/GrahamCampbell",
6706
-
"type": "github"
6707
-
},
6708
-
{
6709
-
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
6710
-
"type": "tidelift"
6711
-
}
6712
-
],
6713
-
"time": "2025-04-30T23:37:27+00:00"
6714
-
},
6715
-
{
6716
-
"name": "voku/portable-ascii",
6717
-
"version": "2.0.3",
6718
-
"source": {
6719
-
"type": "git",
6720
-
"url": "https://github.com/voku/portable-ascii.git",
6721
-
"reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d"
6722
-
},
6723
-
"dist": {
6724
-
"type": "zip",
6725
-
"url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
6726
-
"reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d",
6727
-
"shasum": ""
6728
-
},
6729
-
"require": {
6730
-
"php": ">=7.0.0"
6731
-
},
6732
-
"require-dev": {
6733
-
"phpunit/phpunit": "~6.0 || ~7.0 || ~9.0"
6734
-
},
6735
-
"suggest": {
6736
-
"ext-intl": "Use Intl for transliterator_transliterate() support"
6737
-
},
6738
-
"type": "library",
6739
-
"autoload": {
6740
-
"psr-4": {
6741
-
"voku\\": "src/voku/"
6742
-
}
6743
-
},
6744
-
"notification-url": "https://packagist.org/downloads/",
6745
-
"license": [
6746
-
"MIT"
6747
-
],
6748
-
"authors": [
6749
-
{
6750
-
"name": "Lars Moelleken",
6751
-
"homepage": "https://www.moelleken.org/"
6752
-
}
6753
-
],
6754
-
"description": "Portable ASCII library - performance optimized (ascii) string functions for php.",
6755
-
"homepage": "https://github.com/voku/portable-ascii",
6756
-
"keywords": [
6757
-
"ascii",
6758
-
"clean",
6759
-
"php"
6760
-
],
6761
-
"support": {
6762
-
"issues": "https://github.com/voku/portable-ascii/issues",
6763
-
"source": "https://github.com/voku/portable-ascii/tree/2.0.3"
6764
-
},
6765
-
"funding": [
6766
-
{
6767
-
"url": "https://www.paypal.me/moelleken",
6768
-
"type": "custom"
6769
-
},
6770
-
{
6771
-
"url": "https://github.com/voku",
6772
-
"type": "github"
6773
-
},
6774
-
{
6775
-
"url": "https://opencollective.com/portable-ascii",
6776
-
"type": "open_collective"
6777
-
},
6778
-
{
6779
-
"url": "https://www.patreon.com/voku",
6780
-
"type": "patreon"
6781
-
},
6782
-
{
6783
-
"url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii",
6784
-
"type": "tidelift"
6785
-
}
6786
-
],
6787
-
"time": "2024-11-21T01:49:47+00:00"
6788
-
},
6789
-
{
6790
-
"name": "webmozart/assert",
6791
-
"version": "1.12.1",
6792
-
"source": {
6793
-
"type": "git",
6794
-
"url": "https://github.com/webmozarts/assert.git",
6795
-
"reference": "9be6926d8b485f55b9229203f962b51ed377ba68"
6796
-
},
6797
-
"dist": {
6798
-
"type": "zip",
6799
-
"url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68",
6800
-
"reference": "9be6926d8b485f55b9229203f962b51ed377ba68",
6801
-
"shasum": ""
6802
-
},
6803
-
"require": {
6804
-
"ext-ctype": "*",
6805
-
"ext-date": "*",
6806
-
"ext-filter": "*",
6807
-
"php": "^7.2 || ^8.0"
6808
-
},
6809
-
"suggest": {
6810
-
"ext-intl": "",
6811
-
"ext-simplexml": "",
6812
-
"ext-spl": ""
6813
-
},
6814
-
"type": "library",
6815
-
"extra": {
6816
-
"branch-alias": {
6817
-
"dev-master": "1.10-dev"
6818
-
}
6819
-
},
6820
-
"autoload": {
6821
-
"psr-4": {
6822
-
"Webmozart\\Assert\\": "src/"
6823
-
}
6824
-
},
6825
-
"notification-url": "https://packagist.org/downloads/",
6826
-
"license": [
6827
-
"MIT"
6828
-
],
6829
-
"authors": [
6830
-
{
6831
-
"name": "Bernhard Schussek",
6832
-
"email": "bschussek@gmail.com"
6833
-
}
6834
-
],
6835
-
"description": "Assertions to validate method input/output with nice error messages.",
6836
-
"keywords": [
6837
-
"assert",
6838
-
"check",
6839
-
"validate"
6840
-
],
6841
-
"support": {
6842
-
"issues": "https://github.com/webmozarts/assert/issues",
6843
-
"source": "https://github.com/webmozarts/assert/tree/1.12.1"
6844
-
},
6845
-
"time": "2025-10-29T15:56:20+00:00"
6846
-
},
6847
-
{
6848
-
"name": "yocto/yoclib-multibase",
6849
-
"version": "v1.2.0",
6850
-
"source": {
6851
-
"type": "git",
6852
-
"url": "https://github.com/yocto/yoclib-multibase-php.git",
6853
-
"reference": "c7171897bf61dbc4a4cc6bb3f2fd5c3e62298e13"
6854
-
},
6855
-
"dist": {
6856
-
"type": "zip",
6857
-
"url": "https://api.github.com/repos/yocto/yoclib-multibase-php/zipball/c7171897bf61dbc4a4cc6bb3f2fd5c3e62298e13",
6858
-
"reference": "c7171897bf61dbc4a4cc6bb3f2fd5c3e62298e13",
6859
-
"shasum": ""
6860
-
},
6861
-
"require": {
6862
-
"ext-mbstring": "*",
6863
-
"php": "^7.4||^8"
6864
-
},
6865
-
"require-dev": {
6866
-
"phpunit/phpunit": "^7||^8||^9"
6867
-
},
6868
-
"type": "library",
6869
-
"autoload": {
6870
-
"psr-4": {
6871
-
"YOCLIB\\Multiformats\\Multibase\\": "src/"
6872
-
}
6873
-
},
6874
-
"notification-url": "https://packagist.org/downloads/",
6875
-
"license": [
6876
-
"GPL-3.0-or-later"
6877
-
],
6878
-
"description": "This yocLibrary enables your project to encode and decode Multibases in PHP.",
6879
-
"keywords": [
6880
-
"composer",
6881
-
"multibase",
6882
-
"multiformats",
6883
-
"php",
6884
-
"yoclib",
6885
-
"yocto"
6886
-
],
6887
-
"support": {
6888
-
"issues": "https://github.com/yocto/yoclib-multibase-php/issues",
6889
-
"source": "https://github.com/yocto/yoclib-multibase-php/tree/v1.2.0"
6890
-
},
6891
-
"time": "2024-06-05T13:42:01+00:00"
6892
-
}
6893
-
],
6894
-
"packages-dev": [
6895
-
{
6896
-
"name": "composer/semver",
6897
-
"version": "3.4.4",
6898
-
"source": {
6899
-
"type": "git",
6900
-
"url": "https://github.com/composer/semver.git",
6901
-
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
6902
-
},
6903
-
"dist": {
6904
-
"type": "zip",
6905
-
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
6906
-
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
6907
-
"shasum": ""
6908
-
},
6909
-
"require": {
6910
-
"php": "^5.3.2 || ^7.0 || ^8.0"
6911
-
},
6912
-
"require-dev": {
6913
-
"phpstan/phpstan": "^1.11",
6914
-
"symfony/phpunit-bridge": "^3 || ^7"
6915
-
},
6916
-
"type": "library",
6917
-
"extra": {
6918
-
"branch-alias": {
6919
-
"dev-main": "3.x-dev"
6920
-
}
6921
-
},
6922
-
"autoload": {
6923
-
"psr-4": {
6924
-
"Composer\\Semver\\": "src"
6925
-
}
6926
-
},
6927
-
"notification-url": "https://packagist.org/downloads/",
6928
-
"license": [
6929
-
"MIT"
6930
-
],
6931
-
"authors": [
6932
-
{
6933
-
"name": "Nils Adermann",
6934
-
"email": "naderman@naderman.de",
6935
-
"homepage": "http://www.naderman.de"
6936
-
},
6937
-
{
6938
-
"name": "Jordi Boggiano",
6939
-
"email": "j.boggiano@seld.be",
6940
-
"homepage": "http://seld.be"
6941
-
},
6942
-
{
6943
-
"name": "Rob Bast",
6944
-
"email": "rob.bast@gmail.com",
6945
-
"homepage": "http://robbast.nl"
6946
-
}
6947
-
],
6948
-
"description": "Semver library that offers utilities, version constraint parsing and validation.",
6949
-
"keywords": [
6950
-
"semantic",
6951
-
"semver",
6952
-
"validation",
6953
-
"versioning"
6954
-
],
6955
-
"support": {
6956
-
"irc": "ircs://irc.libera.chat:6697/composer",
6957
-
"issues": "https://github.com/composer/semver/issues",
6958
-
"source": "https://github.com/composer/semver/tree/3.4.4"
6959
-
},
6960
-
"funding": [
6961
-
{
6962
-
"url": "https://packagist.com",
6963
-
"type": "custom"
6964
-
},
6965
-
{
6966
-
"url": "https://github.com/composer",
6967
-
"type": "github"
6968
-
}
6969
-
],
6970
-
"time": "2025-08-20T19:15:30+00:00"
6971
-
},
6972
-
{
6973
-
"name": "fakerphp/faker",
6974
-
"version": "v1.24.1",
6975
-
"source": {
6976
-
"type": "git",
6977
-
"url": "https://github.com/FakerPHP/Faker.git",
6978
-
"reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5"
6979
-
},
6980
-
"dist": {
6981
-
"type": "zip",
6982
-
"url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
6983
-
"reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
6984
-
"shasum": ""
6985
-
},
6986
-
"require": {
6987
-
"php": "^7.4 || ^8.0",
6988
-
"psr/container": "^1.0 || ^2.0",
6989
-
"symfony/deprecation-contracts": "^2.2 || ^3.0"
6990
-
},
6991
-
"conflict": {
6992
-
"fzaninotto/faker": "*"
6993
-
},
6994
-
"require-dev": {
6995
-
"bamarni/composer-bin-plugin": "^1.4.1",
6996
-
"doctrine/persistence": "^1.3 || ^2.0",
6997
-
"ext-intl": "*",
6998
-
"phpunit/phpunit": "^9.5.26",
6999
-
"symfony/phpunit-bridge": "^5.4.16"
7000
-
},
7001
-
"suggest": {
7002
-
"doctrine/orm": "Required to use Faker\\ORM\\Doctrine",
7003
-
"ext-curl": "Required by Faker\\Provider\\Image to download images.",
7004
-
"ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
7005
-
"ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
7006
-
"ext-mbstring": "Required for multibyte Unicode string functionality."
7007
-
},
7008
-
"type": "library",
7009
-
"autoload": {
7010
-
"psr-4": {
7011
-
"Faker\\": "src/Faker/"
7012
-
}
7013
-
},
7014
-
"notification-url": "https://packagist.org/downloads/",
7015
-
"license": [
7016
-
"MIT"
7017
-
],
7018
-
"authors": [
7019
-
{
7020
-
"name": "Franรงois Zaninotto"
7021
-
}
7022
-
],
7023
-
"description": "Faker is a PHP library that generates fake data for you.",
7024
-
"keywords": [
7025
-
"data",
7026
-
"faker",
7027
-
"fixtures"
7028
-
],
7029
-
"support": {
7030
-
"issues": "https://github.com/FakerPHP/Faker/issues",
7031
-
"source": "https://github.com/FakerPHP/Faker/tree/v1.24.1"
7032
-
},
7033
-
"time": "2024-11-21T13:46:39+00:00"
7034
-
},
7035
-
{
7036
-
"name": "filp/whoops",
7037
-
"version": "2.18.4",
7038
-
"source": {
7039
-
"type": "git",
7040
-
"url": "https://github.com/filp/whoops.git",
7041
-
"reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d"
7042
-
},
7043
-
"dist": {
7044
-
"type": "zip",
7045
-
"url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d",
7046
-
"reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d",
7047
-
"shasum": ""
7048
-
},
7049
-
"require": {
7050
-
"php": "^7.1 || ^8.0",
7051
-
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
7052
-
},
7053
-
"require-dev": {
7054
-
"mockery/mockery": "^1.0",
7055
-
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
7056
-
"symfony/var-dumper": "^4.0 || ^5.0"
7057
-
},
7058
-
"suggest": {
7059
-
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
7060
-
"whoops/soap": "Formats errors as SOAP responses"
7061
-
},
7062
-
"type": "library",
7063
-
"extra": {
7064
-
"branch-alias": {
7065
-
"dev-master": "2.7-dev"
7066
-
}
7067
-
},
7068
-
"autoload": {
7069
-
"psr-4": {
7070
-
"Whoops\\": "src/Whoops/"
7071
-
}
7072
-
},
7073
-
"notification-url": "https://packagist.org/downloads/",
7074
-
"license": [
7075
-
"MIT"
7076
-
],
7077
-
"authors": [
7078
-
{
7079
-
"name": "Filipe Dobreira",
7080
-
"homepage": "https://github.com/filp",
7081
-
"role": "Developer"
7082
-
}
7083
-
],
7084
-
"description": "php error handling for cool kids",
7085
-
"homepage": "https://filp.github.io/whoops/",
7086
-
"keywords": [
7087
-
"error",
7088
-
"exception",
7089
-
"handling",
7090
-
"library",
7091
-
"throwable",
7092
-
"whoops"
7093
-
],
7094
-
"support": {
7095
-
"issues": "https://github.com/filp/whoops/issues",
7096
-
"source": "https://github.com/filp/whoops/tree/2.18.4"
7097
-
},
7098
-
"funding": [
7099
-
{
7100
-
"url": "https://github.com/denis-sokolov",
7101
-
"type": "github"
7102
-
}
7103
-
],
7104
-
"time": "2025-08-08T12:00:00+00:00"
7105
-
},
7106
-
{
7107
-
"name": "hamcrest/hamcrest-php",
7108
-
"version": "v2.1.1",
7109
-
"source": {
7110
-
"type": "git",
7111
-
"url": "https://github.com/hamcrest/hamcrest-php.git",
7112
-
"reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487"
7113
-
},
7114
-
"dist": {
7115
-
"type": "zip",
7116
-
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487",
7117
-
"reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487",
7118
-
"shasum": ""
7119
-
},
7120
-
"require": {
7121
-
"php": "^7.4|^8.0"
7122
-
},
7123
-
"replace": {
7124
-
"cordoval/hamcrest-php": "*",
7125
-
"davedevelopment/hamcrest-php": "*",
7126
-
"kodova/hamcrest-php": "*"
7127
-
},
7128
-
"require-dev": {
7129
-
"phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0",
7130
-
"phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0"
7131
-
},
7132
-
"type": "library",
7133
-
"extra": {
7134
-
"branch-alias": {
7135
-
"dev-master": "2.1-dev"
7136
-
}
7137
-
},
7138
-
"autoload": {
7139
-
"classmap": [
7140
-
"hamcrest"
7141
-
]
7142
-
},
7143
-
"notification-url": "https://packagist.org/downloads/",
7144
-
"license": [
7145
-
"BSD-3-Clause"
7146
-
],
7147
-
"description": "This is the PHP port of Hamcrest Matchers",
7148
-
"keywords": [
7149
-
"test"
7150
-
],
7151
-
"support": {
7152
-
"issues": "https://github.com/hamcrest/hamcrest-php/issues",
7153
-
"source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1"
7154
-
},
7155
-
"time": "2025-04-30T06:54:44+00:00"
7156
-
},
7157
-
{
7158
-
"name": "laravel/pail",
7159
-
"version": "v1.2.3",
7160
-
"source": {
7161
-
"type": "git",
7162
-
"url": "https://github.com/laravel/pail.git",
7163
-
"reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a"
7164
-
},
7165
-
"dist": {
7166
-
"type": "zip",
7167
-
"url": "https://api.github.com/repos/laravel/pail/zipball/8cc3d575c1f0e57eeb923f366a37528c50d2385a",
7168
-
"reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a",
7169
-
"shasum": ""
7170
-
},
7171
-
"require": {
7172
-
"ext-mbstring": "*",
7173
-
"illuminate/console": "^10.24|^11.0|^12.0",
7174
-
"illuminate/contracts": "^10.24|^11.0|^12.0",
7175
-
"illuminate/log": "^10.24|^11.0|^12.0",
7176
-
"illuminate/process": "^10.24|^11.0|^12.0",
7177
-
"illuminate/support": "^10.24|^11.0|^12.0",
7178
-
"nunomaduro/termwind": "^1.15|^2.0",
7179
-
"php": "^8.2",
7180
-
"symfony/console": "^6.0|^7.0"
7181
-
},
7182
-
"require-dev": {
7183
-
"laravel/framework": "^10.24|^11.0|^12.0",
7184
-
"laravel/pint": "^1.13",
7185
-
"orchestra/testbench-core": "^8.13|^9.0|^10.0",
7186
-
"pestphp/pest": "^2.20|^3.0",
7187
-
"pestphp/pest-plugin-type-coverage": "^2.3|^3.0",
7188
-
"phpstan/phpstan": "^1.12.27",
7189
-
"symfony/var-dumper": "^6.3|^7.0"
7190
-
},
7191
-
"type": "library",
7192
-
"extra": {
7193
-
"laravel": {
7194
-
"providers": [
7195
-
"Laravel\\Pail\\PailServiceProvider"
7196
-
]
7197
-
},
7198
-
"branch-alias": {
7199
-
"dev-main": "1.x-dev"
7200
-
}
7201
-
},
7202
-
"autoload": {
7203
-
"psr-4": {
7204
-
"Laravel\\Pail\\": "src/"
7205
-
}
7206
-
},
7207
-
"notification-url": "https://packagist.org/downloads/",
7208
-
"license": [
7209
-
"MIT"
7210
-
],
7211
-
"authors": [
7212
-
{
7213
-
"name": "Taylor Otwell",
7214
-
"email": "taylor@laravel.com"
7215
-
},
7216
-
{
7217
-
"name": "Nuno Maduro",
7218
-
"email": "enunomaduro@gmail.com"
7219
-
}
7220
-
],
7221
-
"description": "Easily delve into your Laravel application's log files directly from the command line.",
7222
-
"homepage": "https://github.com/laravel/pail",
7223
-
"keywords": [
7224
-
"dev",
7225
-
"laravel",
7226
-
"logs",
7227
-
"php",
7228
-
"tail"
7229
-
],
7230
-
"support": {
7231
-
"issues": "https://github.com/laravel/pail/issues",
7232
-
"source": "https://github.com/laravel/pail"
7233
-
},
7234
-
"time": "2025-06-05T13:55:57+00:00"
7235
-
},
7236
-
{
7237
-
"name": "laravel/tinker",
7238
-
"version": "v2.10.1",
7239
-
"source": {
7240
-
"type": "git",
7241
-
"url": "https://github.com/laravel/tinker.git",
7242
-
"reference": "22177cc71807d38f2810c6204d8f7183d88a57d3"
7243
-
},
7244
-
"dist": {
7245
-
"type": "zip",
7246
-
"url": "https://api.github.com/repos/laravel/tinker/zipball/22177cc71807d38f2810c6204d8f7183d88a57d3",
7247
-
"reference": "22177cc71807d38f2810c6204d8f7183d88a57d3",
7248
-
"shasum": ""
7249
-
},
7250
-
"require": {
7251
-
"illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
7252
-
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
7253
-
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
7254
-
"php": "^7.2.5|^8.0",
7255
-
"psy/psysh": "^0.11.1|^0.12.0",
7256
-
"symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0"
7257
-
},
7258
-
"require-dev": {
7259
-
"mockery/mockery": "~1.3.3|^1.4.2",
7260
-
"phpstan/phpstan": "^1.10",
7261
-
"phpunit/phpunit": "^8.5.8|^9.3.3|^10.0"
7262
-
},
7263
-
"suggest": {
7264
-
"illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)."
7265
-
},
7266
-
"type": "library",
7267
-
"extra": {
7268
-
"laravel": {
7269
-
"providers": [
7270
-
"Laravel\\Tinker\\TinkerServiceProvider"
7271
-
]
7272
-
}
7273
-
},
7274
-
"autoload": {
7275
-
"psr-4": {
7276
-
"Laravel\\Tinker\\": "src/"
7277
-
}
7278
-
},
7279
-
"notification-url": "https://packagist.org/downloads/",
7280
-
"license": [
7281
-
"MIT"
7282
-
],
7283
-
"authors": [
7284
-
{
7285
-
"name": "Taylor Otwell",
7286
-
"email": "taylor@laravel.com"
7287
-
}
7288
-
],
7289
-
"description": "Powerful REPL for the Laravel framework.",
7290
-
"keywords": [
7291
-
"REPL",
7292
-
"Tinker",
7293
-
"laravel",
7294
-
"psysh"
7295
-
],
7296
-
"support": {
7297
-
"issues": "https://github.com/laravel/tinker/issues",
7298
-
"source": "https://github.com/laravel/tinker/tree/v2.10.1"
7299
-
},
7300
-
"time": "2025-01-27T14:24:01+00:00"
7301
-
},
7302
-
{
7303
-
"name": "mockery/mockery",
7304
-
"version": "1.6.12",
7305
-
"source": {
7306
-
"type": "git",
7307
-
"url": "https://github.com/mockery/mockery.git",
7308
-
"reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699"
7309
-
},
7310
-
"dist": {
7311
-
"type": "zip",
7312
-
"url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699",
7313
-
"reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699",
7314
-
"shasum": ""
7315
-
},
7316
-
"require": {
7317
-
"hamcrest/hamcrest-php": "^2.0.1",
7318
-
"lib-pcre": ">=7.0",
7319
-
"php": ">=7.3"
7320
-
},
7321
-
"conflict": {
7322
-
"phpunit/phpunit": "<8.0"
7323
-
},
7324
-
"require-dev": {
7325
-
"phpunit/phpunit": "^8.5 || ^9.6.17",
7326
-
"symplify/easy-coding-standard": "^12.1.14"
7327
-
},
7328
-
"type": "library",
7329
-
"autoload": {
7330
-
"files": [
7331
-
"library/helpers.php",
7332
-
"library/Mockery.php"
7333
-
],
7334
-
"psr-4": {
7335
-
"Mockery\\": "library/Mockery"
7336
-
}
7337
-
},
7338
-
"notification-url": "https://packagist.org/downloads/",
7339
-
"license": [
7340
-
"BSD-3-Clause"
7341
-
],
7342
-
"authors": [
7343
-
{
7344
-
"name": "Pรกdraic Brady",
7345
-
"email": "padraic.brady@gmail.com",
7346
-
"homepage": "https://github.com/padraic",
7347
-
"role": "Author"
7348
-
},
7349
-
{
7350
-
"name": "Dave Marshall",
7351
-
"email": "dave.marshall@atstsolutions.co.uk",
7352
-
"homepage": "https://davedevelopment.co.uk",
7353
-
"role": "Developer"
7354
-
},
7355
-
{
7356
-
"name": "Nathanael Esayeas",
7357
-
"email": "nathanael.esayeas@protonmail.com",
7358
-
"homepage": "https://github.com/ghostwriter",
7359
-
"role": "Lead Developer"
7360
-
}
7361
-
],
7362
-
"description": "Mockery is a simple yet flexible PHP mock object framework",
7363
-
"homepage": "https://github.com/mockery/mockery",
7364
-
"keywords": [
7365
-
"BDD",
7366
-
"TDD",
7367
-
"library",
7368
-
"mock",
7369
-
"mock objects",
7370
-
"mockery",
7371
-
"stub",
7372
-
"test",
7373
-
"test double",
7374
-
"testing"
7375
-
],
7376
-
"support": {
7377
-
"docs": "https://docs.mockery.io/",
7378
-
"issues": "https://github.com/mockery/mockery/issues",
7379
-
"rss": "https://github.com/mockery/mockery/releases.atom",
7380
-
"security": "https://github.com/mockery/mockery/security/advisories",
7381
-
"source": "https://github.com/mockery/mockery"
7382
-
},
7383
-
"time": "2024-05-16T03:13:13+00:00"
7384
-
},
7385
-
{
7386
-
"name": "myclabs/deep-copy",
7387
-
"version": "1.13.4",
7388
-
"source": {
7389
-
"type": "git",
7390
-
"url": "https://github.com/myclabs/DeepCopy.git",
7391
-
"reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
7392
-
},
7393
-
"dist": {
7394
-
"type": "zip",
7395
-
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
7396
-
"reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
7397
-
"shasum": ""
7398
-
},
7399
-
"require": {
7400
-
"php": "^7.1 || ^8.0"
7401
-
},
7402
-
"conflict": {
7403
-
"doctrine/collections": "<1.6.8",
7404
-
"doctrine/common": "<2.13.3 || >=3 <3.2.2"
7405
-
},
7406
-
"require-dev": {
7407
-
"doctrine/collections": "^1.6.8",
7408
-
"doctrine/common": "^2.13.3 || ^3.2.2",
7409
-
"phpspec/prophecy": "^1.10",
7410
-
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
7411
-
},
7412
-
"type": "library",
7413
-
"autoload": {
7414
-
"files": [
7415
-
"src/DeepCopy/deep_copy.php"
7416
-
],
7417
-
"psr-4": {
7418
-
"DeepCopy\\": "src/DeepCopy/"
7419
-
}
7420
-
},
7421
-
"notification-url": "https://packagist.org/downloads/",
7422
-
"license": [
7423
-
"MIT"
7424
-
],
7425
-
"description": "Create deep copies (clones) of your objects",
7426
-
"keywords": [
7427
-
"clone",
7428
-
"copy",
7429
-
"duplicate",
7430
-
"object",
7431
-
"object graph"
7432
-
],
7433
-
"support": {
7434
-
"issues": "https://github.com/myclabs/DeepCopy/issues",
7435
-
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
7436
-
},
7437
-
"funding": [
7438
-
{
7439
-
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
7440
-
"type": "tidelift"
7441
-
}
7442
-
],
7443
-
"time": "2025-08-01T08:46:24+00:00"
7444
-
},
7445
-
{
7446
-
"name": "nikic/php-parser",
7447
-
"version": "v5.6.2",
7448
-
"source": {
7449
-
"type": "git",
7450
-
"url": "https://github.com/nikic/PHP-Parser.git",
7451
-
"reference": "3a454ca033b9e06b63282ce19562e892747449bb"
7452
-
},
7453
-
"dist": {
7454
-
"type": "zip",
7455
-
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb",
7456
-
"reference": "3a454ca033b9e06b63282ce19562e892747449bb",
7457
-
"shasum": ""
7458
-
},
7459
-
"require": {
7460
-
"ext-ctype": "*",
7461
-
"ext-json": "*",
7462
-
"ext-tokenizer": "*",
7463
-
"php": ">=7.4"
7464
-
},
7465
-
"require-dev": {
7466
-
"ircmaxell/php-yacc": "^0.0.7",
7467
-
"phpunit/phpunit": "^9.0"
7468
-
},
7469
-
"bin": [
7470
-
"bin/php-parse"
7471
-
],
7472
-
"type": "library",
7473
-
"extra": {
7474
-
"branch-alias": {
7475
-
"dev-master": "5.x-dev"
7476
-
}
7477
-
},
7478
-
"autoload": {
7479
-
"psr-4": {
7480
-
"PhpParser\\": "lib/PhpParser"
7481
-
}
7482
-
},
7483
-
"notification-url": "https://packagist.org/downloads/",
7484
-
"license": [
7485
-
"BSD-3-Clause"
7486
-
],
7487
-
"authors": [
7488
-
{
7489
-
"name": "Nikita Popov"
7490
-
}
7491
-
],
7492
-
"description": "A PHP parser written in PHP",
7493
-
"keywords": [
7494
-
"parser",
7495
-
"php"
7496
-
],
7497
-
"support": {
7498
-
"issues": "https://github.com/nikic/PHP-Parser/issues",
7499
-
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2"
7500
-
},
7501
-
"time": "2025-10-21T19:32:17+00:00"
7502
-
},
7503
-
{
7504
-
"name": "nunomaduro/collision",
7505
-
"version": "v8.8.2",
7506
-
"source": {
7507
-
"type": "git",
7508
-
"url": "https://github.com/nunomaduro/collision.git",
7509
-
"reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb"
7510
-
},
7511
-
"dist": {
7512
-
"type": "zip",
7513
-
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/60207965f9b7b7a4ce15a0f75d57f9dadb105bdb",
7514
-
"reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb",
7515
-
"shasum": ""
7516
-
},
7517
-
"require": {
7518
-
"filp/whoops": "^2.18.1",
7519
-
"nunomaduro/termwind": "^2.3.1",
7520
-
"php": "^8.2.0",
7521
-
"symfony/console": "^7.3.0"
7522
-
},
7523
-
"conflict": {
7524
-
"laravel/framework": "<11.44.2 || >=13.0.0",
7525
-
"phpunit/phpunit": "<11.5.15 || >=13.0.0"
7526
-
},
7527
-
"require-dev": {
7528
-
"brianium/paratest": "^7.8.3",
7529
-
"larastan/larastan": "^3.4.2",
7530
-
"laravel/framework": "^11.44.2 || ^12.18",
7531
-
"laravel/pint": "^1.22.1",
7532
-
"laravel/sail": "^1.43.1",
7533
-
"laravel/sanctum": "^4.1.1",
7534
-
"laravel/tinker": "^2.10.1",
7535
-
"orchestra/testbench-core": "^9.12.0 || ^10.4",
7536
-
"pestphp/pest": "^3.8.2",
7537
-
"sebastian/environment": "^7.2.1 || ^8.0"
7538
-
},
7539
-
"type": "library",
7540
-
"extra": {
7541
-
"laravel": {
7542
-
"providers": [
7543
-
"NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
7544
-
]
7545
-
},
7546
-
"branch-alias": {
7547
-
"dev-8.x": "8.x-dev"
7548
-
}
7549
-
},
7550
-
"autoload": {
7551
-
"files": [
7552
-
"./src/Adapters/Phpunit/Autoload.php"
7553
-
],
7554
-
"psr-4": {
7555
-
"NunoMaduro\\Collision\\": "src/"
7556
-
}
7557
-
},
7558
-
"notification-url": "https://packagist.org/downloads/",
7559
-
"license": [
7560
-
"MIT"
7561
-
],
7562
-
"authors": [
7563
-
{
7564
-
"name": "Nuno Maduro",
7565
-
"email": "enunomaduro@gmail.com"
7566
-
}
7567
-
],
7568
-
"description": "Cli error handling for console/command-line PHP applications.",
7569
-
"keywords": [
7570
-
"artisan",
7571
-
"cli",
7572
-
"command-line",
7573
-
"console",
7574
-
"dev",
7575
-
"error",
7576
-
"handling",
7577
-
"laravel",
7578
-
"laravel-zero",
7579
-
"php",
7580
-
"symfony"
7581
-
],
7582
-
"support": {
7583
-
"issues": "https://github.com/nunomaduro/collision/issues",
7584
-
"source": "https://github.com/nunomaduro/collision"
7585
-
},
7586
-
"funding": [
7587
-
{
7588
-
"url": "https://www.paypal.com/paypalme/enunomaduro",
7589
-
"type": "custom"
7590
-
},
7591
-
{
7592
-
"url": "https://github.com/nunomaduro",
7593
-
"type": "github"
7594
-
},
7595
-
{
7596
-
"url": "https://www.patreon.com/nunomaduro",
7597
-
"type": "patreon"
7598
-
}
7599
-
],
7600
-
"time": "2025-06-25T02:12:12+00:00"
7601
-
},
7602
-
{
7603
-
"name": "orchestra/canvas",
7604
-
"version": "v9.2.2",
7605
-
"source": {
7606
-
"type": "git",
7607
-
"url": "https://github.com/orchestral/canvas.git",
7608
-
"reference": "002d948834c0899e511f5ac0381669363d7881e5"
7609
-
},
7610
-
"dist": {
7611
-
"type": "zip",
7612
-
"url": "https://api.github.com/repos/orchestral/canvas/zipball/002d948834c0899e511f5ac0381669363d7881e5",
7613
-
"reference": "002d948834c0899e511f5ac0381669363d7881e5",
7614
-
"shasum": ""
7615
-
},
7616
-
"require": {
7617
-
"composer-runtime-api": "^2.2",
7618
-
"composer/semver": "^3.0",
7619
-
"illuminate/console": "^11.43.0",
7620
-
"illuminate/database": "^11.43.0",
7621
-
"illuminate/filesystem": "^11.43.0",
7622
-
"illuminate/support": "^11.43.0",
7623
-
"orchestra/canvas-core": "^9.1.1",
7624
-
"orchestra/sidekick": "^1.0.2",
7625
-
"orchestra/testbench-core": "^9.11.0",
7626
-
"php": "^8.2",
7627
-
"symfony/polyfill-php83": "^1.31",
7628
-
"symfony/yaml": "^7.0.3"
7629
-
},
7630
-
"require-dev": {
7631
-
"laravel/framework": "^11.43.0",
7632
-
"laravel/pint": "^1.21",
7633
-
"mockery/mockery": "^1.6.10",
7634
-
"phpstan/phpstan": "^2.1",
7635
-
"phpunit/phpunit": "^11.5.7",
7636
-
"spatie/laravel-ray": "^1.39.1"
7637
-
},
7638
-
"bin": [
7639
-
"canvas"
7640
-
],
7641
-
"type": "library",
7642
-
"extra": {
7643
-
"laravel": {
7644
-
"providers": [
7645
-
"Orchestra\\Canvas\\LaravelServiceProvider"
7646
-
]
7647
-
}
7648
-
},
7649
-
"autoload": {
7650
-
"psr-4": {
7651
-
"Orchestra\\Canvas\\": "src/"
7652
-
}
7653
-
},
7654
-
"notification-url": "https://packagist.org/downloads/",
7655
-
"license": [
7656
-
"MIT"
7657
-
],
7658
-
"authors": [
7659
-
{
7660
-
"name": "Taylor Otwell",
7661
-
"email": "taylor@laravel.com"
7662
-
},
7663
-
{
7664
-
"name": "Mior Muhammad Zaki",
7665
-
"email": "crynobone@gmail.com"
7666
-
}
7667
-
],
7668
-
"description": "Code Generators for Laravel Applications and Packages",
7669
-
"support": {
7670
-
"issues": "https://github.com/orchestral/canvas/issues",
7671
-
"source": "https://github.com/orchestral/canvas/tree/v9.2.2"
7672
-
},
7673
-
"time": "2025-02-19T04:27:08+00:00"
7674
-
},
7675
-
{
7676
-
"name": "orchestra/canvas-core",
7677
-
"version": "v9.1.1",
7678
-
"source": {
7679
-
"type": "git",
7680
-
"url": "https://github.com/orchestral/canvas-core.git",
7681
-
"reference": "a8ebfa6c2e50f8c6597c489b4dfaf9af6789f62a"
7682
-
},
7683
-
"dist": {
7684
-
"type": "zip",
7685
-
"url": "https://api.github.com/repos/orchestral/canvas-core/zipball/a8ebfa6c2e50f8c6597c489b4dfaf9af6789f62a",
7686
-
"reference": "a8ebfa6c2e50f8c6597c489b4dfaf9af6789f62a",
7687
-
"shasum": ""
7688
-
},
7689
-
"require": {
7690
-
"composer-runtime-api": "^2.2",
7691
-
"composer/semver": "^3.0",
7692
-
"illuminate/console": "^11.43.0",
7693
-
"illuminate/support": "^11.43.0",
7694
-
"orchestra/sidekick": "^1.0.2",
7695
-
"php": "^8.2",
7696
-
"symfony/polyfill-php83": "^1.31"
7697
-
},
7698
-
"require-dev": {
7699
-
"laravel/framework": "^11.43.0",
7700
-
"laravel/pint": "^1.20",
7701
-
"mockery/mockery": "^1.6.10",
7702
-
"orchestra/testbench-core": "^9.11.0",
7703
-
"phpstan/phpstan": "^2.1",
7704
-
"phpunit/phpunit": "^11.5.7",
7705
-
"symfony/yaml": "^7.0.3"
7706
-
},
7707
-
"type": "library",
7708
-
"extra": {
7709
-
"laravel": {
7710
-
"providers": [
7711
-
"Orchestra\\Canvas\\Core\\LaravelServiceProvider"
7712
-
]
7713
-
}
7714
-
},
7715
-
"autoload": {
7716
-
"psr-4": {
7717
-
"Orchestra\\Canvas\\Core\\": "src/"
7718
-
}
7719
-
},
7720
-
"notification-url": "https://packagist.org/downloads/",
7721
-
"license": [
7722
-
"MIT"
7723
-
],
7724
-
"authors": [
7725
-
{
7726
-
"name": "Taylor Otwell",
7727
-
"email": "taylor@laravel.com"
7728
-
},
7729
-
{
7730
-
"name": "Mior Muhammad Zaki",
7731
-
"email": "crynobone@gmail.com"
7732
-
}
7733
-
],
7734
-
"description": "Code Generators Builder for Laravel Applications and Packages",
7735
-
"support": {
7736
-
"issues": "https://github.com/orchestral/canvas/issues",
7737
-
"source": "https://github.com/orchestral/canvas-core/tree/v9.1.1"
7738
-
},
7739
-
"time": "2025-02-19T04:14:36+00:00"
7740
-
},
7741
-
{
7742
-
"name": "orchestra/sidekick",
7743
-
"version": "v1.2.17",
7744
-
"source": {
7745
-
"type": "git",
7746
-
"url": "https://github.com/orchestral/sidekick.git",
7747
-
"reference": "371ce2882ee3f5bf826b36e75d461e51c9cd76c2"
7748
-
},
7749
-
"dist": {
7750
-
"type": "zip",
7751
-
"url": "https://api.github.com/repos/orchestral/sidekick/zipball/371ce2882ee3f5bf826b36e75d461e51c9cd76c2",
7752
-
"reference": "371ce2882ee3f5bf826b36e75d461e51c9cd76c2",
7753
-
"shasum": ""
7754
-
},
7755
-
"require": {
7756
-
"composer-runtime-api": "^2.2",
7757
-
"php": "^8.1",
7758
-
"symfony/polyfill-php83": "^1.32"
7759
-
},
7760
-
"require-dev": {
7761
-
"fakerphp/faker": "^1.21",
7762
-
"laravel/framework": "^10.48.29|^11.44.7|^12.1.1|^13.0",
7763
-
"laravel/pint": "^1.4",
7764
-
"mockery/mockery": "^1.5.1",
7765
-
"orchestra/testbench-core": "^8.37.0|^9.14.0|^10.2.0|^11.0",
7766
-
"phpstan/phpstan": "^2.1.14",
7767
-
"phpunit/phpunit": "^10.0|^11.0|^12.0",
7768
-
"symfony/process": "^6.0|^7.0"
7769
-
},
7770
-
"type": "library",
7771
-
"autoload": {
7772
-
"files": [
7773
-
"src/Eloquent/functions.php",
7774
-
"src/Http/functions.php",
7775
-
"src/functions.php"
7776
-
],
7777
-
"psr-4": {
7778
-
"Orchestra\\Sidekick\\": "src/"
7779
-
}
7780
-
},
7781
-
"notification-url": "https://packagist.org/downloads/",
7782
-
"license": [
7783
-
"MIT"
7784
-
],
7785
-
"authors": [
7786
-
{
7787
-
"name": "Mior Muhammad Zaki",
7788
-
"email": "crynobone@gmail.com"
7789
-
}
7790
-
],
7791
-
"description": "Packages Toolkit Utilities and Helpers for Laravel",
7792
-
"support": {
7793
-
"issues": "https://github.com/orchestral/sidekick/issues",
7794
-
"source": "https://github.com/orchestral/sidekick/tree/v1.2.17"
7795
-
},
7796
-
"time": "2025-10-02T11:02:26+00:00"
7797
-
},
7798
-
{
7799
-
"name": "orchestra/testbench",
7800
-
"version": "v9.15.0",
7801
-
"source": {
7802
-
"type": "git",
7803
-
"url": "https://github.com/orchestral/testbench.git",
7804
-
"reference": "d0181240f93688448d4ae3b5479ec5ed70a87a47"
7805
-
},
7806
-
"dist": {
7807
-
"type": "zip",
7808
-
"url": "https://api.github.com/repos/orchestral/testbench/zipball/d0181240f93688448d4ae3b5479ec5ed70a87a47",
7809
-
"reference": "d0181240f93688448d4ae3b5479ec5ed70a87a47",
7810
-
"shasum": ""
7811
-
},
7812
-
"require": {
7813
-
"composer-runtime-api": "^2.2",
7814
-
"fakerphp/faker": "^1.23",
7815
-
"laravel/framework": "^11.45.2",
7816
-
"mockery/mockery": "^1.6.10",
7817
-
"orchestra/testbench-core": "^9.16.0",
7818
-
"orchestra/workbench": "^9.13.5",
7819
-
"php": "^8.2",
7820
-
"phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1",
7821
-
"symfony/process": "^7.0.3",
7822
-
"symfony/yaml": "^7.0.3",
7823
-
"vlucas/phpdotenv": "^5.6.1"
7824
-
},
7825
-
"type": "library",
7826
-
"notification-url": "https://packagist.org/downloads/",
7827
-
"license": [
7828
-
"MIT"
7829
-
],
7830
-
"authors": [
7831
-
{
7832
-
"name": "Mior Muhammad Zaki",
7833
-
"email": "crynobone@gmail.com",
7834
-
"homepage": "https://github.com/crynobone"
7835
-
}
7836
-
],
7837
-
"description": "Laravel Testing Helper for Packages Development",
7838
-
"homepage": "https://packages.tools/testbench/",
7839
-
"keywords": [
7840
-
"BDD",
7841
-
"TDD",
7842
-
"dev",
7843
-
"laravel",
7844
-
"laravel-packages",
7845
-
"testing"
7846
-
],
7847
-
"support": {
7848
-
"issues": "https://github.com/orchestral/testbench/issues",
7849
-
"source": "https://github.com/orchestral/testbench/tree/v9.15.0"
7850
-
},
7851
-
"time": "2025-08-20T11:42:03+00:00"
7852
-
},
7853
-
{
7854
-
"name": "orchestra/testbench-core",
7855
-
"version": "v9.17.0",
7856
-
"source": {
7857
-
"type": "git",
7858
-
"url": "https://github.com/orchestral/testbench-core.git",
7859
-
"reference": "a5b4d56a40536fde50a72e20ce43abaa76f8de2f"
7860
-
},
7861
-
"dist": {
7862
-
"type": "zip",
7863
-
"url": "https://api.github.com/repos/orchestral/testbench-core/zipball/a5b4d56a40536fde50a72e20ce43abaa76f8de2f",
7864
-
"reference": "a5b4d56a40536fde50a72e20ce43abaa76f8de2f",
7865
-
"shasum": ""
7866
-
},
7867
-
"require": {
7868
-
"composer-runtime-api": "^2.2",
7869
-
"orchestra/sidekick": "~1.1.20|~1.2.17",
7870
-
"php": "^8.2",
7871
-
"symfony/deprecation-contracts": "^2.5|^3.0",
7872
-
"symfony/polyfill-php83": "^1.32"
7873
-
},
7874
-
"conflict": {
7875
-
"brianium/paratest": "<7.3.0|>=8.0.0",
7876
-
"laravel/framework": "<11.45.3|>=12.0.0",
7877
-
"laravel/serializable-closure": "<1.3.0|>=2.0.0 <2.0.3|>=3.0.0",
7878
-
"nunomaduro/collision": "<8.0.0|>=9.0.0",
7879
-
"orchestra/testbench-dusk": "<9.10.0|>=10.0.0",
7880
-
"phpunit/phpunit": "<10.5.35|>=11.0.0 <11.3.6|>=12.0.0 <12.0.1|>=12.4.0"
7881
-
},
7882
-
"require-dev": {
7883
-
"fakerphp/faker": "^1.24",
7884
-
"laravel/framework": "^11.45.3",
7885
-
"laravel/pint": "^1.24",
7886
-
"laravel/serializable-closure": "^1.3|^2.0.4",
7887
-
"mockery/mockery": "^1.6.10",
7888
-
"phpstan/phpstan": "^2.1.19",
7889
-
"phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1",
7890
-
"spatie/laravel-ray": "^1.40.2",
7891
-
"symfony/process": "^7.0.3",
7892
-
"symfony/yaml": "^7.0.3",
7893
-
"vlucas/phpdotenv": "^5.6.1"
7894
-
},
7895
-
"suggest": {
7896
-
"brianium/paratest": "Allow using parallel testing (^7.3).",
7897
-
"ext-pcntl": "Required to use all features of the console signal trapping.",
7898
-
"fakerphp/faker": "Allow using Faker for testing (^1.23).",
7899
-
"laravel/framework": "Required for testing (^11.45.3).",
7900
-
"mockery/mockery": "Allow using Mockery for testing (^1.6).",
7901
-
"nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^8.0).",
7902
-
"orchestra/testbench-dusk": "Allow using Laravel Dusk for testing (^9.10).",
7903
-
"phpunit/phpunit": "Allow using PHPUnit for testing (^10.5.35|^11.3.6|^12.0.1).",
7904
-
"symfony/process": "Required to use Orchestra\\Testbench\\remote function (^7.0).",
7905
-
"symfony/yaml": "Required for Testbench CLI (^7.0).",
7906
-
"vlucas/phpdotenv": "Required for Testbench CLI (^5.6.1)."
7907
-
},
7908
-
"bin": [
7909
-
"testbench"
7910
-
],
7911
-
"type": "library",
7912
-
"autoload": {
7913
-
"files": [
7914
-
"src/functions.php"
7915
-
],
7916
-
"psr-4": {
7917
-
"Orchestra\\Testbench\\": "src/"
7918
-
}
7919
-
},
7920
-
"notification-url": "https://packagist.org/downloads/",
7921
-
"license": [
7922
-
"MIT"
7923
-
],
7924
-
"authors": [
7925
-
{
7926
-
"name": "Mior Muhammad Zaki",
7927
-
"email": "crynobone@gmail.com",
7928
-
"homepage": "https://github.com/crynobone"
7929
-
}
7930
-
],
7931
-
"description": "Testing Helper for Laravel Development",
7932
-
"homepage": "https://packages.tools/testbench",
7933
-
"keywords": [
7934
-
"BDD",
7935
-
"TDD",
7936
-
"dev",
7937
-
"laravel",
7938
-
"laravel-packages",
7939
-
"testing"
7940
-
],
7941
-
"support": {
7942
-
"issues": "https://github.com/orchestral/testbench/issues",
7943
-
"source": "https://github.com/orchestral/testbench-core"
7944
-
},
7945
-
"time": "2025-10-14T12:02:37+00:00"
7946
-
},
7947
-
{
7948
-
"name": "orchestra/workbench",
7949
-
"version": "v9.13.5",
7950
-
"source": {
7951
-
"type": "git",
7952
-
"url": "https://github.com/orchestral/workbench.git",
7953
-
"reference": "1da2ea95089ed3516bda6f8e9cd57c81290004bf"
7954
-
},
7955
-
"dist": {
7956
-
"type": "zip",
7957
-
"url": "https://api.github.com/repos/orchestral/workbench/zipball/1da2ea95089ed3516bda6f8e9cd57c81290004bf",
7958
-
"reference": "1da2ea95089ed3516bda6f8e9cd57c81290004bf",
7959
-
"shasum": ""
7960
-
},
7961
-
"require": {
7962
-
"composer-runtime-api": "^2.2",
7963
-
"fakerphp/faker": "^1.23",
7964
-
"laravel/framework": "^11.44.2",
7965
-
"laravel/pail": "^1.2",
7966
-
"laravel/tinker": "^2.9",
7967
-
"nunomaduro/collision": "^8.0",
7968
-
"orchestra/canvas": "^9.2.2",
7969
-
"orchestra/sidekick": "^1.1.0",
7970
-
"orchestra/testbench-core": "^9.12.0",
7971
-
"php": "^8.2",
7972
-
"symfony/polyfill-php83": "^1.31",
7973
-
"symfony/polyfill-php84": "^1.31",
7974
-
"symfony/process": "^7.0.3",
7975
-
"symfony/yaml": "^7.0.3"
7976
-
},
7977
-
"require-dev": {
7978
-
"laravel/pint": "^1.21",
7979
-
"mockery/mockery": "^1.6.10",
7980
-
"phpstan/phpstan": "^2.1",
7981
-
"phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1",
7982
-
"spatie/laravel-ray": "^1.39.1"
7983
-
},
7984
-
"suggest": {
7985
-
"ext-pcntl": "Required to use all features of the console signal trapping."
7986
-
},
7987
-
"type": "library",
7988
-
"autoload": {
7989
-
"psr-4": {
7990
-
"Orchestra\\Workbench\\": "src/"
7991
-
}
7992
-
},
7993
-
"notification-url": "https://packagist.org/downloads/",
7994
-
"license": [
7995
-
"MIT"
7996
-
],
7997
-
"authors": [
7998
-
{
7999
-
"name": "Mior Muhammad Zaki",
8000
-
"email": "crynobone@gmail.com"
8001
-
}
8002
-
],
8003
-
"description": "Workbench Companion for Laravel Packages Development",
8004
-
"keywords": [
8005
-
"dev",
8006
-
"laravel",
8007
-
"laravel-packages",
8008
-
"testing"
8009
-
],
8010
-
"support": {
8011
-
"issues": "https://github.com/orchestral/workbench/issues",
8012
-
"source": "https://github.com/orchestral/workbench/tree/v9.13.5"
8013
-
},
8014
-
"time": "2025-04-06T11:06:19+00:00"
8015
-
},
8016
-
{
8017
-
"name": "phar-io/manifest",
8018
-
"version": "2.0.4",
8019
-
"source": {
8020
-
"type": "git",
8021
-
"url": "https://github.com/phar-io/manifest.git",
8022
-
"reference": "54750ef60c58e43759730615a392c31c80e23176"
8023
-
},
8024
-
"dist": {
8025
-
"type": "zip",
8026
-
"url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
8027
-
"reference": "54750ef60c58e43759730615a392c31c80e23176",
8028
-
"shasum": ""
8029
-
},
8030
-
"require": {
8031
-
"ext-dom": "*",
8032
-
"ext-libxml": "*",
8033
-
"ext-phar": "*",
8034
-
"ext-xmlwriter": "*",
8035
-
"phar-io/version": "^3.0.1",
8036
-
"php": "^7.2 || ^8.0"
8037
-
},
8038
-
"type": "library",
8039
-
"extra": {
8040
-
"branch-alias": {
8041
-
"dev-master": "2.0.x-dev"
8042
-
}
8043
-
},
8044
-
"autoload": {
8045
-
"classmap": [
8046
-
"src/"
8047
-
]
8048
-
},
8049
-
"notification-url": "https://packagist.org/downloads/",
8050
-
"license": [
8051
-
"BSD-3-Clause"
8052
-
],
8053
-
"authors": [
8054
-
{
8055
-
"name": "Arne Blankerts",
8056
-
"email": "arne@blankerts.de",
8057
-
"role": "Developer"
8058
-
},
8059
-
{
8060
-
"name": "Sebastian Heuer",
8061
-
"email": "sebastian@phpeople.de",
8062
-
"role": "Developer"
8063
-
},
8064
-
{
8065
-
"name": "Sebastian Bergmann",
8066
-
"email": "sebastian@phpunit.de",
8067
-
"role": "Developer"
8068
-
}
8069
-
],
8070
-
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
8071
-
"support": {
8072
-
"issues": "https://github.com/phar-io/manifest/issues",
8073
-
"source": "https://github.com/phar-io/manifest/tree/2.0.4"
8074
-
},
8075
-
"funding": [
8076
-
{
8077
-
"url": "https://github.com/theseer",
8078
-
"type": "github"
8079
-
}
8080
-
],
8081
-
"time": "2024-03-03T12:33:53+00:00"
8082
-
},
8083
-
{
8084
-
"name": "phar-io/version",
8085
-
"version": "3.2.1",
8086
-
"source": {
8087
-
"type": "git",
8088
-
"url": "https://github.com/phar-io/version.git",
8089
-
"reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
8090
-
},
8091
-
"dist": {
8092
-
"type": "zip",
8093
-
"url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
8094
-
"reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
8095
-
"shasum": ""
8096
-
},
8097
-
"require": {
8098
-
"php": "^7.2 || ^8.0"
8099
-
},
8100
-
"type": "library",
8101
-
"autoload": {
8102
-
"classmap": [
8103
-
"src/"
8104
-
]
8105
-
},
8106
-
"notification-url": "https://packagist.org/downloads/",
8107
-
"license": [
8108
-
"BSD-3-Clause"
8109
-
],
8110
-
"authors": [
8111
-
{
8112
-
"name": "Arne Blankerts",
8113
-
"email": "arne@blankerts.de",
8114
-
"role": "Developer"
8115
-
},
8116
-
{
8117
-
"name": "Sebastian Heuer",
8118
-
"email": "sebastian@phpeople.de",
8119
-
"role": "Developer"
8120
-
},
8121
-
{
8122
-
"name": "Sebastian Bergmann",
8123
-
"email": "sebastian@phpunit.de",
8124
-
"role": "Developer"
8125
-
}
8126
-
],
8127
-
"description": "Library for handling version information and constraints",
8128
-
"support": {
8129
-
"issues": "https://github.com/phar-io/version/issues",
8130
-
"source": "https://github.com/phar-io/version/tree/3.2.1"
8131
-
},
8132
-
"time": "2022-02-21T01:04:05+00:00"
8133
-
},
8134
-
{
8135
-
"name": "phpunit/php-code-coverage",
8136
-
"version": "11.0.11",
8137
-
"source": {
8138
-
"type": "git",
8139
-
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
8140
-
"reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4"
8141
-
},
8142
-
"dist": {
8143
-
"type": "zip",
8144
-
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4",
8145
-
"reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4",
8146
-
"shasum": ""
8147
-
},
8148
-
"require": {
8149
-
"ext-dom": "*",
8150
-
"ext-libxml": "*",
8151
-
"ext-xmlwriter": "*",
8152
-
"nikic/php-parser": "^5.4.0",
8153
-
"php": ">=8.2",
8154
-
"phpunit/php-file-iterator": "^5.1.0",
8155
-
"phpunit/php-text-template": "^4.0.1",
8156
-
"sebastian/code-unit-reverse-lookup": "^4.0.1",
8157
-
"sebastian/complexity": "^4.0.1",
8158
-
"sebastian/environment": "^7.2.0",
8159
-
"sebastian/lines-of-code": "^3.0.1",
8160
-
"sebastian/version": "^5.0.2",
8161
-
"theseer/tokenizer": "^1.2.3"
8162
-
},
8163
-
"require-dev": {
8164
-
"phpunit/phpunit": "^11.5.2"
8165
-
},
8166
-
"suggest": {
8167
-
"ext-pcov": "PHP extension that provides line coverage",
8168
-
"ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
8169
-
},
8170
-
"type": "library",
8171
-
"extra": {
8172
-
"branch-alias": {
8173
-
"dev-main": "11.0.x-dev"
8174
-
}
8175
-
},
8176
-
"autoload": {
8177
-
"classmap": [
8178
-
"src/"
8179
-
]
8180
-
},
8181
-
"notification-url": "https://packagist.org/downloads/",
8182
-
"license": [
8183
-
"BSD-3-Clause"
8184
-
],
8185
-
"authors": [
8186
-
{
8187
-
"name": "Sebastian Bergmann",
8188
-
"email": "sebastian@phpunit.de",
8189
-
"role": "lead"
8190
-
}
8191
-
],
8192
-
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
8193
-
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
8194
-
"keywords": [
8195
-
"coverage",
8196
-
"testing",
8197
-
"xunit"
8198
-
],
8199
-
"support": {
8200
-
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
8201
-
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
8202
-
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11"
8203
-
},
8204
-
"funding": [
8205
-
{
8206
-
"url": "https://github.com/sebastianbergmann",
8207
-
"type": "github"
8208
-
},
8209
-
{
8210
-
"url": "https://liberapay.com/sebastianbergmann",
8211
-
"type": "liberapay"
8212
-
},
8213
-
{
8214
-
"url": "https://thanks.dev/u/gh/sebastianbergmann",
8215
-
"type": "thanks_dev"
8216
-
},
8217
-
{
8218
-
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
8219
-
"type": "tidelift"
8220
-
}
8221
-
],
8222
-
"time": "2025-08-27T14:37:49+00:00"
8223
-
},
8224
-
{
8225
-
"name": "phpunit/php-file-iterator",
8226
-
"version": "5.1.0",
8227
-
"source": {
8228
-
"type": "git",
8229
-
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
8230
-
"reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6"
8231
-
},
8232
-
"dist": {
8233
-
"type": "zip",
8234
-
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6",
8235
-
"reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6",
8236
-
"shasum": ""
8237
-
},
8238
-
"require": {
8239
-
"php": ">=8.2"
8240
-
},
8241
-
"require-dev": {
8242
-
"phpunit/phpunit": "^11.0"
8243
-
},
8244
-
"type": "library",
8245
-
"extra": {
8246
-
"branch-alias": {
8247
-
"dev-main": "5.0-dev"
8248
-
}
8249
-
},
8250
-
"autoload": {
8251
-
"classmap": [
8252
-
"src/"
8253
-
]
8254
-
},
8255
-
"notification-url": "https://packagist.org/downloads/",
8256
-
"license": [
8257
-
"BSD-3-Clause"
8258
-
],
8259
-
"authors": [
8260
-
{
8261
-
"name": "Sebastian Bergmann",
8262
-
"email": "sebastian@phpunit.de",
8263
-
"role": "lead"
8264
-
}
8265
-
],
8266
-
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
8267
-
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
8268
-
"keywords": [
8269
-
"filesystem",
8270
-
"iterator"
8271
-
],
8272
-
"support": {
8273
-
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
8274
-
"security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
8275
-
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0"
8276
-
},
8277
-
"funding": [
8278
-
{
8279
-
"url": "https://github.com/sebastianbergmann",
8280
-
"type": "github"
8281
-
}
8282
-
],
8283
-
"time": "2024-08-27T05:02:59+00:00"
8284
-
},
8285
-
{
8286
-
"name": "phpunit/php-invoker",
8287
-
"version": "5.0.1",
8288
-
"source": {
8289
-
"type": "git",
8290
-
"url": "https://github.com/sebastianbergmann/php-invoker.git",
8291
-
"reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2"
8292
-
},
8293
-
"dist": {
8294
-
"type": "zip",
8295
-
"url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2",
8296
-
"reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2",
8297
-
"shasum": ""
8298
-
},
8299
-
"require": {
8300
-
"php": ">=8.2"
8301
-
},
8302
-
"require-dev": {
8303
-
"ext-pcntl": "*",
8304
-
"phpunit/phpunit": "^11.0"
8305
-
},
8306
-
"suggest": {
8307
-
"ext-pcntl": "*"
8308
-
},
8309
-
"type": "library",
8310
-
"extra": {
8311
-
"branch-alias": {
8312
-
"dev-main": "5.0-dev"
8313
-
}
8314
-
},
8315
-
"autoload": {
8316
-
"classmap": [
8317
-
"src/"
8318
-
]
8319
-
},
8320
-
"notification-url": "https://packagist.org/downloads/",
8321
-
"license": [
8322
-
"BSD-3-Clause"
8323
-
],
8324
-
"authors": [
8325
-
{
8326
-
"name": "Sebastian Bergmann",
8327
-
"email": "sebastian@phpunit.de",
8328
-
"role": "lead"
8329
-
}
8330
-
],
8331
-
"description": "Invoke callables with a timeout",
8332
-
"homepage": "https://github.com/sebastianbergmann/php-invoker/",
8333
-
"keywords": [
8334
-
"process"
8335
-
],
8336
-
"support": {
8337
-
"issues": "https://github.com/sebastianbergmann/php-invoker/issues",
8338
-
"security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
8339
-
"source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1"
8340
-
},
8341
-
"funding": [
8342
-
{
8343
-
"url": "https://github.com/sebastianbergmann",
8344
-
"type": "github"
8345
-
}
8346
-
],
8347
-
"time": "2024-07-03T05:07:44+00:00"
8348
-
},
8349
-
{
8350
-
"name": "phpunit/php-text-template",
8351
-
"version": "4.0.1",
8352
-
"source": {
8353
-
"type": "git",
8354
-
"url": "https://github.com/sebastianbergmann/php-text-template.git",
8355
-
"reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964"
8356
-
},
8357
-
"dist": {
8358
-
"type": "zip",
8359
-
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
8360
-
"reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
8361
-
"shasum": ""
8362
-
},
8363
-
"require": {
8364
-
"php": ">=8.2"
8365
-
},
8366
-
"require-dev": {
8367
-
"phpunit/phpunit": "^11.0"
8368
-
},
8369
-
"type": "library",
8370
-
"extra": {
8371
-
"branch-alias": {
8372
-
"dev-main": "4.0-dev"
8373
-
}
8374
-
},
8375
-
"autoload": {
8376
-
"classmap": [
8377
-
"src/"
8378
-
]
8379
-
},
8380
-
"notification-url": "https://packagist.org/downloads/",
8381
-
"license": [
8382
-
"BSD-3-Clause"
8383
-
],
8384
-
"authors": [
8385
-
{
8386
-
"name": "Sebastian Bergmann",
8387
-
"email": "sebastian@phpunit.de",
8388
-
"role": "lead"
8389
-
}
8390
-
],
8391
-
"description": "Simple template engine.",
8392
-
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
8393
-
"keywords": [
8394
-
"template"
8395
-
],
8396
-
"support": {
8397
-
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
8398
-
"security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
8399
-
"source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1"
8400
-
},
8401
-
"funding": [
8402
-
{
8403
-
"url": "https://github.com/sebastianbergmann",
8404
-
"type": "github"
8405
-
}
8406
-
],
8407
-
"time": "2024-07-03T05:08:43+00:00"
8408
-
},
8409
-
{
8410
-
"name": "phpunit/php-timer",
8411
-
"version": "7.0.1",
8412
-
"source": {
8413
-
"type": "git",
8414
-
"url": "https://github.com/sebastianbergmann/php-timer.git",
8415
-
"reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3"
8416
-
},
8417
-
"dist": {
8418
-
"type": "zip",
8419
-
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
8420
-
"reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
8421
-
"shasum": ""
8422
-
},
8423
-
"require": {
8424
-
"php": ">=8.2"
8425
-
},
8426
-
"require-dev": {
8427
-
"phpunit/phpunit": "^11.0"
8428
-
},
8429
-
"type": "library",
8430
-
"extra": {
8431
-
"branch-alias": {
8432
-
"dev-main": "7.0-dev"
8433
-
}
8434
-
},
8435
-
"autoload": {
8436
-
"classmap": [
8437
-
"src/"
8438
-
]
8439
-
},
8440
-
"notification-url": "https://packagist.org/downloads/",
8441
-
"license": [
8442
-
"BSD-3-Clause"
8443
-
],
8444
-
"authors": [
8445
-
{
8446
-
"name": "Sebastian Bergmann",
8447
-
"email": "sebastian@phpunit.de",
8448
-
"role": "lead"
8449
-
}
8450
-
],
8451
-
"description": "Utility class for timing",
8452
-
"homepage": "https://github.com/sebastianbergmann/php-timer/",
8453
-
"keywords": [
8454
-
"timer"
8455
-
],
8456
-
"support": {
8457
-
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
8458
-
"security": "https://github.com/sebastianbergmann/php-timer/security/policy",
8459
-
"source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1"
8460
-
},
8461
-
"funding": [
8462
-
{
8463
-
"url": "https://github.com/sebastianbergmann",
8464
-
"type": "github"
8465
-
}
8466
-
],
8467
-
"time": "2024-07-03T05:09:35+00:00"
8468
-
},
8469
-
{
8470
-
"name": "phpunit/phpunit",
8471
-
"version": "11.5.42",
8472
-
"source": {
8473
-
"type": "git",
8474
-
"url": "https://github.com/sebastianbergmann/phpunit.git",
8475
-
"reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c"
8476
-
},
8477
-
"dist": {
8478
-
"type": "zip",
8479
-
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c",
8480
-
"reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c",
8481
-
"shasum": ""
8482
-
},
8483
-
"require": {
8484
-
"ext-dom": "*",
8485
-
"ext-json": "*",
8486
-
"ext-libxml": "*",
8487
-
"ext-mbstring": "*",
8488
-
"ext-xml": "*",
8489
-
"ext-xmlwriter": "*",
8490
-
"myclabs/deep-copy": "^1.13.4",
8491
-
"phar-io/manifest": "^2.0.4",
8492
-
"phar-io/version": "^3.2.1",
8493
-
"php": ">=8.2",
8494
-
"phpunit/php-code-coverage": "^11.0.11",
8495
-
"phpunit/php-file-iterator": "^5.1.0",
8496
-
"phpunit/php-invoker": "^5.0.1",
8497
-
"phpunit/php-text-template": "^4.0.1",
8498
-
"phpunit/php-timer": "^7.0.1",
8499
-
"sebastian/cli-parser": "^3.0.2",
8500
-
"sebastian/code-unit": "^3.0.3",
8501
-
"sebastian/comparator": "^6.3.2",
8502
-
"sebastian/diff": "^6.0.2",
8503
-
"sebastian/environment": "^7.2.1",
8504
-
"sebastian/exporter": "^6.3.2",
8505
-
"sebastian/global-state": "^7.0.2",
8506
-
"sebastian/object-enumerator": "^6.0.1",
8507
-
"sebastian/type": "^5.1.3",
8508
-
"sebastian/version": "^5.0.2",
8509
-
"staabm/side-effects-detector": "^1.0.5"
8510
-
},
8511
-
"suggest": {
8512
-
"ext-soap": "To be able to generate mocks based on WSDL files"
8513
-
},
8514
-
"bin": [
8515
-
"phpunit"
8516
-
],
8517
-
"type": "library",
8518
-
"extra": {
8519
-
"branch-alias": {
8520
-
"dev-main": "11.5-dev"
8521
-
}
8522
-
},
8523
-
"autoload": {
8524
-
"files": [
8525
-
"src/Framework/Assert/Functions.php"
8526
-
],
8527
-
"classmap": [
8528
-
"src/"
8529
-
]
8530
-
},
8531
-
"notification-url": "https://packagist.org/downloads/",
8532
-
"license": [
8533
-
"BSD-3-Clause"
8534
-
],
8535
-
"authors": [
8536
-
{
8537
-
"name": "Sebastian Bergmann",
8538
-
"email": "sebastian@phpunit.de",
8539
-
"role": "lead"
8540
-
}
8541
-
],
8542
-
"description": "The PHP Unit Testing framework.",
8543
-
"homepage": "https://phpunit.de/",
8544
-
"keywords": [
8545
-
"phpunit",
8546
-
"testing",
8547
-
"xunit"
8548
-
],
8549
-
"support": {
8550
-
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
8551
-
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
8552
-
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.42"
8553
-
},
8554
-
"funding": [
8555
-
{
8556
-
"url": "https://phpunit.de/sponsors.html",
8557
-
"type": "custom"
8558
-
},
8559
-
{
8560
-
"url": "https://github.com/sebastianbergmann",
8561
-
"type": "github"
8562
-
},
8563
-
{
8564
-
"url": "https://liberapay.com/sebastianbergmann",
8565
-
"type": "liberapay"
8566
-
},
8567
-
{
8568
-
"url": "https://thanks.dev/u/gh/sebastianbergmann",
8569
-
"type": "thanks_dev"
8570
-
},
8571
-
{
8572
-
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
8573
-
"type": "tidelift"
8574
-
}
8575
-
],
8576
-
"time": "2025-09-28T12:09:13+00:00"
8577
-
},
8578
-
{
8579
-
"name": "psy/psysh",
8580
-
"version": "v0.12.14",
8581
-
"source": {
8582
-
"type": "git",
8583
-
"url": "https://github.com/bobthecow/psysh.git",
8584
-
"reference": "95c29b3756a23855a30566b745d218bee690bef2"
8585
-
},
8586
-
"dist": {
8587
-
"type": "zip",
8588
-
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/95c29b3756a23855a30566b745d218bee690bef2",
8589
-
"reference": "95c29b3756a23855a30566b745d218bee690bef2",
8590
-
"shasum": ""
8591
-
},
8592
-
"require": {
8593
-
"ext-json": "*",
8594
-
"ext-tokenizer": "*",
8595
-
"nikic/php-parser": "^5.0 || ^4.0",
8596
-
"php": "^8.0 || ^7.4",
8597
-
"symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4",
8598
-
"symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4"
8599
-
},
8600
-
"conflict": {
8601
-
"symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4"
8602
-
},
8603
-
"require-dev": {
8604
-
"bamarni/composer-bin-plugin": "^1.2",
8605
-
"composer/class-map-generator": "^1.6"
8606
-
},
8607
-
"suggest": {
8608
-
"composer/class-map-generator": "Improved tab completion performance with better class discovery.",
8609
-
"ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
8610
-
"ext-posix": "If you have PCNTL, you'll want the POSIX extension as well."
8611
-
},
8612
-
"bin": [
8613
-
"bin/psysh"
8614
-
],
8615
-
"type": "library",
8616
-
"extra": {
8617
-
"bamarni-bin": {
8618
-
"bin-links": false,
8619
-
"forward-command": false
8620
-
},
8621
-
"branch-alias": {
8622
-
"dev-main": "0.12.x-dev"
8623
-
}
8624
-
},
8625
-
"autoload": {
8626
-
"files": [
8627
-
"src/functions.php"
8628
-
],
8629
-
"psr-4": {
8630
-
"Psy\\": "src/"
8631
-
}
8632
-
},
8633
-
"notification-url": "https://packagist.org/downloads/",
8634
-
"license": [
8635
-
"MIT"
8636
-
],
8637
-
"authors": [
8638
-
{
8639
-
"name": "Justin Hileman",
8640
-
"email": "justin@justinhileman.info"
8641
-
}
8642
-
],
8643
-
"description": "An interactive shell for modern PHP.",
8644
-
"homepage": "https://psysh.org",
8645
-
"keywords": [
8646
-
"REPL",
8647
-
"console",
8648
-
"interactive",
8649
-
"shell"
8650
-
],
8651
-
"support": {
8652
-
"issues": "https://github.com/bobthecow/psysh/issues",
8653
-
"source": "https://github.com/bobthecow/psysh/tree/v0.12.14"
8654
-
},
8655
-
"time": "2025-10-27T17:15:31+00:00"
8656
-
},
8657
-
{
8658
-
"name": "sebastian/cli-parser",
8659
-
"version": "3.0.2",
8660
-
"source": {
8661
-
"type": "git",
8662
-
"url": "https://github.com/sebastianbergmann/cli-parser.git",
8663
-
"reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180"
8664
-
},
8665
-
"dist": {
8666
-
"type": "zip",
8667
-
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180",
8668
-
"reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180",
8669
-
"shasum": ""
8670
-
},
8671
-
"require": {
8672
-
"php": ">=8.2"
8673
-
},
8674
-
"require-dev": {
8675
-
"phpunit/phpunit": "^11.0"
8676
-
},
8677
-
"type": "library",
8678
-
"extra": {
8679
-
"branch-alias": {
8680
-
"dev-main": "3.0-dev"
8681
-
}
8682
-
},
8683
-
"autoload": {
8684
-
"classmap": [
8685
-
"src/"
8686
-
]
8687
-
},
8688
-
"notification-url": "https://packagist.org/downloads/",
8689
-
"license": [
8690
-
"BSD-3-Clause"
8691
-
],
8692
-
"authors": [
8693
-
{
8694
-
"name": "Sebastian Bergmann",
8695
-
"email": "sebastian@phpunit.de",
8696
-
"role": "lead"
8697
-
}
8698
-
],
8699
-
"description": "Library for parsing CLI options",
8700
-
"homepage": "https://github.com/sebastianbergmann/cli-parser",
8701
-
"support": {
8702
-
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
8703
-
"security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
8704
-
"source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2"
8705
-
},
8706
-
"funding": [
8707
-
{
8708
-
"url": "https://github.com/sebastianbergmann",
8709
-
"type": "github"
8710
-
}
8711
-
],
8712
-
"time": "2024-07-03T04:41:36+00:00"
8713
-
},
8714
-
{
8715
-
"name": "sebastian/code-unit",
8716
-
"version": "3.0.3",
8717
-
"source": {
8718
-
"type": "git",
8719
-
"url": "https://github.com/sebastianbergmann/code-unit.git",
8720
-
"reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64"
8721
-
},
8722
-
"dist": {
8723
-
"type": "zip",
8724
-
"url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64",
8725
-
"reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64",
8726
-
"shasum": ""
8727
-
},
8728
-
"require": {
8729
-
"php": ">=8.2"
8730
-
},
8731
-
"require-dev": {
8732
-
"phpunit/phpunit": "^11.5"
8733
-
},
8734
-
"type": "library",
8735
-
"extra": {
8736
-
"branch-alias": {
8737
-
"dev-main": "3.0-dev"
8738
-
}
8739
-
},
8740
-
"autoload": {
8741
-
"classmap": [
8742
-
"src/"
8743
-
]
8744
-
},
8745
-
"notification-url": "https://packagist.org/downloads/",
8746
-
"license": [
8747
-
"BSD-3-Clause"
8748
-
],
8749
-
"authors": [
8750
-
{
8751
-
"name": "Sebastian Bergmann",
8752
-
"email": "sebastian@phpunit.de",
8753
-
"role": "lead"
8754
-
}
8755
-
],
8756
-
"description": "Collection of value objects that represent the PHP code units",
8757
-
"homepage": "https://github.com/sebastianbergmann/code-unit",
8758
-
"support": {
8759
-
"issues": "https://github.com/sebastianbergmann/code-unit/issues",
8760
-
"security": "https://github.com/sebastianbergmann/code-unit/security/policy",
8761
-
"source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3"
8762
-
},
8763
-
"funding": [
8764
-
{
8765
-
"url": "https://github.com/sebastianbergmann",
8766
-
"type": "github"
8767
-
}
8768
-
],
8769
-
"time": "2025-03-19T07:56:08+00:00"
8770
-
},
8771
-
{
8772
-
"name": "sebastian/code-unit-reverse-lookup",
8773
-
"version": "4.0.1",
8774
-
"source": {
8775
-
"type": "git",
8776
-
"url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
8777
-
"reference": "183a9b2632194febd219bb9246eee421dad8d45e"
8778
-
},
8779
-
"dist": {
8780
-
"type": "zip",
8781
-
"url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e",
8782
-
"reference": "183a9b2632194febd219bb9246eee421dad8d45e",
8783
-
"shasum": ""
8784
-
},
8785
-
"require": {
8786
-
"php": ">=8.2"
8787
-
},
8788
-
"require-dev": {
8789
-
"phpunit/phpunit": "^11.0"
8790
-
},
8791
-
"type": "library",
8792
-
"extra": {
8793
-
"branch-alias": {
8794
-
"dev-main": "4.0-dev"
8795
-
}
8796
-
},
8797
-
"autoload": {
8798
-
"classmap": [
8799
-
"src/"
8800
-
]
8801
-
},
8802
-
"notification-url": "https://packagist.org/downloads/",
8803
-
"license": [
8804
-
"BSD-3-Clause"
8805
-
],
8806
-
"authors": [
8807
-
{
8808
-
"name": "Sebastian Bergmann",
8809
-
"email": "sebastian@phpunit.de"
8810
-
}
8811
-
],
8812
-
"description": "Looks up which function or method a line of code belongs to",
8813
-
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
8814
-
"support": {
8815
-
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
8816
-
"security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
8817
-
"source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1"
8818
-
},
8819
-
"funding": [
8820
-
{
8821
-
"url": "https://github.com/sebastianbergmann",
8822
-
"type": "github"
8823
-
}
8824
-
],
8825
-
"time": "2024-07-03T04:45:54+00:00"
8826
-
},
8827
-
{
8828
-
"name": "sebastian/comparator",
8829
-
"version": "6.3.2",
8830
-
"source": {
8831
-
"type": "git",
8832
-
"url": "https://github.com/sebastianbergmann/comparator.git",
8833
-
"reference": "85c77556683e6eee4323e4c5468641ca0237e2e8"
8834
-
},
8835
-
"dist": {
8836
-
"type": "zip",
8837
-
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8",
8838
-
"reference": "85c77556683e6eee4323e4c5468641ca0237e2e8",
8839
-
"shasum": ""
8840
-
},
8841
-
"require": {
8842
-
"ext-dom": "*",
8843
-
"ext-mbstring": "*",
8844
-
"php": ">=8.2",
8845
-
"sebastian/diff": "^6.0",
8846
-
"sebastian/exporter": "^6.0"
8847
-
},
8848
-
"require-dev": {
8849
-
"phpunit/phpunit": "^11.4"
8850
-
},
8851
-
"suggest": {
8852
-
"ext-bcmath": "For comparing BcMath\\Number objects"
8853
-
},
8854
-
"type": "library",
8855
-
"extra": {
8856
-
"branch-alias": {
8857
-
"dev-main": "6.3-dev"
8858
-
}
8859
-
},
8860
-
"autoload": {
8861
-
"classmap": [
8862
-
"src/"
8863
-
]
8864
-
},
8865
-
"notification-url": "https://packagist.org/downloads/",
8866
-
"license": [
8867
-
"BSD-3-Clause"
8868
-
],
8869
-
"authors": [
8870
-
{
8871
-
"name": "Sebastian Bergmann",
8872
-
"email": "sebastian@phpunit.de"
8873
-
},
8874
-
{
8875
-
"name": "Jeff Welch",
8876
-
"email": "whatthejeff@gmail.com"
8877
-
},
8878
-
{
8879
-
"name": "Volker Dusch",
8880
-
"email": "github@wallbash.com"
8881
-
},
8882
-
{
8883
-
"name": "Bernhard Schussek",
8884
-
"email": "bschussek@2bepublished.at"
8885
-
}
8886
-
],
8887
-
"description": "Provides the functionality to compare PHP values for equality",
8888
-
"homepage": "https://github.com/sebastianbergmann/comparator",
8889
-
"keywords": [
8890
-
"comparator",
8891
-
"compare",
8892
-
"equality"
8893
-
],
8894
-
"support": {
8895
-
"issues": "https://github.com/sebastianbergmann/comparator/issues",
8896
-
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
8897
-
"source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2"
8898
-
},
8899
-
"funding": [
8900
-
{
8901
-
"url": "https://github.com/sebastianbergmann",
8902
-
"type": "github"
8903
-
},
8904
-
{
8905
-
"url": "https://liberapay.com/sebastianbergmann",
8906
-
"type": "liberapay"
8907
-
},
8908
-
{
8909
-
"url": "https://thanks.dev/u/gh/sebastianbergmann",
8910
-
"type": "thanks_dev"
8911
-
},
8912
-
{
8913
-
"url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
8914
-
"type": "tidelift"
8915
-
}
8916
-
],
8917
-
"time": "2025-08-10T08:07:46+00:00"
8918
-
},
8919
-
{
8920
-
"name": "sebastian/complexity",
8921
-
"version": "4.0.1",
8922
-
"source": {
8923
-
"type": "git",
8924
-
"url": "https://github.com/sebastianbergmann/complexity.git",
8925
-
"reference": "ee41d384ab1906c68852636b6de493846e13e5a0"
8926
-
},
8927
-
"dist": {
8928
-
"type": "zip",
8929
-
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0",
8930
-
"reference": "ee41d384ab1906c68852636b6de493846e13e5a0",
8931
-
"shasum": ""
8932
-
},
8933
-
"require": {
8934
-
"nikic/php-parser": "^5.0",
8935
-
"php": ">=8.2"
8936
-
},
8937
-
"require-dev": {
8938
-
"phpunit/phpunit": "^11.0"
8939
-
},
8940
-
"type": "library",
8941
-
"extra": {
8942
-
"branch-alias": {
8943
-
"dev-main": "4.0-dev"
8944
-
}
8945
-
},
8946
-
"autoload": {
8947
-
"classmap": [
8948
-
"src/"
8949
-
]
8950
-
},
8951
-
"notification-url": "https://packagist.org/downloads/",
8952
-
"license": [
8953
-
"BSD-3-Clause"
8954
-
],
8955
-
"authors": [
8956
-
{
8957
-
"name": "Sebastian Bergmann",
8958
-
"email": "sebastian@phpunit.de",
8959
-
"role": "lead"
8960
-
}
8961
-
],
8962
-
"description": "Library for calculating the complexity of PHP code units",
8963
-
"homepage": "https://github.com/sebastianbergmann/complexity",
8964
-
"support": {
8965
-
"issues": "https://github.com/sebastianbergmann/complexity/issues",
8966
-
"security": "https://github.com/sebastianbergmann/complexity/security/policy",
8967
-
"source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1"
8968
-
},
8969
-
"funding": [
8970
-
{
8971
-
"url": "https://github.com/sebastianbergmann",
8972
-
"type": "github"
8973
-
}
8974
-
],
8975
-
"time": "2024-07-03T04:49:50+00:00"
8976
-
},
8977
-
{
8978
-
"name": "sebastian/diff",
8979
-
"version": "6.0.2",
8980
-
"source": {
8981
-
"type": "git",
8982
-
"url": "https://github.com/sebastianbergmann/diff.git",
8983
-
"reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
8984
-
},
8985
-
"dist": {
8986
-
"type": "zip",
8987
-
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
8988
-
"reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
8989
-
"shasum": ""
8990
-
},
8991
-
"require": {
8992
-
"php": ">=8.2"
8993
-
},
8994
-
"require-dev": {
8995
-
"phpunit/phpunit": "^11.0",
8996
-
"symfony/process": "^4.2 || ^5"
8997
-
},
8998
-
"type": "library",
8999
-
"extra": {
9000
-
"branch-alias": {
9001
-
"dev-main": "6.0-dev"
9002
-
}
9003
-
},
9004
-
"autoload": {
9005
-
"classmap": [
9006
-
"src/"
9007
-
]
9008
-
},
9009
-
"notification-url": "https://packagist.org/downloads/",
9010
-
"license": [
9011
-
"BSD-3-Clause"
9012
-
],
9013
-
"authors": [
9014
-
{
9015
-
"name": "Sebastian Bergmann",
9016
-
"email": "sebastian@phpunit.de"
9017
-
},
9018
-
{
9019
-
"name": "Kore Nordmann",
9020
-
"email": "mail@kore-nordmann.de"
9021
-
}
9022
-
],
9023
-
"description": "Diff implementation",
9024
-
"homepage": "https://github.com/sebastianbergmann/diff",
9025
-
"keywords": [
9026
-
"diff",
9027
-
"udiff",
9028
-
"unidiff",
9029
-
"unified diff"
9030
-
],
9031
-
"support": {
9032
-
"issues": "https://github.com/sebastianbergmann/diff/issues",
9033
-
"security": "https://github.com/sebastianbergmann/diff/security/policy",
9034
-
"source": "https://github.com/sebastianbergmann/diff/tree/6.0.2"
9035
-
},
9036
-
"funding": [
9037
-
{
9038
-
"url": "https://github.com/sebastianbergmann",
9039
-
"type": "github"
9040
-
}
9041
-
],
9042
-
"time": "2024-07-03T04:53:05+00:00"
9043
-
},
9044
-
{
9045
-
"name": "sebastian/environment",
9046
-
"version": "7.2.1",
9047
-
"source": {
9048
-
"type": "git",
9049
-
"url": "https://github.com/sebastianbergmann/environment.git",
9050
-
"reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4"
9051
-
},
9052
-
"dist": {
9053
-
"type": "zip",
9054
-
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4",
9055
-
"reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4",
9056
-
"shasum": ""
9057
-
},
9058
-
"require": {
9059
-
"php": ">=8.2"
9060
-
},
9061
-
"require-dev": {
9062
-
"phpunit/phpunit": "^11.3"
9063
-
},
9064
-
"suggest": {
9065
-
"ext-posix": "*"
9066
-
},
9067
-
"type": "library",
9068
-
"extra": {
9069
-
"branch-alias": {
9070
-
"dev-main": "7.2-dev"
9071
-
}
9072
-
},
9073
-
"autoload": {
9074
-
"classmap": [
9075
-
"src/"
9076
-
]
9077
-
},
9078
-
"notification-url": "https://packagist.org/downloads/",
9079
-
"license": [
9080
-
"BSD-3-Clause"
9081
-
],
9082
-
"authors": [
9083
-
{
9084
-
"name": "Sebastian Bergmann",
9085
-
"email": "sebastian@phpunit.de"
9086
-
}
9087
-
],
9088
-
"description": "Provides functionality to handle HHVM/PHP environments",
9089
-
"homepage": "https://github.com/sebastianbergmann/environment",
9090
-
"keywords": [
9091
-
"Xdebug",
9092
-
"environment",
9093
-
"hhvm"
9094
-
],
9095
-
"support": {
9096
-
"issues": "https://github.com/sebastianbergmann/environment/issues",
9097
-
"security": "https://github.com/sebastianbergmann/environment/security/policy",
9098
-
"source": "https://github.com/sebastianbergmann/environment/tree/7.2.1"
9099
-
},
9100
-
"funding": [
9101
-
{
9102
-
"url": "https://github.com/sebastianbergmann",
9103
-
"type": "github"
9104
-
},
9105
-
{
9106
-
"url": "https://liberapay.com/sebastianbergmann",
9107
-
"type": "liberapay"
9108
-
},
9109
-
{
9110
-
"url": "https://thanks.dev/u/gh/sebastianbergmann",
9111
-
"type": "thanks_dev"
9112
-
},
9113
-
{
9114
-
"url": "https://tidelift.com/funding/github/packagist/sebastian/environment",
9115
-
"type": "tidelift"
9116
-
}
9117
-
],
9118
-
"time": "2025-05-21T11:55:47+00:00"
9119
-
},
9120
-
{
9121
-
"name": "sebastian/exporter",
9122
-
"version": "6.3.2",
9123
-
"source": {
9124
-
"type": "git",
9125
-
"url": "https://github.com/sebastianbergmann/exporter.git",
9126
-
"reference": "70a298763b40b213ec087c51c739efcaa90bcd74"
9127
-
},
9128
-
"dist": {
9129
-
"type": "zip",
9130
-
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74",
9131
-
"reference": "70a298763b40b213ec087c51c739efcaa90bcd74",
9132
-
"shasum": ""
9133
-
},
9134
-
"require": {
9135
-
"ext-mbstring": "*",
9136
-
"php": ">=8.2",
9137
-
"sebastian/recursion-context": "^6.0"
9138
-
},
9139
-
"require-dev": {
9140
-
"phpunit/phpunit": "^11.3"
9141
-
},
9142
-
"type": "library",
9143
-
"extra": {
9144
-
"branch-alias": {
9145
-
"dev-main": "6.3-dev"
9146
-
}
9147
-
},
9148
-
"autoload": {
9149
-
"classmap": [
9150
-
"src/"
9151
-
]
9152
-
},
9153
-
"notification-url": "https://packagist.org/downloads/",
9154
-
"license": [
9155
-
"BSD-3-Clause"
9156
-
],
9157
-
"authors": [
9158
-
{
9159
-
"name": "Sebastian Bergmann",
9160
-
"email": "sebastian@phpunit.de"
9161
-
},
9162
-
{
9163
-
"name": "Jeff Welch",
9164
-
"email": "whatthejeff@gmail.com"
9165
-
},
9166
-
{
9167
-
"name": "Volker Dusch",
9168
-
"email": "github@wallbash.com"
9169
-
},
9170
-
{
9171
-
"name": "Adam Harvey",
9172
-
"email": "aharvey@php.net"
9173
-
},
9174
-
{
9175
-
"name": "Bernhard Schussek",
9176
-
"email": "bschussek@gmail.com"
9177
-
}
9178
-
],
9179
-
"description": "Provides the functionality to export PHP variables for visualization",
9180
-
"homepage": "https://www.github.com/sebastianbergmann/exporter",
9181
-
"keywords": [
9182
-
"export",
9183
-
"exporter"
9184
-
],
9185
-
"support": {
9186
-
"issues": "https://github.com/sebastianbergmann/exporter/issues",
9187
-
"security": "https://github.com/sebastianbergmann/exporter/security/policy",
9188
-
"source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2"
9189
-
},
9190
-
"funding": [
9191
-
{
9192
-
"url": "https://github.com/sebastianbergmann",
9193
-
"type": "github"
9194
-
},
9195
-
{
9196
-
"url": "https://liberapay.com/sebastianbergmann",
9197
-
"type": "liberapay"
9198
-
},
9199
-
{
9200
-
"url": "https://thanks.dev/u/gh/sebastianbergmann",
9201
-
"type": "thanks_dev"
9202
-
},
9203
-
{
9204
-
"url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
9205
-
"type": "tidelift"
9206
-
}
9207
-
],
9208
-
"time": "2025-09-24T06:12:51+00:00"
9209
-
},
9210
-
{
9211
-
"name": "sebastian/global-state",
9212
-
"version": "7.0.2",
9213
-
"source": {
9214
-
"type": "git",
9215
-
"url": "https://github.com/sebastianbergmann/global-state.git",
9216
-
"reference": "3be331570a721f9a4b5917f4209773de17f747d7"
9217
-
},
9218
-
"dist": {
9219
-
"type": "zip",
9220
-
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7",
9221
-
"reference": "3be331570a721f9a4b5917f4209773de17f747d7",
9222
-
"shasum": ""
9223
-
},
9224
-
"require": {
9225
-
"php": ">=8.2",
9226
-
"sebastian/object-reflector": "^4.0",
9227
-
"sebastian/recursion-context": "^6.0"
9228
-
},
9229
-
"require-dev": {
9230
-
"ext-dom": "*",
9231
-
"phpunit/phpunit": "^11.0"
9232
-
},
9233
-
"type": "library",
9234
-
"extra": {
9235
-
"branch-alias": {
9236
-
"dev-main": "7.0-dev"
9237
-
}
9238
-
},
9239
-
"autoload": {
9240
-
"classmap": [
9241
-
"src/"
9242
-
]
9243
-
},
9244
-
"notification-url": "https://packagist.org/downloads/",
9245
-
"license": [
9246
-
"BSD-3-Clause"
9247
-
],
9248
-
"authors": [
9249
-
{
9250
-
"name": "Sebastian Bergmann",
9251
-
"email": "sebastian@phpunit.de"
9252
-
}
9253
-
],
9254
-
"description": "Snapshotting of global state",
9255
-
"homepage": "https://www.github.com/sebastianbergmann/global-state",
9256
-
"keywords": [
9257
-
"global state"
9258
-
],
9259
-
"support": {
9260
-
"issues": "https://github.com/sebastianbergmann/global-state/issues",
9261
-
"security": "https://github.com/sebastianbergmann/global-state/security/policy",
9262
-
"source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2"
9263
-
},
9264
-
"funding": [
9265
-
{
9266
-
"url": "https://github.com/sebastianbergmann",
9267
-
"type": "github"
9268
-
}
9269
-
],
9270
-
"time": "2024-07-03T04:57:36+00:00"
9271
-
},
9272
-
{
9273
-
"name": "sebastian/lines-of-code",
9274
-
"version": "3.0.1",
9275
-
"source": {
9276
-
"type": "git",
9277
-
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
9278
-
"reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a"
9279
-
},
9280
-
"dist": {
9281
-
"type": "zip",
9282
-
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a",
9283
-
"reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a",
9284
-
"shasum": ""
9285
-
},
9286
-
"require": {
9287
-
"nikic/php-parser": "^5.0",
9288
-
"php": ">=8.2"
9289
-
},
9290
-
"require-dev": {
9291
-
"phpunit/phpunit": "^11.0"
9292
-
},
9293
-
"type": "library",
9294
-
"extra": {
9295
-
"branch-alias": {
9296
-
"dev-main": "3.0-dev"
9297
-
}
9298
-
},
9299
-
"autoload": {
9300
-
"classmap": [
9301
-
"src/"
9302
-
]
9303
-
},
9304
-
"notification-url": "https://packagist.org/downloads/",
9305
-
"license": [
9306
-
"BSD-3-Clause"
9307
-
],
9308
-
"authors": [
9309
-
{
9310
-
"name": "Sebastian Bergmann",
9311
-
"email": "sebastian@phpunit.de",
9312
-
"role": "lead"
9313
-
}
9314
-
],
9315
-
"description": "Library for counting the lines of code in PHP source code",
9316
-
"homepage": "https://github.com/sebastianbergmann/lines-of-code",
9317
-
"support": {
9318
-
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
9319
-
"security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
9320
-
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1"
9321
-
},
9322
-
"funding": [
9323
-
{
9324
-
"url": "https://github.com/sebastianbergmann",
9325
-
"type": "github"
9326
-
}
9327
-
],
9328
-
"time": "2024-07-03T04:58:38+00:00"
9329
-
},
9330
-
{
9331
-
"name": "sebastian/object-enumerator",
9332
-
"version": "6.0.1",
9333
-
"source": {
9334
-
"type": "git",
9335
-
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
9336
-
"reference": "f5b498e631a74204185071eb41f33f38d64608aa"
9337
-
},
9338
-
"dist": {
9339
-
"type": "zip",
9340
-
"url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa",
9341
-
"reference": "f5b498e631a74204185071eb41f33f38d64608aa",
9342
-
"shasum": ""
9343
-
},
9344
-
"require": {
9345
-
"php": ">=8.2",
9346
-
"sebastian/object-reflector": "^4.0",
9347
-
"sebastian/recursion-context": "^6.0"
9348
-
},
9349
-
"require-dev": {
9350
-
"phpunit/phpunit": "^11.0"
9351
-
},
9352
-
"type": "library",
9353
-
"extra": {
9354
-
"branch-alias": {
9355
-
"dev-main": "6.0-dev"
9356
-
}
9357
-
},
9358
-
"autoload": {
9359
-
"classmap": [
9360
-
"src/"
9361
-
]
9362
-
},
9363
-
"notification-url": "https://packagist.org/downloads/",
9364
-
"license": [
9365
-
"BSD-3-Clause"
9366
-
],
9367
-
"authors": [
9368
-
{
9369
-
"name": "Sebastian Bergmann",
9370
-
"email": "sebastian@phpunit.de"
9371
-
}
9372
-
],
9373
-
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
9374
-
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
9375
-
"support": {
9376
-
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
9377
-
"security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
9378
-
"source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1"
9379
-
},
9380
-
"funding": [
9381
-
{
9382
-
"url": "https://github.com/sebastianbergmann",
9383
-
"type": "github"
9384
-
}
9385
-
],
9386
-
"time": "2024-07-03T05:00:13+00:00"
9387
-
},
9388
-
{
9389
-
"name": "sebastian/object-reflector",
9390
-
"version": "4.0.1",
9391
-
"source": {
9392
-
"type": "git",
9393
-
"url": "https://github.com/sebastianbergmann/object-reflector.git",
9394
-
"reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9"
9395
-
},
9396
-
"dist": {
9397
-
"type": "zip",
9398
-
"url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9",
9399
-
"reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9",
9400
-
"shasum": ""
9401
-
},
9402
-
"require": {
9403
-
"php": ">=8.2"
9404
-
},
9405
-
"require-dev": {
9406
-
"phpunit/phpunit": "^11.0"
9407
-
},
9408
-
"type": "library",
9409
-
"extra": {
9410
-
"branch-alias": {
9411
-
"dev-main": "4.0-dev"
9412
-
}
9413
-
},
9414
-
"autoload": {
9415
-
"classmap": [
9416
-
"src/"
9417
-
]
9418
-
},
9419
-
"notification-url": "https://packagist.org/downloads/",
9420
-
"license": [
9421
-
"BSD-3-Clause"
9422
-
],
9423
-
"authors": [
9424
-
{
9425
-
"name": "Sebastian Bergmann",
9426
-
"email": "sebastian@phpunit.de"
9427
-
}
9428
-
],
9429
-
"description": "Allows reflection of object attributes, including inherited and non-public ones",
9430
-
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
9431
-
"support": {
9432
-
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
9433
-
"security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
9434
-
"source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1"
9435
-
},
9436
-
"funding": [
9437
-
{
9438
-
"url": "https://github.com/sebastianbergmann",
9439
-
"type": "github"
9440
-
}
9441
-
],
9442
-
"time": "2024-07-03T05:01:32+00:00"
9443
-
},
9444
-
{
9445
-
"name": "sebastian/recursion-context",
9446
-
"version": "6.0.3",
9447
-
"source": {
9448
-
"type": "git",
9449
-
"url": "https://github.com/sebastianbergmann/recursion-context.git",
9450
-
"reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc"
9451
-
},
9452
-
"dist": {
9453
-
"type": "zip",
9454
-
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc",
9455
-
"reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc",
9456
-
"shasum": ""
9457
-
},
9458
-
"require": {
9459
-
"php": ">=8.2"
9460
-
},
9461
-
"require-dev": {
9462
-
"phpunit/phpunit": "^11.3"
9463
-
},
9464
-
"type": "library",
9465
-
"extra": {
9466
-
"branch-alias": {
9467
-
"dev-main": "6.0-dev"
9468
-
}
9469
-
},
9470
-
"autoload": {
9471
-
"classmap": [
9472
-
"src/"
9473
-
]
9474
-
},
9475
-
"notification-url": "https://packagist.org/downloads/",
9476
-
"license": [
9477
-
"BSD-3-Clause"
9478
-
],
9479
-
"authors": [
9480
-
{
9481
-
"name": "Sebastian Bergmann",
9482
-
"email": "sebastian@phpunit.de"
9483
-
},
9484
-
{
9485
-
"name": "Jeff Welch",
9486
-
"email": "whatthejeff@gmail.com"
9487
-
},
9488
-
{
9489
-
"name": "Adam Harvey",
9490
-
"email": "aharvey@php.net"
9491
-
}
9492
-
],
9493
-
"description": "Provides functionality to recursively process PHP variables",
9494
-
"homepage": "https://github.com/sebastianbergmann/recursion-context",
9495
-
"support": {
9496
-
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
9497
-
"security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
9498
-
"source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3"
9499
-
},
9500
-
"funding": [
9501
-
{
9502
-
"url": "https://github.com/sebastianbergmann",
9503
-
"type": "github"
9504
-
},
9505
-
{
9506
-
"url": "https://liberapay.com/sebastianbergmann",
9507
-
"type": "liberapay"
9508
-
},
9509
-
{
9510
-
"url": "https://thanks.dev/u/gh/sebastianbergmann",
9511
-
"type": "thanks_dev"
9512
-
},
9513
-
{
9514
-
"url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context",
9515
-
"type": "tidelift"
9516
-
}
9517
-
],
9518
-
"time": "2025-08-13T04:42:22+00:00"
9519
-
},
9520
-
{
9521
-
"name": "sebastian/type",
9522
-
"version": "5.1.3",
9523
-
"source": {
9524
-
"type": "git",
9525
-
"url": "https://github.com/sebastianbergmann/type.git",
9526
-
"reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449"
9527
-
},
9528
-
"dist": {
9529
-
"type": "zip",
9530
-
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449",
9531
-
"reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449",
9532
-
"shasum": ""
9533
-
},
9534
-
"require": {
9535
-
"php": ">=8.2"
9536
-
},
9537
-
"require-dev": {
9538
-
"phpunit/phpunit": "^11.3"
9539
-
},
9540
-
"type": "library",
9541
-
"extra": {
9542
-
"branch-alias": {
9543
-
"dev-main": "5.1-dev"
9544
-
}
9545
-
},
9546
-
"autoload": {
9547
-
"classmap": [
9548
-
"src/"
9549
-
]
9550
-
},
9551
-
"notification-url": "https://packagist.org/downloads/",
9552
-
"license": [
9553
-
"BSD-3-Clause"
9554
-
],
9555
-
"authors": [
9556
-
{
9557
-
"name": "Sebastian Bergmann",
9558
-
"email": "sebastian@phpunit.de",
9559
-
"role": "lead"
9560
-
}
9561
-
],
9562
-
"description": "Collection of value objects that represent the types of the PHP type system",
9563
-
"homepage": "https://github.com/sebastianbergmann/type",
9564
-
"support": {
9565
-
"issues": "https://github.com/sebastianbergmann/type/issues",
9566
-
"security": "https://github.com/sebastianbergmann/type/security/policy",
9567
-
"source": "https://github.com/sebastianbergmann/type/tree/5.1.3"
9568
-
},
9569
-
"funding": [
9570
-
{
9571
-
"url": "https://github.com/sebastianbergmann",
9572
-
"type": "github"
9573
-
},
9574
-
{
9575
-
"url": "https://liberapay.com/sebastianbergmann",
9576
-
"type": "liberapay"
9577
-
},
9578
-
{
9579
-
"url": "https://thanks.dev/u/gh/sebastianbergmann",
9580
-
"type": "thanks_dev"
9581
-
},
9582
-
{
9583
-
"url": "https://tidelift.com/funding/github/packagist/sebastian/type",
9584
-
"type": "tidelift"
9585
-
}
9586
-
],
9587
-
"time": "2025-08-09T06:55:48+00:00"
9588
-
},
9589
-
{
9590
-
"name": "sebastian/version",
9591
-
"version": "5.0.2",
9592
-
"source": {
9593
-
"type": "git",
9594
-
"url": "https://github.com/sebastianbergmann/version.git",
9595
-
"reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874"
9596
-
},
9597
-
"dist": {
9598
-
"type": "zip",
9599
-
"url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874",
9600
-
"reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874",
9601
-
"shasum": ""
9602
-
},
9603
-
"require": {
9604
-
"php": ">=8.2"
9605
-
},
9606
-
"type": "library",
9607
-
"extra": {
9608
-
"branch-alias": {
9609
-
"dev-main": "5.0-dev"
9610
-
}
9611
-
},
9612
-
"autoload": {
9613
-
"classmap": [
9614
-
"src/"
9615
-
]
9616
-
},
9617
-
"notification-url": "https://packagist.org/downloads/",
9618
-
"license": [
9619
-
"BSD-3-Clause"
9620
-
],
9621
-
"authors": [
9622
-
{
9623
-
"name": "Sebastian Bergmann",
9624
-
"email": "sebastian@phpunit.de",
9625
-
"role": "lead"
9626
-
}
9627
-
],
9628
-
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
9629
-
"homepage": "https://github.com/sebastianbergmann/version",
9630
-
"support": {
9631
-
"issues": "https://github.com/sebastianbergmann/version/issues",
9632
-
"security": "https://github.com/sebastianbergmann/version/security/policy",
9633
-
"source": "https://github.com/sebastianbergmann/version/tree/5.0.2"
9634
-
},
9635
-
"funding": [
9636
-
{
9637
-
"url": "https://github.com/sebastianbergmann",
9638
-
"type": "github"
9639
-
}
9640
-
],
9641
-
"time": "2024-10-09T05:16:32+00:00"
9642
-
},
9643
-
{
9644
-
"name": "staabm/side-effects-detector",
9645
-
"version": "1.0.5",
9646
-
"source": {
9647
-
"type": "git",
9648
-
"url": "https://github.com/staabm/side-effects-detector.git",
9649
-
"reference": "d8334211a140ce329c13726d4a715adbddd0a163"
9650
-
},
9651
-
"dist": {
9652
-
"type": "zip",
9653
-
"url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
9654
-
"reference": "d8334211a140ce329c13726d4a715adbddd0a163",
9655
-
"shasum": ""
9656
-
},
9657
-
"require": {
9658
-
"ext-tokenizer": "*",
9659
-
"php": "^7.4 || ^8.0"
9660
-
},
9661
-
"require-dev": {
9662
-
"phpstan/extension-installer": "^1.4.3",
9663
-
"phpstan/phpstan": "^1.12.6",
9664
-
"phpunit/phpunit": "^9.6.21",
9665
-
"symfony/var-dumper": "^5.4.43",
9666
-
"tomasvotruba/type-coverage": "1.0.0",
9667
-
"tomasvotruba/unused-public": "1.0.0"
9668
-
},
9669
-
"type": "library",
9670
-
"autoload": {
9671
-
"classmap": [
9672
-
"lib/"
9673
-
]
9674
-
},
9675
-
"notification-url": "https://packagist.org/downloads/",
9676
-
"license": [
9677
-
"MIT"
9678
-
],
9679
-
"description": "A static analysis tool to detect side effects in PHP code",
9680
-
"keywords": [
9681
-
"static analysis"
9682
-
],
9683
-
"support": {
9684
-
"issues": "https://github.com/staabm/side-effects-detector/issues",
9685
-
"source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
9686
-
},
9687
-
"funding": [
9688
-
{
9689
-
"url": "https://github.com/staabm",
9690
-
"type": "github"
9691
-
}
9692
-
],
9693
-
"time": "2024-10-20T05:08:20+00:00"
9694
-
},
9695
-
{
9696
-
"name": "symfony/polyfill-php84",
9697
-
"version": "v1.33.0",
9698
-
"source": {
9699
-
"type": "git",
9700
-
"url": "https://github.com/symfony/polyfill-php84.git",
9701
-
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191"
9702
-
},
9703
-
"dist": {
9704
-
"type": "zip",
9705
-
"url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191",
9706
-
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191",
9707
-
"shasum": ""
9708
-
},
9709
-
"require": {
9710
-
"php": ">=7.2"
9711
-
},
9712
-
"type": "library",
9713
-
"extra": {
9714
-
"thanks": {
9715
-
"url": "https://github.com/symfony/polyfill",
9716
-
"name": "symfony/polyfill"
9717
-
}
9718
-
},
9719
-
"autoload": {
9720
-
"files": [
9721
-
"bootstrap.php"
9722
-
],
9723
-
"psr-4": {
9724
-
"Symfony\\Polyfill\\Php84\\": ""
9725
-
},
9726
-
"classmap": [
9727
-
"Resources/stubs"
9728
-
]
9729
-
},
9730
-
"notification-url": "https://packagist.org/downloads/",
9731
-
"license": [
9732
-
"MIT"
9733
-
],
9734
-
"authors": [
9735
-
{
9736
-
"name": "Nicolas Grekas",
9737
-
"email": "p@tchwork.com"
9738
-
},
9739
-
{
9740
-
"name": "Symfony Community",
9741
-
"homepage": "https://symfony.com/contributors"
9742
-
}
9743
-
],
9744
-
"description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions",
9745
-
"homepage": "https://symfony.com",
9746
-
"keywords": [
9747
-
"compatibility",
9748
-
"polyfill",
9749
-
"portable",
9750
-
"shim"
9751
-
],
9752
-
"support": {
9753
-
"source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0"
9754
-
},
9755
-
"funding": [
9756
-
{
9757
-
"url": "https://symfony.com/sponsor",
9758
-
"type": "custom"
9759
-
},
9760
-
{
9761
-
"url": "https://github.com/fabpot",
9762
-
"type": "github"
9763
-
},
9764
-
{
9765
-
"url": "https://github.com/nicolas-grekas",
9766
-
"type": "github"
9767
-
},
9768
-
{
9769
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
9770
-
"type": "tidelift"
9771
-
}
9772
-
],
9773
-
"time": "2025-06-24T13:30:11+00:00"
9774
-
},
9775
-
{
9776
-
"name": "symfony/yaml",
9777
-
"version": "v7.3.5",
9778
-
"source": {
9779
-
"type": "git",
9780
-
"url": "https://github.com/symfony/yaml.git",
9781
-
"reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc"
9782
-
},
9783
-
"dist": {
9784
-
"type": "zip",
9785
-
"url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc",
9786
-
"reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc",
9787
-
"shasum": ""
9788
-
},
9789
-
"require": {
9790
-
"php": ">=8.2",
9791
-
"symfony/deprecation-contracts": "^2.5|^3.0",
9792
-
"symfony/polyfill-ctype": "^1.8"
9793
-
},
9794
-
"conflict": {
9795
-
"symfony/console": "<6.4"
9796
-
},
9797
-
"require-dev": {
9798
-
"symfony/console": "^6.4|^7.0"
9799
-
},
9800
-
"bin": [
9801
-
"Resources/bin/yaml-lint"
9802
-
],
9803
-
"type": "library",
9804
-
"autoload": {
9805
-
"psr-4": {
9806
-
"Symfony\\Component\\Yaml\\": ""
9807
-
},
9808
-
"exclude-from-classmap": [
9809
-
"/Tests/"
9810
-
]
9811
-
},
9812
-
"notification-url": "https://packagist.org/downloads/",
9813
-
"license": [
9814
-
"MIT"
9815
-
],
9816
-
"authors": [
9817
-
{
9818
-
"name": "Fabien Potencier",
9819
-
"email": "fabien@symfony.com"
9820
-
},
9821
-
{
9822
-
"name": "Symfony Community",
9823
-
"homepage": "https://symfony.com/contributors"
9824
-
}
9825
-
],
9826
-
"description": "Loads and dumps YAML files",
9827
-
"homepage": "https://symfony.com",
9828
-
"support": {
9829
-
"source": "https://github.com/symfony/yaml/tree/v7.3.5"
9830
-
},
9831
-
"funding": [
9832
-
{
9833
-
"url": "https://symfony.com/sponsor",
9834
-
"type": "custom"
9835
-
},
9836
-
{
9837
-
"url": "https://github.com/fabpot",
9838
-
"type": "github"
9839
-
},
9840
-
{
9841
-
"url": "https://github.com/nicolas-grekas",
9842
-
"type": "github"
9843
-
},
9844
-
{
9845
-
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
9846
-
"type": "tidelift"
9847
-
}
9848
-
],
9849
-
"time": "2025-09-27T09:00:46+00:00"
9850
-
},
9851
-
{
9852
-
"name": "theseer/tokenizer",
9853
-
"version": "1.2.3",
9854
-
"source": {
9855
-
"type": "git",
9856
-
"url": "https://github.com/theseer/tokenizer.git",
9857
-
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
9858
-
},
9859
-
"dist": {
9860
-
"type": "zip",
9861
-
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
9862
-
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
9863
-
"shasum": ""
9864
-
},
9865
-
"require": {
9866
-
"ext-dom": "*",
9867
-
"ext-tokenizer": "*",
9868
-
"ext-xmlwriter": "*",
9869
-
"php": "^7.2 || ^8.0"
9870
-
},
9871
-
"type": "library",
9872
-
"autoload": {
9873
-
"classmap": [
9874
-
"src/"
9875
-
]
9876
-
},
9877
-
"notification-url": "https://packagist.org/downloads/",
9878
-
"license": [
9879
-
"BSD-3-Clause"
9880
-
],
9881
-
"authors": [
9882
-
{
9883
-
"name": "Arne Blankerts",
9884
-
"email": "arne@blankerts.de",
9885
-
"role": "Developer"
9886
-
}
9887
-
],
9888
-
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
9889
-
"support": {
9890
-
"issues": "https://github.com/theseer/tokenizer/issues",
9891
-
"source": "https://github.com/theseer/tokenizer/tree/1.2.3"
9892
-
},
9893
-
"funding": [
9894
-
{
9895
-
"url": "https://github.com/theseer",
9896
-
"type": "github"
9897
-
}
9898
-
],
9899
-
"time": "2024-03-03T12:36:25+00:00"
9900
-
}
9901
-
],
9902
-
"aliases": [],
9903
-
"minimum-stability": "stable",
9904
-
"stability-flags": {},
9905
-
"prefer-stable": false,
9906
-
"prefer-lowest": false,
9907
-
"platform": {
9908
-
"php": "^8.2"
9909
-
},
9910
-
"platform-dev": {},
9911
-
"plugin-api-version": "2.6.0"
9912
-
}
+687
docs/configuration.md
+687
docs/configuration.md
···
1
+
# Configuration
2
+
3
+
Signal's configuration file provides complete control over how your application consumes AT Protocol events.
4
+
5
+
## Configuration File
6
+
7
+
After installation, configuration lives in `config/signal.php`.
8
+
9
+
### Publishing Configuration
10
+
11
+
Publish the config file manually if needed:
12
+
13
+
```bash
14
+
php artisan vendor:publish --tag=signal-config
15
+
```
16
+
17
+
This creates `config/signal.php` with all available options.
18
+
19
+
## Environment Variables
20
+
21
+
Most configuration can be set via `.env` for environment-specific values.
22
+
23
+
### Basic Configuration
24
+
25
+
```env
26
+
# Consumer Mode (jetstream or firehose)
27
+
SIGNAL_MODE=jetstream
28
+
29
+
# Jetstream Configuration
30
+
SIGNAL_JETSTREAM_URL=wss://jetstream2.us-east.bsky.network
31
+
32
+
# Firehose Configuration
33
+
SIGNAL_FIREHOSE_HOST=bsky.network
34
+
35
+
# Cursor Storage (database, redis, or file)
36
+
SIGNAL_CURSOR_STORAGE=database
37
+
38
+
# Queue Configuration
39
+
SIGNAL_QUEUE_CONNECTION=redis
40
+
SIGNAL_QUEUE=signal
41
+
```
42
+
43
+
## Consumer Mode
44
+
45
+
Choose between Jetstream and Firehose mode.
46
+
47
+
### Configuration
48
+
49
+
```php
50
+
'mode' => env('SIGNAL_MODE', 'jetstream'),
51
+
```
52
+
53
+
**Options:**
54
+
- `jetstream` - JSON events, server-side filtering (default)
55
+
- `firehose` - CBOR/CAR events, client-side filtering
56
+
57
+
**Environment Variable:**
58
+
```env
59
+
SIGNAL_MODE=jetstream
60
+
```
61
+
62
+
**When to use each:**
63
+
- **Jetstream**: Standard Bluesky collections, production efficiency
64
+
- **Firehose**: Custom collections, AppViews, comprehensive indexing
65
+
66
+
[Learn more about modes โ](modes.md)
67
+
68
+
## Jetstream Configuration
69
+
70
+
Configuration specific to Jetstream mode.
71
+
72
+
### WebSocket URL
73
+
74
+
```php
75
+
'jetstream' => [
76
+
'websocket_url' => env(
77
+
'SIGNAL_JETSTREAM_URL',
78
+
'wss://jetstream2.us-east.bsky.network'
79
+
),
80
+
],
81
+
```
82
+
83
+
**Available Endpoints:**
84
+
85
+
**US East (Default):**
86
+
```env
87
+
SIGNAL_JETSTREAM_URL=wss://jetstream2.us-east.bsky.network
88
+
```
89
+
90
+
**US West:**
91
+
```env
92
+
SIGNAL_JETSTREAM_URL=wss://jetstream1.us-west.bsky.network
93
+
```
94
+
95
+
Choose the endpoint closest to your server for best latency.
96
+
97
+
## Firehose Configuration
98
+
99
+
Configuration specific to Firehose mode.
100
+
101
+
### Host
102
+
103
+
```php
104
+
'firehose' => [
105
+
'host' => env('SIGNAL_FIREHOSE_HOST', 'bsky.network'),
106
+
],
107
+
```
108
+
109
+
The WebSocket URL is constructed as:
110
+
```
111
+
wss://{host}/xrpc/com.atproto.sync.subscribeRepos
112
+
```
113
+
114
+
**Environment Variable:**
115
+
```env
116
+
SIGNAL_FIREHOSE_HOST=bsky.network
117
+
```
118
+
119
+
**Default Host:** `bsky.network`
120
+
121
+
**Custom Hosts:** If you're running your own AT Protocol PDS, specify it here:
122
+
```env
123
+
SIGNAL_FIREHOSE_HOST=my-pds.example.com
124
+
```
125
+
126
+
## Cursor Storage
127
+
128
+
Configure how Signal stores cursor positions for resuming after disconnections.
129
+
130
+
### Storage Driver
131
+
132
+
```php
133
+
'cursor_storage' => env('SIGNAL_CURSOR_STORAGE', 'database'),
134
+
```
135
+
136
+
**Available Drivers:**
137
+
- `database` - Store in database (recommended for production)
138
+
- `redis` - Store in Redis (high performance)
139
+
- `file` - Store in filesystem (development only)
140
+
141
+
**Environment Variable:**
142
+
```env
143
+
SIGNAL_CURSOR_STORAGE=database
144
+
```
145
+
146
+
### Database Driver
147
+
148
+
Uses Laravel's default database connection.
149
+
150
+
**Configuration:**
151
+
```php
152
+
'cursor_storage' => 'database',
153
+
```
154
+
155
+
**Requires:**
156
+
- Migration published and run
157
+
- Database connection configured
158
+
159
+
**Table:** `signal_cursors`
160
+
161
+
### Redis Driver
162
+
163
+
Stores cursors in Redis for high performance.
164
+
165
+
**Configuration:**
166
+
```php
167
+
'cursor_storage' => 'redis',
168
+
169
+
'redis' => [
170
+
'connection' => env('SIGNAL_REDIS_CONNECTION', 'default'),
171
+
'key_prefix' => env('SIGNAL_REDIS_PREFIX', 'signal:cursor:'),
172
+
],
173
+
```
174
+
175
+
**Environment Variables:**
176
+
```env
177
+
SIGNAL_CURSOR_STORAGE=redis
178
+
SIGNAL_REDIS_CONNECTION=default
179
+
SIGNAL_REDIS_PREFIX=signal:cursor:
180
+
```
181
+
182
+
**Requires:**
183
+
- Redis connection configured in `config/database.php`
184
+
- Redis server running
185
+
186
+
### File Driver
187
+
188
+
Stores cursors in the filesystem (development only).
189
+
190
+
**Configuration:**
191
+
```php
192
+
'cursor_storage' => 'file',
193
+
194
+
'file' => [
195
+
'path' => env('SIGNAL_FILE_PATH', storage_path('app/signal')),
196
+
],
197
+
```
198
+
199
+
**Environment Variables:**
200
+
```env
201
+
SIGNAL_CURSOR_STORAGE=file
202
+
SIGNAL_FILE_PATH=/path/to/storage/signal
203
+
```
204
+
205
+
**Not recommended for production:**
206
+
- Single server only
207
+
- No clustering support
208
+
- Filesystem I/O overhead
209
+
210
+
## Queue Configuration
211
+
212
+
Configure how Signal dispatches queued jobs.
213
+
214
+
### Queue Connection
215
+
216
+
```php
217
+
'queue' => [
218
+
'connection' => env('SIGNAL_QUEUE_CONNECTION', null),
219
+
'queue' => env('SIGNAL_QUEUE', 'default'),
220
+
],
221
+
```
222
+
223
+
**Environment Variables:**
224
+
```env
225
+
# Queue connection (redis, database, sqs, etc.)
226
+
SIGNAL_QUEUE_CONNECTION=redis
227
+
228
+
# Queue name
229
+
SIGNAL_QUEUE=signal
230
+
```
231
+
232
+
**Defaults:**
233
+
- `connection`: Uses Laravel's default queue connection
234
+
- `queue`: Uses Laravel's default queue name
235
+
236
+
### Per-Signal Configuration
237
+
238
+
Signals can override queue configuration:
239
+
240
+
```php
241
+
public function shouldQueue(): bool
242
+
{
243
+
return true;
244
+
}
245
+
246
+
public function queueConnection(): string
247
+
{
248
+
return 'redis'; // Override connection
249
+
}
250
+
251
+
public function queue(): string
252
+
{
253
+
return 'high-priority'; // Override queue name
254
+
}
255
+
```
256
+
257
+
[Learn more about queue integration โ](queues.md)
258
+
259
+
## Auto-Discovery
260
+
261
+
Configure automatic Signal discovery.
262
+
263
+
### Enable/Disable
264
+
265
+
```php
266
+
'auto_discovery' => [
267
+
'enabled' => true,
268
+
'path' => app_path('Signals'),
269
+
'namespace' => 'App\\Signals',
270
+
],
271
+
```
272
+
273
+
**Options:**
274
+
- `enabled`: Enable/disable auto-discovery (default: `true`)
275
+
- `path`: Directory to scan for Signals (default: `app/Signals`)
276
+
- `namespace`: Namespace for discovered Signals (default: `App\Signals`)
277
+
278
+
### Disable Auto-Discovery
279
+
280
+
Manually register Signals instead:
281
+
282
+
```php
283
+
'auto_discovery' => [
284
+
'enabled' => false,
285
+
],
286
+
287
+
'signals' => [
288
+
\App\Signals\NewPostSignal::class,
289
+
\App\Signals\NewFollowSignal::class,
290
+
],
291
+
```
292
+
293
+
### Custom Discovery Path
294
+
295
+
Organize Signals in a custom directory:
296
+
297
+
```php
298
+
'auto_discovery' => [
299
+
'enabled' => true,
300
+
'path' => app_path('Domain/Signals'),
301
+
'namespace' => 'App\\Domain\\Signals',
302
+
],
303
+
```
304
+
305
+
## Manual Signal Registration
306
+
307
+
Register Signals explicitly.
308
+
309
+
### Configuration
310
+
311
+
```php
312
+
'signals' => [
313
+
\App\Signals\NewPostSignal::class,
314
+
\App\Signals\NewFollowSignal::class,
315
+
\App\Signals\ProfileUpdateSignal::class,
316
+
],
317
+
```
318
+
319
+
**When to use:**
320
+
- Auto-discovery disabled
321
+
- Signals outside standard directory
322
+
- Fine-grained control over which Signals run
323
+
324
+
## Logging
325
+
326
+
Signal uses Laravel's logging system.
327
+
328
+
### Configure Logging
329
+
330
+
Standard Laravel log configuration applies:
331
+
332
+
```php
333
+
// config/logging.php
334
+
'channels' => [
335
+
'signal' => [
336
+
'driver' => 'daily',
337
+
'path' => storage_path('logs/signal.log'),
338
+
'level' => env('SIGNAL_LOG_LEVEL', 'debug'),
339
+
'days' => 14,
340
+
],
341
+
],
342
+
```
343
+
344
+
Use in Signals:
345
+
346
+
```php
347
+
use Illuminate\Support\Facades\Log;
348
+
349
+
public function handle(SignalEvent $event): void
350
+
{
351
+
Log::channel('signal')->info('Processing event', [
352
+
'did' => $event->did,
353
+
]);
354
+
}
355
+
```
356
+
357
+
## Complete Configuration Reference
358
+
359
+
Here's the full `config/signal.php` with all options:
360
+
361
+
```php
362
+
<?php
363
+
364
+
return [
365
+
366
+
/*
367
+
|--------------------------------------------------------------------------
368
+
| Consumer Mode
369
+
|--------------------------------------------------------------------------
370
+
|
371
+
| Choose between 'jetstream' (JSON events) or 'firehose' (CBOR/CAR events).
372
+
| Jetstream is more efficient for standard Bluesky collections.
373
+
| Firehose is required for custom collections.
374
+
|
375
+
| Options: 'jetstream', 'firehose'
376
+
|
377
+
*/
378
+
379
+
'mode' => env('SIGNAL_MODE', 'jetstream'),
380
+
381
+
/*
382
+
|--------------------------------------------------------------------------
383
+
| Jetstream Configuration
384
+
|--------------------------------------------------------------------------
385
+
|
386
+
| Configuration for Jetstream mode (JSON events).
387
+
|
388
+
*/
389
+
390
+
'jetstream' => [
391
+
'websocket_url' => env(
392
+
'SIGNAL_JETSTREAM_URL',
393
+
'wss://jetstream2.us-east.bsky.network'
394
+
),
395
+
],
396
+
397
+
/*
398
+
|--------------------------------------------------------------------------
399
+
| Firehose Configuration
400
+
|--------------------------------------------------------------------------
401
+
|
402
+
| Configuration for Firehose mode (CBOR/CAR events).
403
+
|
404
+
*/
405
+
406
+
'firehose' => [
407
+
'host' => env('SIGNAL_FIREHOSE_HOST', 'bsky.network'),
408
+
],
409
+
410
+
/*
411
+
|--------------------------------------------------------------------------
412
+
| Cursor Storage
413
+
|--------------------------------------------------------------------------
414
+
|
415
+
| Configure how Signal stores cursor positions for resuming after
416
+
| disconnections. Options: 'database', 'redis', 'file'
417
+
|
418
+
*/
419
+
420
+
'cursor_storage' => env('SIGNAL_CURSOR_STORAGE', 'database'),
421
+
422
+
/*
423
+
|--------------------------------------------------------------------------
424
+
| Redis Configuration
425
+
|--------------------------------------------------------------------------
426
+
|
427
+
| Configuration for Redis cursor storage.
428
+
|
429
+
*/
430
+
431
+
'redis' => [
432
+
'connection' => env('SIGNAL_REDIS_CONNECTION', 'default'),
433
+
'key_prefix' => env('SIGNAL_REDIS_PREFIX', 'signal:cursor:'),
434
+
],
435
+
436
+
/*
437
+
|--------------------------------------------------------------------------
438
+
| File Configuration
439
+
|--------------------------------------------------------------------------
440
+
|
441
+
| Configuration for file-based cursor storage.
442
+
|
443
+
*/
444
+
445
+
'file' => [
446
+
'path' => env('SIGNAL_FILE_PATH', storage_path('app/signal')),
447
+
],
448
+
449
+
/*
450
+
|--------------------------------------------------------------------------
451
+
| Queue Configuration
452
+
|--------------------------------------------------------------------------
453
+
|
454
+
| Configure queue connection and name for processing events asynchronously.
455
+
|
456
+
*/
457
+
458
+
'queue' => [
459
+
'connection' => env('SIGNAL_QUEUE_CONNECTION', null),
460
+
'queue' => env('SIGNAL_QUEUE', 'default'),
461
+
],
462
+
463
+
/*
464
+
|--------------------------------------------------------------------------
465
+
| Auto-Discovery
466
+
|--------------------------------------------------------------------------
467
+
|
468
+
| Automatically discover and register Signals from the specified directory.
469
+
|
470
+
*/
471
+
472
+
'auto_discovery' => [
473
+
'enabled' => true,
474
+
'path' => app_path('Signals'),
475
+
'namespace' => 'App\\Signals',
476
+
],
477
+
478
+
/*
479
+
|--------------------------------------------------------------------------
480
+
| Manual Signal Registration
481
+
|--------------------------------------------------------------------------
482
+
|
483
+
| Manually register Signals if auto-discovery is disabled.
484
+
|
485
+
*/
486
+
487
+
'signals' => [
488
+
// \App\Signals\NewPostSignal::class,
489
+
],
490
+
491
+
];
492
+
```
493
+
494
+
## Environment-Specific Configuration
495
+
496
+
### Development
497
+
498
+
```env
499
+
SIGNAL_MODE=firehose
500
+
SIGNAL_CURSOR_STORAGE=file
501
+
SIGNAL_QUEUE_CONNECTION=sync
502
+
```
503
+
504
+
**Why:**
505
+
- Firehose mode sees all events (comprehensive testing)
506
+
- File storage is simple and adequate
507
+
- Sync queue processes immediately (easier debugging)
508
+
509
+
### Staging
510
+
511
+
```env
512
+
SIGNAL_MODE=jetstream
513
+
SIGNAL_CURSOR_STORAGE=redis
514
+
SIGNAL_QUEUE_CONNECTION=redis
515
+
SIGNAL_QUEUE=signal-staging
516
+
```
517
+
518
+
**Why:**
519
+
- Jetstream mode matches production
520
+
- Redis for performance testing
521
+
- Separate queue for staging isolation
522
+
523
+
### Production
524
+
525
+
```env
526
+
SIGNAL_MODE=jetstream
527
+
SIGNAL_CURSOR_STORAGE=database
528
+
SIGNAL_QUEUE_CONNECTION=redis
529
+
SIGNAL_QUEUE=signal
530
+
```
531
+
532
+
**Why:**
533
+
- Jetstream mode for efficiency
534
+
- Database storage for reliability
535
+
- Redis queues for performance
536
+
537
+
## Runtime Configuration
538
+
539
+
Change configuration at runtime:
540
+
541
+
```php
542
+
use SocialDept\AtpSignals\Facades\Signal;
543
+
544
+
// Override mode
545
+
config(['signal.mode' => 'firehose']);
546
+
547
+
// Override cursor storage
548
+
config(['signal.cursor_storage' => 'redis']);
549
+
550
+
// Start consumer with new config
551
+
Signal::start();
552
+
```
553
+
554
+
## Validation
555
+
556
+
Signal validates configuration on startup:
557
+
558
+
```bash
559
+
php artisan signal:consume
560
+
```
561
+
562
+
**Checks:**
563
+
- Mode is valid (`jetstream` or `firehose`)
564
+
- Cursor storage driver exists
565
+
- Required endpoints are configured
566
+
- Queue configuration is valid
567
+
568
+
**Validation errors prevent consumer from starting.**
569
+
570
+
## Configuration Helpers
571
+
572
+
### Check Current Mode
573
+
574
+
```php
575
+
$mode = config('signal.mode'); // 'jetstream' or 'firehose'
576
+
```
577
+
578
+
Or via Facade:
579
+
580
+
```php
581
+
use SocialDept\AtpSignals\Facades\Signal;
582
+
583
+
$mode = Signal::getMode();
584
+
```
585
+
586
+
### Check Cursor Storage
587
+
588
+
```php
589
+
$storage = config('signal.cursor_storage'); // 'database', 'redis', or 'file'
590
+
```
591
+
592
+
### Check Queue Configuration
593
+
594
+
```php
595
+
$connection = config('signal.queue.connection');
596
+
$queue = config('signal.queue.queue');
597
+
```
598
+
599
+
## Best Practices
600
+
601
+
### Use Environment Variables
602
+
603
+
Don't hardcode values in config file:
604
+
605
+
```php
606
+
// Good
607
+
'mode' => env('SIGNAL_MODE', 'jetstream'),
608
+
609
+
// Bad
610
+
'mode' => 'jetstream',
611
+
```
612
+
613
+
### Separate Staging and Production
614
+
615
+
Use different queues and storage:
616
+
617
+
```env
618
+
# .env.staging
619
+
SIGNAL_QUEUE=signal-staging
620
+
621
+
# .env.production
622
+
SIGNAL_QUEUE=signal-production
623
+
```
624
+
625
+
### Document Custom Configuration
626
+
627
+
If you change defaults, document why:
628
+
629
+
```php
630
+
// We use Firehose mode because we have custom collections
631
+
'mode' => env('SIGNAL_MODE', 'firehose'),
632
+
```
633
+
634
+
### Version Control
635
+
636
+
Commit `config/signal.php` but not `.env`:
637
+
638
+
```gitignore
639
+
# .gitignore
640
+
.env
641
+
.env.*
642
+
643
+
# Commit
644
+
config/signal.php
645
+
```
646
+
647
+
## Troubleshooting
648
+
649
+
### Configuration Not Loading
650
+
651
+
Clear config cache:
652
+
653
+
```bash
654
+
php artisan config:clear
655
+
php artisan config:cache
656
+
```
657
+
658
+
### Environment Variables Not Working
659
+
660
+
Check `.env` file exists and is readable:
661
+
662
+
```bash
663
+
ls -la .env
664
+
```
665
+
666
+
Restart services after changing `.env`:
667
+
668
+
```bash
669
+
# If using Supervisor
670
+
sudo supervisorctl restart signal-consumer:*
671
+
```
672
+
673
+
### Invalid Configuration
674
+
675
+
Run consumer to see validation errors:
676
+
677
+
```bash
678
+
php artisan signal:consume
679
+
```
680
+
681
+
Signal will display specific errors about misconfiguration.
682
+
683
+
## Next Steps
684
+
685
+
- **[Learn about testing โ](testing.md)** - Test your configuration
686
+
- **[See real-world examples โ](examples.md)** - Learn from production configurations
687
+
- **[Review queue integration โ](queues.md)** - Configure queues optimally
+795
docs/examples.md
+795
docs/examples.md
···
1
+
# Real-World Examples
2
+
3
+
Learn from production-ready Signal examples covering common use cases.
4
+
5
+
## Social Media Analytics
6
+
7
+
Track engagement metrics across Bluesky.
8
+
9
+
```php
10
+
<?php
11
+
12
+
namespace App\Signals;
13
+
14
+
use App\Models\EngagementMetric;
15
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
16
+
use SocialDept\AtpSignals\Events\SignalEvent;
17
+
use SocialDept\AtpSignals\Signals\Signal;
18
+
use Illuminate\Support\Facades\DB;
19
+
20
+
class EngagementTrackerSignal extends Signal
21
+
{
22
+
public function eventTypes(): array
23
+
{
24
+
return ['commit'];
25
+
}
26
+
27
+
public function collections(): ?array
28
+
{
29
+
return [
30
+
'app.bsky.feed.post',
31
+
'app.bsky.feed.like',
32
+
'app.bsky.feed.repost',
33
+
'app.bsky.graph.follow',
34
+
];
35
+
}
36
+
37
+
public function operations(): ?array
38
+
{
39
+
return [SignalCommitOperation::Create];
40
+
}
41
+
42
+
public function shouldQueue(): bool
43
+
{
44
+
return true;
45
+
}
46
+
47
+
public function handle(SignalEvent $event): void
48
+
{
49
+
$collection = $event->getCollection();
50
+
$timestamp = $event->getTimestamp();
51
+
52
+
// Increment counter for this hour
53
+
DB::table('engagement_metrics')
54
+
->updateOrInsert(
55
+
[
56
+
'collection' => $collection,
57
+
'hour' => $timestamp->startOfHour(),
58
+
],
59
+
[
60
+
'count' => DB::raw('count + 1'),
61
+
'updated_at' => now(),
62
+
]
63
+
);
64
+
}
65
+
}
66
+
```
67
+
68
+
**Use case:** Build analytics dashboards showing posts/hour, likes/hour, follows/hour.
69
+
70
+
## Content Moderation
71
+
72
+
Automatically flag problematic content.
73
+
74
+
```php
75
+
<?php
76
+
77
+
namespace App\Signals;
78
+
79
+
use App\Models\FlaggedPost;
80
+
use App\Services\ModerationService;
81
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
82
+
use SocialDept\AtpSignals\Events\SignalEvent;
83
+
use SocialDept\AtpSignals\Signals\Signal;
84
+
85
+
class ModerationSignal extends Signal
86
+
{
87
+
public function __construct(
88
+
private ModerationService $moderation
89
+
) {}
90
+
91
+
public function eventTypes(): array
92
+
{
93
+
return ['commit'];
94
+
}
95
+
96
+
public function collections(): ?array
97
+
{
98
+
return ['app.bsky.feed.post'];
99
+
}
100
+
101
+
public function operations(): ?array
102
+
{
103
+
return [SignalCommitOperation::Create];
104
+
}
105
+
106
+
public function shouldQueue(): bool
107
+
{
108
+
return true;
109
+
}
110
+
111
+
public function queue(): string
112
+
{
113
+
return 'moderation';
114
+
}
115
+
116
+
public function handle(SignalEvent $event): void
117
+
{
118
+
$record = $event->getRecord();
119
+
120
+
if (!isset($record->text)) {
121
+
return;
122
+
}
123
+
124
+
$result = $this->moderation->analyze($record->text);
125
+
126
+
if ($result->needsReview) {
127
+
FlaggedPost::create([
128
+
'did' => $event->did,
129
+
'rkey' => $event->commit->rkey,
130
+
'text' => $record->text,
131
+
'reason' => $result->reason,
132
+
'confidence' => $result->confidence,
133
+
'flagged_at' => now(),
134
+
]);
135
+
}
136
+
}
137
+
138
+
public function failed(SignalEvent $event, \Throwable $exception): void
139
+
{
140
+
Log::error('Moderation signal failed', [
141
+
'did' => $event->did,
142
+
'error' => $exception->getMessage(),
143
+
]);
144
+
}
145
+
}
146
+
```
147
+
148
+
**Use case:** Automated content moderation with human review queue.
149
+
150
+
## User Activity Feed
151
+
152
+
Build a personalized activity feed.
153
+
154
+
```php
155
+
<?php
156
+
157
+
namespace App\Signals;
158
+
159
+
use App\Models\Activity;
160
+
use App\Models\User;
161
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
162
+
use SocialDept\AtpSignals\Events\SignalEvent;
163
+
use SocialDept\AtpSignals\Signals\Signal;
164
+
165
+
class ActivityFeedSignal extends Signal
166
+
{
167
+
public function eventTypes(): array
168
+
{
169
+
return ['commit'];
170
+
}
171
+
172
+
public function collections(): ?array
173
+
{
174
+
return [
175
+
'app.bsky.feed.post',
176
+
'app.bsky.feed.like',
177
+
'app.bsky.feed.repost',
178
+
];
179
+
}
180
+
181
+
public function operations(): ?array
182
+
{
183
+
return [SignalCommitOperation::Create];
184
+
}
185
+
186
+
public function shouldQueue(): bool
187
+
{
188
+
return true;
189
+
}
190
+
191
+
public function handle(SignalEvent $event): void
192
+
{
193
+
// Check if we're tracking this user
194
+
$user = User::where('did', $event->did)->first();
195
+
196
+
if (!$user) {
197
+
return;
198
+
}
199
+
200
+
// Check if any followers want to see this
201
+
$followerIds = $user->followers()->pluck('id');
202
+
203
+
if ($followerIds->isEmpty()) {
204
+
return;
205
+
}
206
+
207
+
$collection = $event->getCollection();
208
+
209
+
// Create activity for each follower's feed
210
+
foreach ($followerIds as $followerId) {
211
+
Activity::create([
212
+
'user_id' => $followerId,
213
+
'actor_did' => $event->did,
214
+
'type' => $this->getActivityType($collection),
215
+
'data' => $event->toArray(),
216
+
'created_at' => $event->getTimestamp(),
217
+
]);
218
+
}
219
+
}
220
+
221
+
private function getActivityType(string $collection): string
222
+
{
223
+
return match ($collection) {
224
+
'app.bsky.feed.post' => 'post',
225
+
'app.bsky.feed.like' => 'like',
226
+
'app.bsky.feed.repost' => 'repost',
227
+
default => 'unknown',
228
+
};
229
+
}
230
+
}
231
+
```
232
+
233
+
**Use case:** Show users activity from people they follow.
234
+
235
+
## Real-Time Notifications
236
+
237
+
Send notifications for mentions and interactions.
238
+
239
+
```php
240
+
<?php
241
+
242
+
namespace App\Signals;
243
+
244
+
use App\Models\User;
245
+
use App\Notifications\MentionedInPost;
246
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
247
+
use SocialDept\AtpSignals\Events\SignalEvent;
248
+
use SocialDept\AtpSignals\Signals\Signal;
249
+
250
+
class MentionNotificationSignal extends Signal
251
+
{
252
+
public function eventTypes(): array
253
+
{
254
+
return ['commit'];
255
+
}
256
+
257
+
public function collections(): ?array
258
+
{
259
+
return ['app.bsky.feed.post'];
260
+
}
261
+
262
+
public function operations(): ?array
263
+
{
264
+
return [SignalCommitOperation::Create];
265
+
}
266
+
267
+
public function shouldQueue(): bool
268
+
{
269
+
return true;
270
+
}
271
+
272
+
public function handle(SignalEvent $event): void
273
+
{
274
+
$record = $event->getRecord();
275
+
276
+
if (!isset($record->facets)) {
277
+
return;
278
+
}
279
+
280
+
// Extract mentions from facets
281
+
$mentions = collect($record->facets)
282
+
->filter(fn($facet) => isset($facet->features))
283
+
->flatMap(fn($facet) => $facet->features)
284
+
->filter(fn($feature) => $feature->{'$type'} === 'app.bsky.richtext.facet#mention')
285
+
->pluck('did')
286
+
->unique();
287
+
288
+
foreach ($mentions as $mentionedDid) {
289
+
$user = User::where('did', $mentionedDid)->first();
290
+
291
+
if ($user) {
292
+
$user->notify(new MentionedInPost(
293
+
authorDid: $event->did,
294
+
text: $record->text ?? '',
295
+
uri: "at://{$event->did}/app.bsky.feed.post/{$event->commit->rkey}"
296
+
));
297
+
}
298
+
}
299
+
}
300
+
}
301
+
```
302
+
303
+
**Use case:** Real-time notifications when users are mentioned.
304
+
305
+
## Follow Tracker
306
+
307
+
Track follow relationships and send notifications.
308
+
309
+
```php
310
+
<?php
311
+
312
+
namespace App\Signals;
313
+
314
+
use App\Models\Follow;
315
+
use App\Models\User;
316
+
use App\Notifications\NewFollower;
317
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
318
+
use SocialDept\AtpSignals\Events\SignalEvent;
319
+
use SocialDept\AtpSignals\Signals\Signal;
320
+
321
+
class FollowTrackerSignal extends Signal
322
+
{
323
+
public function eventTypes(): array
324
+
{
325
+
return ['commit'];
326
+
}
327
+
328
+
public function collections(): ?array
329
+
{
330
+
return ['app.bsky.graph.follow'];
331
+
}
332
+
333
+
public function operations(): ?array
334
+
{
335
+
return [
336
+
SignalCommitOperation::Create,
337
+
SignalCommitOperation::Delete,
338
+
];
339
+
}
340
+
341
+
public function shouldQueue(): bool
342
+
{
343
+
return true;
344
+
}
345
+
346
+
public function handle(SignalEvent $event): void
347
+
{
348
+
$record = $event->getRecord();
349
+
$operation = $event->getOperation();
350
+
351
+
if ($operation === SignalCommitOperation::Create) {
352
+
$this->handleNewFollow($event, $record);
353
+
} else {
354
+
$this->handleUnfollow($event);
355
+
}
356
+
}
357
+
358
+
private function handleNewFollow(SignalEvent $event, object $record): void
359
+
{
360
+
Follow::create([
361
+
'follower_did' => $event->did,
362
+
'following_did' => $record->subject,
363
+
'created_at' => $record->createdAt ?? now(),
364
+
]);
365
+
366
+
// Notify the followed user
367
+
$followedUser = User::where('did', $record->subject)->first();
368
+
369
+
if ($followedUser) {
370
+
$followedUser->notify(new NewFollower($event->did));
371
+
}
372
+
}
373
+
374
+
private function handleUnfollow(SignalEvent $event): void
375
+
{
376
+
Follow::where('follower_did', $event->did)
377
+
->where('rkey', $event->commit->rkey)
378
+
->delete();
379
+
}
380
+
}
381
+
```
382
+
383
+
**Use case:** Track follows and notify users of new followers.
384
+
385
+
## Search Indexer
386
+
387
+
Index posts for full-text search.
388
+
389
+
```php
390
+
<?php
391
+
392
+
namespace App\Signals;
393
+
394
+
use App\Models\Post;
395
+
use Laravel\Scout\Searchable;
396
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
397
+
use SocialDept\AtpSignals\Events\SignalEvent;
398
+
use SocialDept\AtpSignals\Signals\Signal;
399
+
400
+
class SearchIndexerSignal extends Signal
401
+
{
402
+
public function eventTypes(): array
403
+
{
404
+
return ['commit'];
405
+
}
406
+
407
+
public function collections(): ?array
408
+
{
409
+
return ['app.bsky.feed.post'];
410
+
}
411
+
412
+
public function shouldQueue(): bool
413
+
{
414
+
return true;
415
+
}
416
+
417
+
public function queue(): string
418
+
{
419
+
return 'indexing';
420
+
}
421
+
422
+
public function handle(SignalEvent $event): void
423
+
{
424
+
$operation = $event->getOperation();
425
+
426
+
match ($operation) {
427
+
SignalCommitOperation::Create,
428
+
SignalCommitOperation::Update => $this->indexPost($event),
429
+
SignalCommitOperation::Delete => $this->deletePost($event),
430
+
};
431
+
}
432
+
433
+
private function indexPost(SignalEvent $event): void
434
+
{
435
+
$record = $event->getRecord();
436
+
437
+
$post = Post::updateOrCreate(
438
+
[
439
+
'did' => $event->did,
440
+
'rkey' => $event->commit->rkey,
441
+
],
442
+
[
443
+
'text' => $record->text ?? '',
444
+
'created_at' => $record->createdAt ?? now(),
445
+
'indexed_at' => now(),
446
+
]
447
+
);
448
+
449
+
// Scout automatically indexes
450
+
$post->searchable();
451
+
}
452
+
453
+
private function deletePost(SignalEvent $event): void
454
+
{
455
+
$post = Post::where('did', $event->did)
456
+
->where('rkey', $event->commit->rkey)
457
+
->first();
458
+
459
+
if ($post) {
460
+
$post->unsearchable();
461
+
$post->delete();
462
+
}
463
+
}
464
+
}
465
+
```
466
+
467
+
**Use case:** Full-text search across all Bluesky posts.
468
+
469
+
## Trend Detection
470
+
471
+
Identify trending topics and hashtags.
472
+
473
+
```php
474
+
<?php
475
+
476
+
namespace App\Signals;
477
+
478
+
use App\Models\TrendingTopic;
479
+
use Illuminate\Support\Facades\Cache;
480
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
481
+
use SocialDept\AtpSignals\Events\SignalEvent;
482
+
use SocialDept\AtpSignals\Signals\Signal;
483
+
484
+
class TrendDetectionSignal extends Signal
485
+
{
486
+
public function eventTypes(): array
487
+
{
488
+
return ['commit'];
489
+
}
490
+
491
+
public function collections(): ?array
492
+
{
493
+
return ['app.bsky.feed.post'];
494
+
}
495
+
496
+
public function operations(): ?array
497
+
{
498
+
return [SignalCommitOperation::Create];
499
+
}
500
+
501
+
public function shouldQueue(): bool
502
+
{
503
+
return true;
504
+
}
505
+
506
+
public function handle(SignalEvent $event): void
507
+
{
508
+
$record = $event->getRecord();
509
+
510
+
if (!isset($record->text)) {
511
+
return;
512
+
}
513
+
514
+
// Extract hashtags
515
+
preg_match_all('/#(\w+)/', $record->text, $matches);
516
+
517
+
foreach ($matches[1] as $hashtag) {
518
+
$this->incrementHashtag($hashtag);
519
+
}
520
+
}
521
+
522
+
private function incrementHashtag(string $hashtag): void
523
+
{
524
+
$key = "trending:hashtag:{$hashtag}";
525
+
526
+
// Increment counter (expires after 1 hour)
527
+
$count = Cache::increment($key, 1);
528
+
529
+
if (!Cache::has($key)) {
530
+
Cache::put($key, 1, now()->addHour());
531
+
}
532
+
533
+
// Update trending topics if threshold reached
534
+
if ($count > 100) {
535
+
TrendingTopic::updateOrCreate(
536
+
['hashtag' => $hashtag],
537
+
['count' => $count, 'updated_at' => now()]
538
+
);
539
+
}
540
+
}
541
+
}
542
+
```
543
+
544
+
**Use case:** Identify trending hashtags and topics in real-time.
545
+
546
+
## Custom AppView
547
+
548
+
Index custom collections for your AppView.
549
+
550
+
```php
551
+
<?php
552
+
553
+
namespace App\Signals;
554
+
555
+
use App\Models\Publication;
556
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
557
+
use SocialDept\AtpSignals\Events\SignalEvent;
558
+
use SocialDept\AtpSignals\Signals\Signal;
559
+
560
+
class PublicationIndexerSignal extends Signal
561
+
{
562
+
public function eventTypes(): array
563
+
{
564
+
return ['commit'];
565
+
}
566
+
567
+
public function collections(): ?array
568
+
{
569
+
return [
570
+
'app.offprint.beta.publication',
571
+
'app.offprint.beta.post',
572
+
];
573
+
}
574
+
575
+
public function shouldQueue(): bool
576
+
{
577
+
return true;
578
+
}
579
+
580
+
public function handle(SignalEvent $event): void
581
+
{
582
+
$collection = $event->getCollection();
583
+
$operation = $event->getOperation();
584
+
585
+
if ($collection === 'app.offprint.beta.publication') {
586
+
$this->handlePublication($event, $operation);
587
+
} else {
588
+
$this->handlePost($event, $operation);
589
+
}
590
+
}
591
+
592
+
private function handlePublication(SignalEvent $event, SignalCommitOperation $operation): void
593
+
{
594
+
if ($operation === SignalCommitOperation::Delete) {
595
+
Publication::where('did', $event->did)
596
+
->where('rkey', $event->commit->rkey)
597
+
->delete();
598
+
return;
599
+
}
600
+
601
+
$record = $event->getRecord();
602
+
603
+
Publication::updateOrCreate(
604
+
[
605
+
'did' => $event->did,
606
+
'rkey' => $event->commit->rkey,
607
+
],
608
+
[
609
+
'title' => $record->title ?? '',
610
+
'description' => $record->description ?? null,
611
+
'created_at' => $record->createdAt ?? now(),
612
+
]
613
+
);
614
+
}
615
+
616
+
private function handlePost(SignalEvent $event, SignalCommitOperation $operation): void
617
+
{
618
+
// Handle custom post records
619
+
}
620
+
}
621
+
```
622
+
623
+
**Use case:** Build AT Protocol AppViews with custom collections.
624
+
625
+
## Rate-Limited API Integration
626
+
627
+
Integrate with external APIs respecting rate limits.
628
+
629
+
```php
630
+
<?php
631
+
632
+
namespace App\Signals;
633
+
634
+
use App\Services\ExternalAPIService;
635
+
use Illuminate\Support\Facades\RateLimiter;
636
+
use SocialDept\AtpSignals\Events\SignalEvent;
637
+
use SocialDept\AtpSignals\Signals\Signal;
638
+
639
+
class APIIntegrationSignal extends Signal
640
+
{
641
+
public function __construct(
642
+
private ExternalAPIService $api
643
+
) {}
644
+
645
+
public function eventTypes(): array
646
+
{
647
+
return ['commit'];
648
+
}
649
+
650
+
public function collections(): ?array
651
+
{
652
+
return ['app.bsky.feed.post'];
653
+
}
654
+
655
+
public function shouldQueue(): bool
656
+
{
657
+
return true;
658
+
}
659
+
660
+
public function handle(SignalEvent $event): void
661
+
{
662
+
$record = $event->getRecord();
663
+
664
+
// Rate limit: 100 calls per minute
665
+
$executed = RateLimiter::attempt(
666
+
'external-api',
667
+
$perMinute = 100,
668
+
function () use ($event, $record) {
669
+
$this->api->sendPost([
670
+
'author' => $event->did,
671
+
'text' => $record->text ?? '',
672
+
'timestamp' => $event->getTimestamp(),
673
+
]);
674
+
}
675
+
);
676
+
677
+
if (!$executed) {
678
+
// Re-queue for later
679
+
dispatch(fn() => $this->handle($event))
680
+
->delay(now()->addMinutes(1));
681
+
}
682
+
}
683
+
}
684
+
```
685
+
686
+
**Use case:** Mirror content to external platforms with rate limiting.
687
+
688
+
## Multi-Collection Analytics
689
+
690
+
Track engagement across multiple collection types.
691
+
692
+
```php
693
+
<?php
694
+
695
+
namespace App\Signals;
696
+
697
+
use App\Models\UserMetrics;
698
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
699
+
use SocialDept\AtpSignals\Events\SignalEvent;
700
+
use SocialDept\AtpSignals\Signals\Signal;
701
+
702
+
class UserMetricsSignal extends Signal
703
+
{
704
+
public function eventTypes(): array
705
+
{
706
+
return ['commit'];
707
+
}
708
+
709
+
public function collections(): ?array
710
+
{
711
+
return ['app.bsky.feed.*', 'app.bsky.graph.*'];
712
+
}
713
+
714
+
public function operations(): ?array
715
+
{
716
+
return [SignalCommitOperation::Create];
717
+
}
718
+
719
+
public function shouldQueue(): bool
720
+
{
721
+
return true;
722
+
}
723
+
724
+
public function handle(SignalEvent $event): void
725
+
{
726
+
$collection = $event->getCollection();
727
+
728
+
$metrics = UserMetrics::firstOrCreate(
729
+
['did' => $event->did],
730
+
['total_posts' => 0, 'total_likes' => 0, 'total_follows' => 0]
731
+
);
732
+
733
+
match ($collection) {
734
+
'app.bsky.feed.post' => $metrics->increment('total_posts'),
735
+
'app.bsky.feed.like' => $metrics->increment('total_likes'),
736
+
'app.bsky.graph.follow' => $metrics->increment('total_follows'),
737
+
default => null,
738
+
};
739
+
740
+
$metrics->touch('last_activity_at');
741
+
}
742
+
}
743
+
```
744
+
745
+
**Use case:** User activity metrics and leaderboards.
746
+
747
+
## Performance Tips
748
+
749
+
### Batch Database Operations
750
+
751
+
```php
752
+
public function handle(SignalEvent $event): void
753
+
{
754
+
// Bad - individual inserts
755
+
Post::create([...]);
756
+
757
+
// Good - batch inserts
758
+
$posts = Cache::get('pending_posts', []);
759
+
$posts[] = [...];
760
+
761
+
if (count($posts) >= 100) {
762
+
Post::insert($posts);
763
+
Cache::forget('pending_posts');
764
+
} else {
765
+
Cache::put('pending_posts', $posts, now()->addMinutes(5));
766
+
}
767
+
}
768
+
```
769
+
770
+
### Use Queues for Heavy Operations
771
+
772
+
```php
773
+
public function shouldQueue(): bool
774
+
{
775
+
// Queue if operation takes > 100ms
776
+
return true;
777
+
}
778
+
```
779
+
780
+
### Add Indexes for Filtering
781
+
782
+
```php
783
+
// Migration for fast lookups
784
+
Schema::table('posts', function (Blueprint $table) {
785
+
$table->index(['did', 'rkey']);
786
+
$table->index('created_at');
787
+
});
788
+
```
789
+
790
+
## Next Steps
791
+
792
+
- **[Review signal architecture โ](signals.md)** - Understand Signal structure
793
+
- **[Learn about filtering โ](filtering.md)** - Master event filtering
794
+
- **[Explore queue integration โ](queues.md)** - Build high-performance Signals
795
+
- **[Configure your setup โ](configuration.md)** - Optimize configuration
+704
docs/filtering.md
+704
docs/filtering.md
···
1
+
# Filtering Events
2
+
3
+
Filtering is how you control which events your Signals process. Signal provides multiple layers of filtering to help you target exactly the events you care about.
4
+
5
+
## Why Filter?
6
+
7
+
The AT Protocol generates millions of events per hour. Without filtering:
8
+
9
+
- Your Signals would process every event (slow and expensive)
10
+
- Your database would fill with irrelevant data
11
+
- Your queues would be overwhelmed
12
+
- Your costs would skyrocket
13
+
14
+
Filtering lets you focus on what matters.
15
+
16
+
## Filter Layers
17
+
18
+
Signal provides four filtering layers, applied in order:
19
+
20
+
1. **Event Type Filtering** - Which kind of events (commit, identity, account)
21
+
2. **Collection Filtering** - Which AT Protocol collections
22
+
3. **Operation Filtering** - Which operations (create, update, delete)
23
+
4. **DID Filtering** - Which users
24
+
5. **Custom Filtering** - Your own logic
25
+
26
+
## Event Type Filtering
27
+
28
+
The most basic filter - required for all Signals.
29
+
30
+
### Available Event Types
31
+
32
+
```php
33
+
use SocialDept\AtpSignals\Enums\SignalEventType;
34
+
35
+
public function eventTypes(): array
36
+
{
37
+
return [SignalEventType::Commit]; // Most common
38
+
// Or: return ['commit'];
39
+
}
40
+
```
41
+
42
+
**Three event types:**
43
+
44
+
| Type | Description | Use Cases |
45
+
|------------|--------------------|----------------------------------------|
46
+
| `commit` | Repository changes | Posts, likes, follows, profile updates |
47
+
| `identity` | Handle changes | Username updates, account migrations |
48
+
| `account` | Account status | Deactivation, suspension |
49
+
50
+
### Multiple Event Types
51
+
52
+
Listen to multiple types in one Signal:
53
+
54
+
```php
55
+
public function eventTypes(): array
56
+
{
57
+
return [
58
+
SignalEventType::Commit,
59
+
SignalEventType::Identity,
60
+
];
61
+
}
62
+
```
63
+
64
+
Then check the type in your handler:
65
+
66
+
```php
67
+
public function handle(SignalEvent $event): void
68
+
{
69
+
if ($event->isCommit()) {
70
+
$this->handleCommit($event);
71
+
}
72
+
73
+
if ($event->isIdentity()) {
74
+
$this->handleIdentity($event);
75
+
}
76
+
}
77
+
```
78
+
79
+
## Collection Filtering
80
+
81
+
Collections represent different types of data in the AT Protocol.
82
+
83
+
### Basic Collection Filter
84
+
85
+
```php
86
+
public function collections(): ?array
87
+
{
88
+
return ['app.bsky.feed.post'];
89
+
}
90
+
```
91
+
92
+
### No Filter (All Collections)
93
+
94
+
Return `null` to process all collections:
95
+
96
+
```php
97
+
public function collections(): ?array
98
+
{
99
+
return null; // Handle everything
100
+
}
101
+
```
102
+
103
+
### Multiple Collections
104
+
105
+
```php
106
+
public function collections(): ?array
107
+
{
108
+
return [
109
+
'app.bsky.feed.post',
110
+
'app.bsky.feed.like',
111
+
'app.bsky.feed.repost',
112
+
];
113
+
}
114
+
```
115
+
116
+
### Wildcard Patterns
117
+
118
+
Use `*` to match multiple collections:
119
+
120
+
```php
121
+
public function collections(): ?array
122
+
{
123
+
return ['app.bsky.feed.*'];
124
+
}
125
+
```
126
+
127
+
**This matches:**
128
+
- `app.bsky.feed.post`
129
+
- `app.bsky.feed.like`
130
+
- `app.bsky.feed.repost`
131
+
- Any other `app.bsky.feed.*` collection
132
+
133
+
### Common Collection Patterns
134
+
135
+
| Pattern | Matches | Use Case |
136
+
|--------------------|-------------------------|------------------------|
137
+
| `app.bsky.feed.*` | All feed interactions | Posts, likes, reposts |
138
+
| `app.bsky.graph.*` | All social graph | Follows, blocks, mutes |
139
+
| `app.bsky.actor.*` | All profile changes | Profile updates |
140
+
| `app.bsky.*` | All Bluesky collections | Everything Bluesky |
141
+
| `app.yourapp.*` | Your custom collections | Custom AppView |
142
+
143
+
### Mixing Exact and Wildcards
144
+
145
+
Combine exact matches with wildcards:
146
+
147
+
```php
148
+
public function collections(): ?array
149
+
{
150
+
return [
151
+
'app.bsky.feed.post', // Exact: only posts
152
+
'app.bsky.graph.*', // Wildcard: all graph events
153
+
'app.myapp.custom.record', // Exact: custom collection
154
+
];
155
+
}
156
+
```
157
+
158
+
### Standard Bluesky Collections
159
+
160
+
**Feed Collections** (`app.bsky.feed.*`):
161
+
- `app.bsky.feed.post` - Posts (text, images, videos)
162
+
- `app.bsky.feed.like` - Likes on posts
163
+
- `app.bsky.feed.repost` - Reposts (shares)
164
+
- `app.bsky.feed.threadgate` - Thread reply controls
165
+
- `app.bsky.feed.generator` - Custom feed generators
166
+
167
+
**Graph Collections** (`app.bsky.graph.*`):
168
+
- `app.bsky.graph.follow` - Follow relationships
169
+
- `app.bsky.graph.block` - Blocked users
170
+
- `app.bsky.graph.list` - User lists
171
+
- `app.bsky.graph.listitem` - List memberships
172
+
- `app.bsky.graph.listblock` - List blocks
173
+
174
+
**Actor Collections** (`app.bsky.actor.*`):
175
+
- `app.bsky.actor.profile` - User profiles
176
+
177
+
**Labeler Collections** (`app.bsky.labeler.*`):
178
+
- `app.bsky.labeler.service` - Labeler services
179
+
180
+
### Important: Jetstream vs Firehose Filtering
181
+
182
+
**Jetstream Mode:**
183
+
- Exact collection names are sent to server for filtering (efficient)
184
+
- Wildcards work client-side only (you receive more data)
185
+
186
+
**Firehose Mode:**
187
+
- All filtering is client-side
188
+
- Wildcards work normally (no difference in data received)
189
+
190
+
[Learn more about modes โ](modes.md)
191
+
192
+
### Custom Collections (AppViews)
193
+
194
+
Filter your own custom collections:
195
+
196
+
```php
197
+
public function collections(): ?array
198
+
{
199
+
return [
200
+
'app.offprint.beta.publication',
201
+
'app.offprint.beta.post',
202
+
];
203
+
}
204
+
```
205
+
206
+
## Operation Filtering
207
+
208
+
Filter by operation type (only applies to commit events).
209
+
210
+
### Available Operations
211
+
212
+
```php
213
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
214
+
215
+
public function operations(): ?array
216
+
{
217
+
return [SignalCommitOperation::Create];
218
+
// Or: return ['create'];
219
+
}
220
+
```
221
+
222
+
**Three operation types:**
223
+
224
+
| Operation | Description | Example |
225
+
|-----------|------------------|-----------------|
226
+
| `create` | New records | Creating a post |
227
+
| `update` | Modified records | Editing a post |
228
+
| `delete` | Removed records | Deleting a post |
229
+
230
+
### No Filter (All Operations)
231
+
232
+
```php
233
+
public function operations(): ?array
234
+
{
235
+
return null; // Handle all operations
236
+
}
237
+
```
238
+
239
+
### Multiple Operations
240
+
241
+
```php
242
+
public function operations(): ?array
243
+
{
244
+
return [
245
+
SignalCommitOperation::Create,
246
+
SignalCommitOperation::Update,
247
+
];
248
+
// Or: return ['create', 'update'];
249
+
}
250
+
```
251
+
252
+
### Common Patterns
253
+
254
+
**Only track new content:**
255
+
```php
256
+
public function operations(): ?array
257
+
{
258
+
return [SignalCommitOperation::Create];
259
+
}
260
+
```
261
+
262
+
**Track modifications:**
263
+
```php
264
+
public function operations(): ?array
265
+
{
266
+
return [SignalCommitOperation::Update];
267
+
}
268
+
```
269
+
270
+
**Cleanup on deletions:**
271
+
```php
272
+
public function operations(): ?array
273
+
{
274
+
return [SignalCommitOperation::Delete];
275
+
}
276
+
```
277
+
278
+
### Checking Operations in Handler
279
+
280
+
You can also check operation type in your handler:
281
+
282
+
```php
283
+
public function handle(SignalEvent $event): void
284
+
{
285
+
$operation = $event->getOperation();
286
+
287
+
// Using enum
288
+
if ($operation === SignalCommitOperation::Create) {
289
+
$this->createRecord($event);
290
+
}
291
+
292
+
// Using commit helper
293
+
if ($event->commit->isCreate()) {
294
+
$this->createRecord($event);
295
+
}
296
+
297
+
if ($event->commit->isUpdate()) {
298
+
$this->updateRecord($event);
299
+
}
300
+
301
+
if ($event->commit->isDelete()) {
302
+
$this->deleteRecord($event);
303
+
}
304
+
}
305
+
```
306
+
307
+
## DID Filtering
308
+
309
+
Filter events by specific users (DIDs).
310
+
311
+
### Basic DID Filter
312
+
313
+
```php
314
+
public function dids(): ?array
315
+
{
316
+
return [
317
+
'did:plc:z72i7hdynmk6r22z27h6tvur',
318
+
];
319
+
}
320
+
```
321
+
322
+
### No Filter (All Users)
323
+
324
+
```php
325
+
public function dids(): ?array
326
+
{
327
+
return null; // Handle all users
328
+
}
329
+
```
330
+
331
+
### Multiple DIDs
332
+
333
+
```php
334
+
public function dids(): ?array
335
+
{
336
+
return [
337
+
'did:plc:z72i7hdynmk6r22z27h6tvur',
338
+
'did:plc:ragtjsm2j2vknwkz3zp4oxrd',
339
+
];
340
+
}
341
+
```
342
+
343
+
### Use Cases
344
+
345
+
**Monitor specific accounts:**
346
+
```php
347
+
// Track posts from specific content creators
348
+
public function collections(): ?array
349
+
{
350
+
return ['app.bsky.feed.post'];
351
+
}
352
+
353
+
public function dids(): ?array
354
+
{
355
+
return [
356
+
'did:plc:z72i7hdynmk6r22z27h6tvur', // Creator 1
357
+
'did:plc:ragtjsm2j2vknwkz3zp4oxrd', // Creator 2
358
+
];
359
+
}
360
+
```
361
+
362
+
**Dynamic DID filtering:**
363
+
```php
364
+
use App\Models\MonitoredAccount;
365
+
366
+
public function dids(): ?array
367
+
{
368
+
return MonitoredAccount::pluck('did')->toArray();
369
+
}
370
+
```
371
+
372
+
## Custom Filtering
373
+
374
+
Implement complex filtering logic with `shouldHandle()`.
375
+
376
+
### Basic Custom Filter
377
+
378
+
```php
379
+
public function shouldHandle(SignalEvent $event): bool
380
+
{
381
+
// Only handle posts with images
382
+
if ($event->isCommit() && $event->getCollection() === 'app.bsky.feed.post') {
383
+
$record = $event->getRecord();
384
+
return isset($record->embed);
385
+
}
386
+
387
+
return true;
388
+
}
389
+
```
390
+
391
+
### Advanced Examples
392
+
393
+
**Filter by text content:**
394
+
```php
395
+
public function shouldHandle(SignalEvent $event): bool
396
+
{
397
+
$record = $event->getRecord();
398
+
399
+
if (!isset($record->text)) {
400
+
return false;
401
+
}
402
+
403
+
// Only handle posts mentioning "Laravel"
404
+
return str_contains($record->text, 'Laravel');
405
+
}
406
+
```
407
+
408
+
**Filter by language:**
409
+
```php
410
+
public function shouldHandle(SignalEvent $event): bool
411
+
{
412
+
$record = $event->getRecord();
413
+
414
+
// Only handle English posts
415
+
return ($record->langs[0] ?? null) === 'en';
416
+
}
417
+
```
418
+
419
+
**Filter by engagement:**
420
+
```php
421
+
use App\Services\EngagementCalculator;
422
+
423
+
public function shouldHandle(SignalEvent $event): bool
424
+
{
425
+
$engagement = EngagementCalculator::calculate($event);
426
+
427
+
// Only handle high-engagement content
428
+
return $engagement > 100;
429
+
}
430
+
```
431
+
432
+
**Time-based filtering:**
433
+
```php
434
+
public function shouldHandle(SignalEvent $event): bool
435
+
{
436
+
$timestamp = $event->getTimestamp();
437
+
438
+
// Only handle events from the last hour
439
+
return $timestamp->isAfter(now()->subHour());
440
+
}
441
+
```
442
+
443
+
## Combining Filters
444
+
445
+
Stack multiple filter layers for precise targeting:
446
+
447
+
```php
448
+
class HighEngagementPostsSignal extends Signal
449
+
{
450
+
// Layer 1: Event type
451
+
public function eventTypes(): array
452
+
{
453
+
return ['commit'];
454
+
}
455
+
456
+
// Layer 2: Collection
457
+
public function collections(): ?array
458
+
{
459
+
return ['app.bsky.feed.post'];
460
+
}
461
+
462
+
// Layer 3: Operation
463
+
public function operations(): ?array
464
+
{
465
+
return [SignalCommitOperation::Create];
466
+
}
467
+
468
+
// Layer 4: Custom logic
469
+
public function shouldHandle(SignalEvent $event): bool
470
+
{
471
+
$record = $event->getRecord();
472
+
473
+
// Must have text
474
+
if (!isset($record->text)) {
475
+
return false;
476
+
}
477
+
478
+
// Must be longer than 100 characters
479
+
if (strlen($record->text) < 100) {
480
+
return false;
481
+
}
482
+
483
+
// Must have media
484
+
if (!isset($record->embed)) {
485
+
return false;
486
+
}
487
+
488
+
return true;
489
+
}
490
+
491
+
public function handle(SignalEvent $event): void
492
+
{
493
+
// Only high-quality posts make it here
494
+
}
495
+
}
496
+
```
497
+
498
+
## Performance Considerations
499
+
500
+
### Server-Side vs Client-Side Filtering
501
+
502
+
**Jetstream Mode (Server-Side):**
503
+
- Collections filter applied on server (efficient)
504
+
- Only receives matching events
505
+
- Lower bandwidth usage
506
+
507
+
```php
508
+
// These collections are sent to Jetstream server
509
+
public function collections(): ?array
510
+
{
511
+
return ['app.bsky.feed.post', 'app.bsky.feed.like'];
512
+
}
513
+
```
514
+
515
+
**Firehose Mode (Client-Side):**
516
+
- All filtering happens in your application
517
+
- Receives all events (higher bandwidth)
518
+
- More control but higher cost
519
+
520
+
[Learn more about modes โ](modes.md)
521
+
522
+
### Filter Early
523
+
524
+
Apply the most restrictive filters first:
525
+
526
+
```php
527
+
// Good - filters early
528
+
public function eventTypes(): array
529
+
{
530
+
return ['commit']; // Narrows to commits only
531
+
}
532
+
533
+
public function collections(): ?array
534
+
{
535
+
return ['app.bsky.feed.post']; // Further narrows to posts
536
+
}
537
+
538
+
// Less ideal - too broad
539
+
public function eventTypes(): array
540
+
{
541
+
return ['commit', 'identity', 'account']; // Too many events
542
+
}
543
+
544
+
public function shouldHandle(SignalEvent $event): bool
545
+
{
546
+
// Filtering everything in custom logic (expensive)
547
+
return $event->isCommit() && $event->getCollection() === 'app.bsky.feed.post';
548
+
}
549
+
```
550
+
551
+
### Avoid Heavy Logic in shouldHandle()
552
+
553
+
Keep custom filtering lightweight:
554
+
555
+
```php
556
+
// Good - lightweight checks
557
+
public function shouldHandle(SignalEvent $event): bool
558
+
{
559
+
$record = $event->getRecord();
560
+
return isset($record->text) && strlen($record->text) > 10;
561
+
}
562
+
563
+
// Less ideal - heavy database queries
564
+
public function shouldHandle(SignalEvent $event): bool
565
+
{
566
+
// Database query on every event (slow!)
567
+
return User::where('did', $event->did)->exists();
568
+
}
569
+
```
570
+
571
+
If you need heavy logic, use queues:
572
+
573
+
```php
574
+
public function shouldQueue(): bool
575
+
{
576
+
return true; // Move heavy work to queue
577
+
}
578
+
```
579
+
580
+
## Common Filter Patterns
581
+
582
+
### Track All Activity from Specific Users
583
+
584
+
```php
585
+
public function eventTypes(): array
586
+
{
587
+
return ['commit'];
588
+
}
589
+
590
+
public function dids(): ?array
591
+
{
592
+
return [
593
+
'did:plc:z72i7hdynmk6r22z27h6tvur',
594
+
];
595
+
}
596
+
```
597
+
598
+
### Monitor All Feed Activity
599
+
600
+
```php
601
+
public function eventTypes(): array
602
+
{
603
+
return ['commit'];
604
+
}
605
+
606
+
public function collections(): ?array
607
+
{
608
+
return ['app.bsky.feed.*'];
609
+
}
610
+
```
611
+
612
+
### Track Only New Posts
613
+
614
+
```php
615
+
public function eventTypes(): array
616
+
{
617
+
return ['commit'];
618
+
}
619
+
620
+
public function collections(): ?array
621
+
{
622
+
return ['app.bsky.feed.post'];
623
+
}
624
+
625
+
public function operations(): ?array
626
+
{
627
+
return [SignalCommitOperation::Create];
628
+
}
629
+
```
630
+
631
+
### Monitor Content Deletions
632
+
633
+
```php
634
+
public function eventTypes(): array
635
+
{
636
+
return ['commit'];
637
+
}
638
+
639
+
public function operations(): ?array
640
+
{
641
+
return [SignalCommitOperation::Delete];
642
+
}
643
+
```
644
+
645
+
### Track Profile Changes
646
+
647
+
```php
648
+
public function eventTypes(): array
649
+
{
650
+
return ['commit'];
651
+
}
652
+
653
+
public function collections(): ?array
654
+
{
655
+
return ['app.bsky.actor.profile'];
656
+
}
657
+
```
658
+
659
+
### Monitor Handle Changes
660
+
661
+
```php
662
+
public function eventTypes(): array
663
+
{
664
+
return ['identity'];
665
+
}
666
+
```
667
+
668
+
## Debugging Filters
669
+
670
+
### Log What's Being Filtered
671
+
672
+
```php
673
+
public function shouldHandle(SignalEvent $event): bool
674
+
{
675
+
$shouldHandle = $this->myCustomLogic($event);
676
+
677
+
if (!$shouldHandle) {
678
+
Log::debug('Event filtered out', [
679
+
'signal' => static::class,
680
+
'did' => $event->did,
681
+
'collection' => $event->getCollection(),
682
+
'reason' => 'Failed custom logic',
683
+
]);
684
+
}
685
+
686
+
return $shouldHandle;
687
+
}
688
+
```
689
+
690
+
### Test Your Filters
691
+
692
+
```bash
693
+
php artisan signal:test YourSignal
694
+
```
695
+
696
+
This runs your Signal with sample data to verify filtering works correctly.
697
+
698
+
[Learn more about testing โ](testing.md)
699
+
700
+
## Next Steps
701
+
702
+
- **[Understand Jetstream vs Firehose โ](modes.md)** - Choose the right mode for your filters
703
+
- **[Learn about queue integration โ](queues.md)** - Handle high-volume filtered events
704
+
- **[See real-world examples โ](examples.md)** - Learn from production filter patterns
+188
docs/installation.md
+188
docs/installation.md
···
1
+
# Installation
2
+
3
+
Signal is designed to be installed quickly and easily in any Laravel 11+ application.
4
+
5
+
## Requirements
6
+
7
+
Before installing Signal, ensure your environment meets these requirements:
8
+
9
+
- **PHP 8.2 or higher**
10
+
- **Laravel 11.0 or higher**
11
+
- **WebSocket support** (enabled by default in most environments)
12
+
- **Database** (for cursor storage)
13
+
14
+
## Composer Installation
15
+
16
+
Install Signal via Composer:
17
+
18
+
```bash
19
+
composer require socialdept/atp-signals
20
+
```
21
+
22
+
## Quick Setup
23
+
24
+
Run the installation command to set up everything automatically:
25
+
26
+
```bash
27
+
php artisan signal:install
28
+
```
29
+
30
+
This interactive command will:
31
+
32
+
1. Publish the configuration file to `config/signal.php`
33
+
2. Publish database migrations for cursor storage
34
+
3. Ask if you'd like to run migrations immediately
35
+
4. Display next steps and helpful information
36
+
37
+
### What Gets Created
38
+
39
+
After installation, you'll have:
40
+
41
+
- **Configuration file**: `config/signal.php` - All Signal settings
42
+
- **Migration**: `database/migrations/2024_01_01_000000_create_signal_cursors_table.php` - Cursor storage
43
+
- **Signal directory**: `app/Signals/` - Where your Signals live (created when you make your first Signal)
44
+
45
+
## Manual Installation
46
+
47
+
If you prefer more control, you can install manually:
48
+
49
+
### 1. Publish Configuration
50
+
51
+
```bash
52
+
php artisan vendor:publish --tag=signal-config
53
+
```
54
+
55
+
This creates `config/signal.php` with all available options.
56
+
57
+
### 2. Publish Migrations
58
+
59
+
```bash
60
+
php artisan vendor:publish --tag=signal-migrations
61
+
```
62
+
63
+
This creates the cursor storage migration in `database/migrations/`.
64
+
65
+
### 3. Run Migrations
66
+
67
+
```bash
68
+
php artisan migrate
69
+
```
70
+
71
+
This creates the `signal_cursors` table for resuming from last position after disconnections.
72
+
73
+
## Environment Configuration
74
+
75
+
Add Signal configuration to your `.env` file:
76
+
77
+
```env
78
+
# Consumer Mode (jetstream or firehose)
79
+
SIGNAL_MODE=jetstream
80
+
81
+
# Jetstream URL (if using jetstream mode)
82
+
SIGNAL_JETSTREAM_URL=wss://jetstream2.us-east.bsky.network
83
+
84
+
# Firehose Host (if using firehose mode)
85
+
SIGNAL_FIREHOSE_HOST=bsky.network
86
+
87
+
# Optional: Cursor Storage Driver (database, redis, or file)
88
+
SIGNAL_CURSOR_STORAGE=database
89
+
90
+
# Optional: Queue Configuration
91
+
SIGNAL_QUEUE_CONNECTION=redis
92
+
SIGNAL_QUEUE=signal
93
+
```
94
+
95
+
## Choosing Your Mode
96
+
97
+
Signal supports two modes for consuming events. Choose based on your use case:
98
+
99
+
### Jetstream Mode (Recommended)
100
+
101
+
Best for most applications:
102
+
103
+
```env
104
+
SIGNAL_MODE=jetstream
105
+
SIGNAL_JETSTREAM_URL=wss://jetstream2.us-east.bsky.network
106
+
```
107
+
108
+
**Advantages:**
109
+
- Simplified JSON events (easy to work with)
110
+
- Server-side collection filtering (efficient)
111
+
- Lower bandwidth and processing overhead
112
+
113
+
### Firehose Mode
114
+
115
+
Best for comprehensive indexing and raw data access:
116
+
117
+
```env
118
+
SIGNAL_MODE=firehose
119
+
SIGNAL_FIREHOSE_HOST=bsky.network
120
+
```
121
+
122
+
**Advantages:**
123
+
- Access to raw CBOR/CAR data
124
+
- Full AT Protocol event stream
125
+
- Complete control over event processing
126
+
127
+
**Trade-offs:**
128
+
- Client-side filtering only (higher bandwidth)
129
+
- More processing overhead
130
+
131
+
[Learn more about choosing the right mode โ](modes.md)
132
+
133
+
## Verify Installation
134
+
135
+
Check that Signal is installed correctly:
136
+
137
+
```bash
138
+
php artisan signal:list
139
+
```
140
+
141
+
This should display available Signals (initially none until you create them).
142
+
143
+
## Next Steps
144
+
145
+
Now that Signal is installed, you're ready to start building:
146
+
147
+
1. **[Create your first Signal โ](quickstart.md)**
148
+
2. **[Learn about Signal architecture โ](signals.md)**
149
+
3. **[Understand filtering options โ](filtering.md)**
150
+
151
+
## Troubleshooting
152
+
153
+
### Migration Already Exists
154
+
155
+
If you see "migration already exists" when running `signal:install`, you've likely already installed Signal. You can safely skip this step.
156
+
157
+
### WebSocket Connection Issues
158
+
159
+
If you experience WebSocket connection issues:
160
+
161
+
1. Verify your firewall allows WebSocket connections
162
+
2. Check that your hosting environment supports WebSockets
163
+
3. Try switching Jetstream endpoints (US East vs US West)
164
+
165
+
### Permission Errors
166
+
167
+
If you encounter permission errors with cursor storage:
168
+
169
+
- **Database mode**: Ensure database connection is configured correctly
170
+
- **Redis mode**: Verify Redis connection is available
171
+
- **File mode**: Check that Laravel has write permissions to `storage/app/signal/`
172
+
173
+
## Uninstallation
174
+
175
+
To remove Signal from your application:
176
+
177
+
```bash
178
+
# Remove the package
179
+
composer remove socialdept/atp-signals
180
+
181
+
# Optionally, rollback migrations
182
+
php artisan migrate:rollback
183
+
```
184
+
185
+
You can also manually delete:
186
+
- `config/signal.php`
187
+
- `app/Signals/` directory
188
+
- Signal-related migrations
+493
docs/modes.md
+493
docs/modes.md
···
1
+
# Jetstream vs Firehose
2
+
3
+
Signal supports two modes for consuming AT Protocol events. Understanding the differences is crucial for building efficient, scalable applications.
4
+
5
+
## Quick Comparison
6
+
7
+
| Feature | Jetstream | Firehose |
8
+
|------------------|-------------------|------------------------|
9
+
| **Event Format** | Simplified JSON | Raw CBOR/CAR |
10
+
| **Filtering** | Server-side | Client-side |
11
+
| **Bandwidth** | Lower | Higher |
12
+
| **Processing** | Lighter | Heavier |
13
+
| **Best For** | Most applications | Comprehensive indexing |
14
+
15
+
## Jetstream Mode
16
+
17
+
Jetstream is a **simplified, JSON-based event stream** built on top of the AT Protocol Firehose.
18
+
19
+
### When to Use Jetstream
20
+
21
+
Choose Jetstream if you're:
22
+
23
+
- Building production applications where efficiency matters
24
+
- Concerned about bandwidth and server costs
25
+
- Processing high volumes of events
26
+
- Want server-side filtering for reduced bandwidth
27
+
28
+
### Configuration
29
+
30
+
Set Jetstream as your mode in `.env`:
31
+
32
+
```env
33
+
SIGNAL_MODE=jetstream
34
+
SIGNAL_JETSTREAM_URL=wss://jetstream2.us-east.bsky.network
35
+
```
36
+
37
+
### Available Endpoints
38
+
39
+
Jetstream has multiple regional endpoints:
40
+
41
+
**US East (Default):**
42
+
```env
43
+
SIGNAL_JETSTREAM_URL=wss://jetstream2.us-east.bsky.network
44
+
```
45
+
46
+
**US West:**
47
+
```env
48
+
SIGNAL_JETSTREAM_URL=wss://jetstream1.us-west.bsky.network
49
+
```
50
+
51
+
Choose the endpoint closest to your server for best performance.
52
+
53
+
### Advantages
54
+
55
+
**1. Simplified JSON Format**
56
+
57
+
Events arrive as clean JSON objects:
58
+
59
+
```json
60
+
{
61
+
"did": "did:plc:z72i7hdynmk6r22z27h6tvur",
62
+
"time_us": 1234567890,
63
+
"kind": "commit",
64
+
"commit": {
65
+
"rev": "abc123",
66
+
"operation": "create",
67
+
"collection": "app.bsky.feed.post",
68
+
"rkey": "3k2yihcrr2c2a",
69
+
"record": {
70
+
"text": "Hello World!",
71
+
"createdAt": "2024-01-15T10:30:00Z"
72
+
}
73
+
}
74
+
}
75
+
```
76
+
77
+
No complex parsing or decoding required.
78
+
79
+
**2. Server-Side Filtering**
80
+
81
+
Your collection filters are sent to Jetstream:
82
+
83
+
```php
84
+
public function collections(): ?array
85
+
{
86
+
return ['app.bsky.feed.post', 'app.bsky.feed.like'];
87
+
}
88
+
```
89
+
90
+
Jetstream only sends matching events, dramatically reducing bandwidth.
91
+
92
+
**3. Lower Bandwidth**
93
+
94
+
Only receive the events you care about:
95
+
96
+
- **Jetstream**: Receive ~1,000 events/sec for specific collections
97
+
- **Firehose**: Receive ~50,000 events/sec for everything
98
+
99
+
**4. Lower Processing Overhead**
100
+
101
+
JSON parsing is faster than CBOR/CAR decoding:
102
+
103
+
- **Jetstream**: Simple JSON deserialization
104
+
- **Firehose**: Complex CBOR/CAR decoding with `revolution/laravel-bluesky`
105
+
106
+
### Limitations
107
+
108
+
**1. Client-Side Wildcards**
109
+
110
+
Wildcard patterns work client-side only:
111
+
112
+
```php
113
+
public function collections(): ?array
114
+
{
115
+
return ['app.bsky.feed.*']; // Still receives all collections
116
+
}
117
+
```
118
+
119
+
The wildcard matching happens in your app, not on the server.
120
+
121
+
### Example Configuration
122
+
123
+
```php
124
+
// config/signal.php
125
+
return [
126
+
'mode' => env('SIGNAL_MODE', 'jetstream'),
127
+
128
+
'jetstream' => [
129
+
'websocket_url' => env(
130
+
'SIGNAL_JETSTREAM_URL',
131
+
'wss://jetstream2.us-east.bsky.network'
132
+
),
133
+
],
134
+
];
135
+
```
136
+
137
+
## Firehose Mode
138
+
139
+
Firehose is the **raw AT Protocol event stream** with comprehensive support for all collections.
140
+
141
+
### When to Use Firehose
142
+
143
+
Choose Firehose if you're:
144
+
145
+
- Building comprehensive indexing systems
146
+
- Developing AT Protocol infrastructure
147
+
- Need access to raw CBOR/CAR data
148
+
- Prefer client-side filtering control
149
+
150
+
### Configuration
151
+
152
+
Set Firehose as your mode in `.env`:
153
+
154
+
```env
155
+
SIGNAL_MODE=firehose
156
+
SIGNAL_FIREHOSE_HOST=bsky.network
157
+
```
158
+
159
+
### Advantages
160
+
161
+
**1. Raw Event Access**
162
+
163
+
Full access to raw AT Protocol data:
164
+
165
+
```php
166
+
public function handle(SignalEvent $event): void
167
+
{
168
+
// Access raw CBOR/CAR data
169
+
$cid = $event->commit->cid;
170
+
$blocks = $event->commit->blocks;
171
+
}
172
+
```
173
+
174
+
**2. Comprehensive Events**
175
+
176
+
Every event from the AT Protocol network arrives:
177
+
178
+
- All collections (standard and custom)
179
+
- All operations (create, update, delete)
180
+
- All metadata and context
181
+
- Complete repository commits
182
+
183
+
**3. Complete Control**
184
+
185
+
Full access to raw AT Protocol data:
186
+
187
+
- CID (Content Identifiers)
188
+
- Block structures
189
+
- CAR file data
190
+
- Complete repository commits
191
+
192
+
### Trade-offs
193
+
194
+
**1. Client-Side Filtering**
195
+
196
+
All filtering happens in your application:
197
+
198
+
```php
199
+
public function collections(): ?array
200
+
{
201
+
return ['app.bsky.feed.post']; // Still receives all events
202
+
}
203
+
```
204
+
205
+
Your app receives everything and filters locally.
206
+
207
+
**2. Higher Bandwidth**
208
+
209
+
Receive the full event stream:
210
+
211
+
- **~50,000+ events per second** during peak times
212
+
- **~10-50 MB/s** of data throughput
213
+
- Requires adequate network capacity
214
+
215
+
**3. More Processing Overhead**
216
+
217
+
Complex CBOR/CAR decoding:
218
+
219
+
```php
220
+
// Signal automatically handles decoding using revolution/laravel-bluesky
221
+
$record = $event->getRecord(); // Decoded from CBOR/CAR
222
+
```
223
+
224
+
Processing is more CPU-intensive than Jetstream's JSON.
225
+
226
+
**4. Requires revolution/laravel-bluesky**
227
+
228
+
Firehose mode depends on the `revolution/laravel-bluesky` package for decoding:
229
+
230
+
```bash
231
+
composer require revolution/bluesky
232
+
```
233
+
234
+
Signal handles this dependency automatically.
235
+
236
+
### Example Configuration
237
+
238
+
```php
239
+
// config/signal.php
240
+
return [
241
+
'mode' => env('SIGNAL_MODE', 'jetstream'),
242
+
243
+
'firehose' => [
244
+
'host' => env('SIGNAL_FIREHOSE_HOST', 'bsky.network'),
245
+
],
246
+
];
247
+
```
248
+
249
+
The WebSocket URL is constructed as:
250
+
```
251
+
wss://{host}/xrpc/com.atproto.sync.subscribeRepos
252
+
```
253
+
254
+
## Choosing the Right Mode
255
+
256
+
### Decision Tree
257
+
258
+
```
259
+
Do you need raw CBOR/CAR access?
260
+
โโ Yes โ Use Firehose
261
+
โโ No
262
+
โ
263
+
Do you want server-side filtering?
264
+
โโ Yes โ Use Jetstream (recommended)
265
+
โโ No โ Use Firehose
266
+
```
267
+
268
+
### Use Case Examples
269
+
270
+
**Social Media Analytics (Jetstream)**
271
+
272
+
```php
273
+
// Efficient monitoring with server-side filtering
274
+
public function collections(): ?array
275
+
{
276
+
return [
277
+
'app.bsky.feed.post',
278
+
'app.bsky.feed.like',
279
+
'app.bsky.graph.follow',
280
+
];
281
+
}
282
+
```
283
+
284
+
**Content Moderation (Jetstream)**
285
+
286
+
```php
287
+
// Standard content monitoring
288
+
public function collections(): ?array
289
+
{
290
+
return ['app.bsky.feed.*'];
291
+
}
292
+
```
293
+
294
+
**Comprehensive Indexer (Firehose)**
295
+
296
+
```php
297
+
// Index everything with raw data access
298
+
public function collections(): ?array
299
+
{
300
+
return null; // All collections
301
+
}
302
+
```
303
+
304
+
## Switching Between Modes
305
+
306
+
You can switch modes without code changes:
307
+
308
+
### Option 1: Environment Variable
309
+
310
+
```env
311
+
# Development - comprehensive testing
312
+
SIGNAL_MODE=firehose
313
+
314
+
# Production - efficient processing
315
+
SIGNAL_MODE=jetstream
316
+
```
317
+
318
+
### Option 2: Runtime Configuration
319
+
320
+
```php
321
+
use SocialDept\AtpSignals\Facades\Signal;
322
+
323
+
// Set mode dynamically
324
+
config(['signal.mode' => 'jetstream']);
325
+
326
+
Signal::start();
327
+
```
328
+
329
+
## Performance Comparison
330
+
331
+
### Bandwidth Usage
332
+
333
+
**Processing 1 hour of posts:**
334
+
335
+
| Mode | Data Received | Bandwidth |
336
+
|-----------|-------------------|-----------|
337
+
| Jetstream | ~50,000 events | ~10 MB |
338
+
| Firehose | ~5,000,000 events | ~500 MB |
339
+
340
+
**Savings:** 50x reduction with Jetstream
341
+
342
+
### CPU Usage
343
+
344
+
**Processing same events:**
345
+
346
+
| Mode | CPU Usage | Processing Time |
347
+
|-----------|-----------|-----------------|
348
+
| Jetstream | ~5% | 0.1ms per event |
349
+
| Firehose | ~20% | 0.4ms per event |
350
+
351
+
**Savings:** 4x more efficient with Jetstream
352
+
353
+
### Cost Implications
354
+
355
+
For a medium-traffic application:
356
+
357
+
| Mode | Monthly Bandwidth | Est. Cost* |
358
+
|-----------|-------------------|------------|
359
+
| Jetstream | ~20 GB | ~$2 |
360
+
| Firehose | ~10 TB | ~$1000 |
361
+
362
+
*Estimates vary by provider and usage
363
+
364
+
## Best Practices
365
+
366
+
### Start with Jetstream
367
+
368
+
Start with Jetstream for most applications:
369
+
370
+
```env
371
+
SIGNAL_MODE=jetstream
372
+
```
373
+
374
+
Switch to Firehose only if you need raw CBOR/CAR access.
375
+
376
+
### Use Firehose for Development
377
+
378
+
Test with Firehose in development to see all events:
379
+
380
+
```env
381
+
# .env.local
382
+
SIGNAL_MODE=firehose
383
+
384
+
# .env.production
385
+
SIGNAL_MODE=jetstream
386
+
```
387
+
388
+
### Monitor Performance
389
+
390
+
Track your Signal's performance:
391
+
392
+
```php
393
+
public function handle(SignalEvent $event): void
394
+
{
395
+
$start = microtime(true);
396
+
397
+
// Your logic
398
+
399
+
$duration = microtime(true) - $start;
400
+
401
+
if ($duration > 0.1) {
402
+
Log::warning('Slow signal processing', [
403
+
'signal' => static::class,
404
+
'duration' => $duration,
405
+
'mode' => config('signal.mode'),
406
+
]);
407
+
}
408
+
}
409
+
```
410
+
411
+
### Use Queues with Firehose
412
+
413
+
Firehose generates high volume. Use queues to avoid blocking:
414
+
415
+
```php
416
+
public function shouldQueue(): bool
417
+
{
418
+
// Queue when using Firehose
419
+
return config('signal.mode') === 'firehose';
420
+
}
421
+
```
422
+
423
+
[Learn more about queue integration โ](queues.md)
424
+
425
+
## Testing Both Modes
426
+
427
+
Test your Signals work in both modes:
428
+
429
+
```bash
430
+
# Test with Jetstream
431
+
SIGNAL_MODE=jetstream php artisan signal:test MySignal
432
+
433
+
# Test with Firehose
434
+
SIGNAL_MODE=firehose php artisan signal:test MySignal
435
+
```
436
+
437
+
[Learn more about testing โ](testing.md)
438
+
439
+
## Common Questions
440
+
441
+
### Can I use both modes simultaneously?
442
+
443
+
No, each consumer runs in one mode. However, you can run multiple consumers:
444
+
445
+
```bash
446
+
# Terminal 1 - Jetstream consumer
447
+
SIGNAL_MODE=jetstream php artisan signal:consume
448
+
449
+
# Terminal 2 - Firehose consumer
450
+
SIGNAL_MODE=firehose php artisan signal:consume
451
+
```
452
+
453
+
### Will my Signals break if I switch modes?
454
+
455
+
Signals work in both modes without changes. The main difference is:
456
+
- Jetstream provides server-side filtering (more efficient)
457
+
- Firehose provides raw CBOR/CAR data access (more comprehensive)
458
+
459
+
### How do I know which mode I'm using?
460
+
461
+
Check at runtime:
462
+
463
+
```php
464
+
$mode = config('signal.mode'); // 'jetstream' or 'firehose'
465
+
```
466
+
467
+
Or via Facade:
468
+
469
+
```php
470
+
use SocialDept\AtpSignals\Facades\Signal;
471
+
472
+
$mode = Signal::getMode();
473
+
```
474
+
475
+
### Can I switch modes while consuming?
476
+
477
+
No, you must restart the consumer:
478
+
479
+
```bash
480
+
# Stop current consumer (Ctrl+C)
481
+
482
+
# Change mode
483
+
# Edit .env: SIGNAL_MODE=firehose
484
+
485
+
# Start new consumer
486
+
php artisan signal:consume
487
+
```
488
+
489
+
## Next Steps
490
+
491
+
- **[Learn about queue integration โ](queues.md)** - Handle high-volume events efficiently
492
+
- **[Review configuration options โ](configuration.md)** - Fine-tune your setup
493
+
- **[See real-world examples โ](examples.md)** - Learn from production patterns
+672
docs/queues.md
+672
docs/queues.md
···
1
+
# Queue Integration
2
+
3
+
Processing AT Protocol events can be resource-intensive. Signal's queue integration lets you handle events asynchronously, preventing bottlenecks and improving performance.
4
+
5
+
## Why Use Queues?
6
+
7
+
### Without Queues (Synchronous)
8
+
9
+
```php
10
+
public function handle(SignalEvent $event): void
11
+
{
12
+
$this->performExpensiveAnalysis($event); // Blocks for 2 seconds
13
+
$this->sendNotifications($event); // Blocks for 1 second
14
+
$this->updateDatabase($event); // Blocks for 0.5 seconds
15
+
}
16
+
```
17
+
18
+
**Problems:**
19
+
- Consumer blocks while processing (3.5 seconds per event)
20
+
- Events queue up during slow operations
21
+
- Risk of disconnection during long processing
22
+
- Can't scale horizontally
23
+
- Memory issues with long-running processes
24
+
25
+
### With Queues (Asynchronous)
26
+
27
+
```php
28
+
public function shouldQueue(): bool
29
+
{
30
+
return true;
31
+
}
32
+
33
+
public function handle(SignalEvent $event): void
34
+
{
35
+
$this->performExpensiveAnalysis($event); // Runs in background
36
+
$this->sendNotifications($event); // Runs in background
37
+
$this->updateDatabase($event); // Runs in background
38
+
}
39
+
```
40
+
41
+
**Benefits:**
42
+
- Consumer stays responsive
43
+
- Processing happens in parallel
44
+
- Scale by adding queue workers
45
+
- Better memory management
46
+
- Automatic retry on failures
47
+
48
+
## Basic Queue Configuration
49
+
50
+
### Enable Queueing
51
+
52
+
Simply return `true` from `shouldQueue()`:
53
+
54
+
```php
55
+
class MySignal extends Signal
56
+
{
57
+
public function eventTypes(): array
58
+
{
59
+
return ['commit'];
60
+
}
61
+
62
+
public function shouldQueue(): bool
63
+
{
64
+
return true; // Enable queuing
65
+
}
66
+
67
+
public function handle(SignalEvent $event): void
68
+
{
69
+
// This now runs in a queue job
70
+
}
71
+
}
72
+
```
73
+
74
+
That's it! Signal automatically:
75
+
- Creates a queue job for each event
76
+
- Serializes the event data
77
+
- Dispatches to Laravel's queue system
78
+
- Handles retries and failures
79
+
80
+
### Default Queue Configuration
81
+
82
+
Signal uses your Laravel queue configuration:
83
+
84
+
```env
85
+
# Default queue connection
86
+
QUEUE_CONNECTION=redis
87
+
88
+
# Signal-specific queue (optional)
89
+
SIGNAL_QUEUE=signal
90
+
91
+
# Signal queue connection (optional)
92
+
SIGNAL_QUEUE_CONNECTION=redis
93
+
```
94
+
95
+
## Customizing Queue Behavior
96
+
97
+
### Specify Queue Name
98
+
99
+
Send events to a specific queue:
100
+
101
+
```php
102
+
public function shouldQueue(): bool
103
+
{
104
+
return true;
105
+
}
106
+
107
+
public function queue(): string
108
+
{
109
+
return 'high-priority'; // Queue name
110
+
}
111
+
```
112
+
113
+
Now your events go to the `high-priority` queue:
114
+
115
+
```bash
116
+
php artisan queue:work --queue=high-priority
117
+
```
118
+
119
+
### Specify Queue Connection
120
+
121
+
Use a different queue connection:
122
+
123
+
```php
124
+
public function shouldQueue(): bool
125
+
{
126
+
return true;
127
+
}
128
+
129
+
public function queueConnection(): string
130
+
{
131
+
return 'redis'; // Connection name
132
+
}
133
+
```
134
+
135
+
### Combine Queue Configuration
136
+
137
+
```php
138
+
public function shouldQueue(): bool
139
+
{
140
+
return true;
141
+
}
142
+
143
+
public function queueConnection(): string
144
+
{
145
+
return 'redis';
146
+
}
147
+
148
+
public function queue(): string
149
+
{
150
+
return 'signal-events';
151
+
}
152
+
```
153
+
154
+
## Running Queue Workers
155
+
156
+
### Start a Worker
157
+
158
+
Process queued events:
159
+
160
+
```bash
161
+
php artisan queue:work
162
+
```
163
+
164
+
### Process Specific Queue
165
+
166
+
```bash
167
+
php artisan queue:work --queue=signal
168
+
```
169
+
170
+
### Multiple Queues with Priority
171
+
172
+
Process high-priority queue first:
173
+
174
+
```bash
175
+
php artisan queue:work --queue=high-priority,default
176
+
```
177
+
178
+
### Scale with Multiple Workers
179
+
180
+
Run multiple workers for throughput:
181
+
182
+
```bash
183
+
# Terminal 1
184
+
php artisan queue:work --queue=signal
185
+
186
+
# Terminal 2
187
+
php artisan queue:work --queue=signal
188
+
189
+
# Terminal 3
190
+
php artisan queue:work --queue=signal
191
+
```
192
+
193
+
### Supervisor Configuration
194
+
195
+
For production, use Supervisor to manage workers:
196
+
197
+
```ini
198
+
[program:signal-queue-worker]
199
+
process_name=%(program_name)s_%(process_num)02d
200
+
command=php /path/to/artisan queue:work --sleep=3 --tries=3 --queue=signal
201
+
autostart=true
202
+
autorestart=true
203
+
stopasgroup=true
204
+
killasgroup=true
205
+
user=www-data
206
+
numprocs=4
207
+
redirect_stderr=true
208
+
stdout_logfile=/path/to/logs/signal-worker.log
209
+
stopwaitsecs=3600
210
+
```
211
+
212
+
This creates 4 workers processing the `signal` queue.
213
+
214
+
## Error Handling
215
+
216
+
### Failed Method
217
+
218
+
Handle job failures:
219
+
220
+
```php
221
+
public function shouldQueue(): bool
222
+
{
223
+
return true;
224
+
}
225
+
226
+
public function handle(SignalEvent $event): void
227
+
{
228
+
// Your logic that might fail
229
+
$this->riskyOperation($event);
230
+
}
231
+
232
+
public function failed(SignalEvent $event, \Throwable $exception): void
233
+
{
234
+
Log::error('Signal processing failed', [
235
+
'signal' => static::class,
236
+
'did' => $event->did,
237
+
'collection' => $event->getCollection(),
238
+
'error' => $exception->getMessage(),
239
+
'trace' => $exception->getTraceAsString(),
240
+
]);
241
+
242
+
// Optional: Send alerts
243
+
$this->notifyAdmin($exception);
244
+
245
+
// Optional: Store for manual review
246
+
FailedSignal::create([
247
+
'event_data' => $event->toArray(),
248
+
'exception' => $exception->getMessage(),
249
+
]);
250
+
}
251
+
```
252
+
253
+
### Automatic Retries
254
+
255
+
Laravel automatically retries failed jobs:
256
+
257
+
```bash
258
+
# Retry up to 3 times
259
+
php artisan queue:work --tries=3
260
+
```
261
+
262
+
Configure retry delay:
263
+
264
+
```php
265
+
public function retryAfter(): int
266
+
{
267
+
return 60; // Wait 60 seconds before retry
268
+
}
269
+
```
270
+
271
+
### Exponential Backoff
272
+
273
+
Increase delay between retries:
274
+
275
+
```php
276
+
public function backoff(): array
277
+
{
278
+
return [10, 30, 60]; // 10s, then 30s, then 60s
279
+
}
280
+
```
281
+
282
+
## Performance Optimization
283
+
284
+
### Batch Processing
285
+
286
+
Process multiple events at once:
287
+
288
+
```php
289
+
use Illuminate\Support\Collection;
290
+
291
+
class BatchPostSignal extends Signal
292
+
{
293
+
public function shouldQueue(): bool
294
+
{
295
+
return true;
296
+
}
297
+
298
+
public function handle(SignalEvent $event): void
299
+
{
300
+
// Collect events in cache
301
+
$events = Cache::get('pending_posts', []);
302
+
$events[] = $event->toArray();
303
+
304
+
Cache::put('pending_posts', $events, now()->addMinutes(5));
305
+
306
+
// Process in batches of 100
307
+
if (count($events) >= 100) {
308
+
$this->processBatch($events);
309
+
Cache::forget('pending_posts');
310
+
}
311
+
}
312
+
313
+
private function processBatch(array $events): void
314
+
{
315
+
// Bulk insert, API calls, etc.
316
+
}
317
+
}
318
+
```
319
+
320
+
### Conditional Queuing
321
+
322
+
Queue only expensive operations:
323
+
324
+
```php
325
+
public function shouldQueue(): bool
326
+
{
327
+
// Queue during high traffic
328
+
return now()->hour >= 9 && now()->hour <= 17;
329
+
}
330
+
```
331
+
332
+
Or based on event type:
333
+
334
+
```php
335
+
public function handle(SignalEvent $event): void
336
+
{
337
+
if ($this->isExpensive($event)) {
338
+
dispatch(function () use ($event) {
339
+
$this->handleExpensive($event);
340
+
})->onQueue('slow-operations');
341
+
} else {
342
+
$this->handleQuick($event);
343
+
}
344
+
}
345
+
```
346
+
347
+
### Rate Limiting
348
+
349
+
Prevent overwhelming external APIs:
350
+
351
+
```php
352
+
use Illuminate\Support\Facades\RateLimiter;
353
+
354
+
public function handle(SignalEvent $event): void
355
+
{
356
+
RateLimiter::attempt(
357
+
'api-calls',
358
+
$perMinute = 100,
359
+
function () use ($event) {
360
+
$this->callExternalAPI($event);
361
+
}
362
+
);
363
+
}
364
+
```
365
+
366
+
## Common Patterns
367
+
368
+
### High-Volume Signal
369
+
370
+
Process millions of events efficiently:
371
+
372
+
```php
373
+
class HighVolumeSignal extends Signal
374
+
{
375
+
public function eventTypes(): array
376
+
{
377
+
return ['commit'];
378
+
}
379
+
380
+
public function collections(): ?array
381
+
{
382
+
return ['app.bsky.feed.post'];
383
+
}
384
+
385
+
public function shouldQueue(): bool
386
+
{
387
+
return true;
388
+
}
389
+
390
+
public function queue(): string
391
+
{
392
+
return 'high-volume';
393
+
}
394
+
395
+
public function handle(SignalEvent $event): void
396
+
{
397
+
// Lightweight processing only
398
+
$this->incrementCounter($event);
399
+
}
400
+
}
401
+
```
402
+
403
+
Run many workers:
404
+
405
+
```bash
406
+
# 10 workers on high-volume queue
407
+
php artisan queue:work --queue=high-volume --workers=10
408
+
```
409
+
410
+
### Priority Queues
411
+
412
+
Different priorities for different events:
413
+
414
+
```php
415
+
class PrioritySignal extends Signal
416
+
{
417
+
public function shouldQueue(): bool
418
+
{
419
+
return true;
420
+
}
421
+
422
+
public function queue(): string
423
+
{
424
+
// Determine priority based on event
425
+
return $this->getQueueForEvent();
426
+
}
427
+
428
+
private function getQueueForEvent(): string
429
+
{
430
+
// Check event attributes
431
+
// Return 'high', 'medium', or 'low'
432
+
}
433
+
}
434
+
```
435
+
436
+
Process high-priority first:
437
+
438
+
```bash
439
+
php artisan queue:work --queue=high,medium,low
440
+
```
441
+
442
+
### Delayed Processing
443
+
444
+
Delay event processing:
445
+
446
+
```php
447
+
public function handle(SignalEvent $event): void
448
+
{
449
+
// Dispatch with delay
450
+
dispatch(function () use ($event) {
451
+
$this->processLater($event);
452
+
})->delay(now()->addMinutes(5));
453
+
}
454
+
```
455
+
456
+
### Scheduled Batch Processing
457
+
458
+
Collect events and process on schedule:
459
+
460
+
```php
461
+
// Signal collects events
462
+
class CollectorSignal extends Signal
463
+
{
464
+
public function handle(SignalEvent $event): void
465
+
{
466
+
PendingEvent::create([
467
+
'data' => $event->toArray(),
468
+
]);
469
+
}
470
+
}
471
+
472
+
// Scheduled command processes batch
473
+
// app/Console/Kernel.php
474
+
protected function schedule(Schedule $schedule)
475
+
{
476
+
$schedule->call(function () {
477
+
$events = PendingEvent::all();
478
+
$this->processBatch($events);
479
+
PendingEvent::truncate();
480
+
})->hourly();
481
+
}
482
+
```
483
+
484
+
## Monitoring Queues
485
+
486
+
### Check Queue Status
487
+
488
+
```bash
489
+
# View failed jobs
490
+
php artisan queue:failed
491
+
492
+
# Retry failed job
493
+
php artisan queue:retry {id}
494
+
495
+
# Retry all failed
496
+
php artisan queue:retry all
497
+
498
+
# Clear failed jobs
499
+
php artisan queue:flush
500
+
```
501
+
502
+
### Queue Metrics
503
+
504
+
Track queue performance:
505
+
506
+
```php
507
+
use Illuminate\Support\Facades\Queue;
508
+
509
+
Queue::after(function ($connection, $job, $data) {
510
+
Log::info('Job processed', [
511
+
'queue' => $job->queue,
512
+
'class' => $job->resolveName(),
513
+
'attempts' => $job->attempts(),
514
+
]);
515
+
});
516
+
```
517
+
518
+
### Horizon (Recommended)
519
+
520
+
Use Laravel Horizon for Redis queues:
521
+
522
+
```bash
523
+
composer require laravel/horizon
524
+
php artisan horizon:install
525
+
php artisan horizon
526
+
```
527
+
528
+
View dashboard at `/horizon`.
529
+
530
+
## Testing Queued Signals
531
+
532
+
### Test with Fake Queue
533
+
534
+
```php
535
+
use Illuminate\Support\Facades\Queue;
536
+
537
+
/** @test */
538
+
public function it_queues_events()
539
+
{
540
+
Queue::fake();
541
+
542
+
$signal = new MySignal();
543
+
$event = $this->createSampleEvent();
544
+
545
+
// Assert queue behavior
546
+
$this->assertTrue($signal->shouldQueue());
547
+
548
+
// Process would normally queue
549
+
$signal->handle($event);
550
+
551
+
// Verify job was queued
552
+
Queue::assertPushed(SignalJob::class);
553
+
}
554
+
```
555
+
556
+
### Test Synchronously
557
+
558
+
Disable queueing for tests:
559
+
560
+
```php
561
+
/** @test */
562
+
public function it_processes_events()
563
+
{
564
+
config(['queue.default' => 'sync']);
565
+
566
+
$signal = new MySignal();
567
+
$event = $this->createSampleEvent();
568
+
569
+
$signal->handle($event);
570
+
571
+
// Assert processing happened
572
+
$this->assertDatabaseHas('posts', [...]);
573
+
}
574
+
```
575
+
576
+
[Learn more about testing โ](testing.md)
577
+
578
+
## Production Checklist
579
+
580
+
### Infrastructure
581
+
582
+
- [ ] Queue driver configured (Redis recommended)
583
+
- [ ] Supervisor installed and configured
584
+
- [ ] Multiple workers running
585
+
- [ ] Worker auto-restart enabled
586
+
- [ ] Logs configured and monitored
587
+
588
+
### Configuration
589
+
590
+
- [ ] Queue connection set correctly
591
+
- [ ] Queue names configured
592
+
- [ ] Retry attempts configured
593
+
- [ ] Timeout values appropriate
594
+
- [ ] Memory limits set
595
+
596
+
### Monitoring
597
+
598
+
- [ ] Queue length monitored
599
+
- [ ] Failed jobs tracked
600
+
- [ ] Worker health checked
601
+
- [ ] Processing times measured
602
+
- [ ] Horizon installed (if using Redis)
603
+
604
+
### Scaling
605
+
606
+
- [ ] Worker count appropriate for volume
607
+
- [ ] Priority queues configured
608
+
- [ ] Rate limiting implemented
609
+
- [ ] Database connection pooling enabled
610
+
- [ ] Redis maxmemory policy set
611
+
612
+
## Common Issues
613
+
614
+
### Queue Jobs Not Processing
615
+
616
+
**Check worker is running:**
617
+
```bash
618
+
php artisan queue:work
619
+
```
620
+
621
+
**Check queue connection:**
622
+
```php
623
+
// Should match QUEUE_CONNECTION
624
+
config('queue.default')
625
+
```
626
+
627
+
### Jobs Timing Out
628
+
629
+
**Increase timeout:**
630
+
```bash
631
+
php artisan queue:work --timeout=300
632
+
```
633
+
634
+
**Or in Signal:**
635
+
```php
636
+
public function timeout(): int
637
+
{
638
+
return 300; // 5 minutes
639
+
}
640
+
```
641
+
642
+
### Memory Leaks
643
+
644
+
**Restart workers periodically:**
645
+
```bash
646
+
php artisan queue:work --max-jobs=1000
647
+
```
648
+
649
+
Or:
650
+
```bash
651
+
php artisan queue:work --max-time=3600
652
+
```
653
+
654
+
### Failed Jobs Piling Up
655
+
656
+
**Review failures:**
657
+
```bash
658
+
php artisan queue:failed
659
+
```
660
+
661
+
**Retry or delete:**
662
+
```bash
663
+
php artisan queue:retry all
664
+
# or
665
+
php artisan queue:flush
666
+
```
667
+
668
+
## Next Steps
669
+
670
+
- **[Review configuration options โ](configuration.md)** - Fine-tune queue settings
671
+
- **[Learn about testing โ](testing.md)** - Test queued Signals
672
+
- **[See real-world examples โ](examples.md)** - Learn from production queue patterns
+391
docs/quickstart.md
+391
docs/quickstart.md
···
1
+
# Quickstart Guide
2
+
3
+
This guide will walk you through building your first Signal and consuming AT Protocol events in under 5 minutes.
4
+
5
+
## Prerequisites
6
+
7
+
Before starting, ensure you have:
8
+
9
+
- [Installed Signal](installation.md) in your Laravel application
10
+
- Run `php artisan signal:install` successfully
11
+
- Basic familiarity with Laravel
12
+
13
+
## Your First Signal
14
+
15
+
We'll build a Signal that logs every new post created on Bluesky.
16
+
17
+
### Step 1: Generate a Signal
18
+
19
+
Use the Artisan command to create a new Signal:
20
+
21
+
```bash
22
+
php artisan make:signal NewPostSignal
23
+
```
24
+
25
+
This creates `app/Signals/NewPostSignal.php` with a basic template.
26
+
27
+
### Step 2: Define the Signal
28
+
29
+
Open the generated file and update it:
30
+
31
+
```php
32
+
<?php
33
+
34
+
namespace App\Signals;
35
+
36
+
use SocialDept\AtpSignals\Events\SignalEvent;
37
+
use SocialDept\AtpSignals\Signals\Signal;
38
+
use Illuminate\Support\Facades\Log;
39
+
40
+
class NewPostSignal extends Signal
41
+
{
42
+
/**
43
+
* Define which event types to listen for.
44
+
*/
45
+
public function eventTypes(): array
46
+
{
47
+
return ['commit']; // Listen for repository commits
48
+
}
49
+
50
+
/**
51
+
* Filter by specific collections.
52
+
*/
53
+
public function collections(): ?array
54
+
{
55
+
return ['app.bsky.feed.post']; // Only handle posts
56
+
}
57
+
58
+
/**
59
+
* Handle the event when it arrives.
60
+
*/
61
+
public function handle(SignalEvent $event): void
62
+
{
63
+
$record = $event->getRecord();
64
+
65
+
Log::info('New post created', [
66
+
'author' => $event->did,
67
+
'text' => $record->text ?? null,
68
+
'created_at' => $record->createdAt ?? null,
69
+
]);
70
+
}
71
+
}
72
+
```
73
+
74
+
### Step 3: Start Consuming Events
75
+
76
+
Run the consumer to start listening:
77
+
78
+
```bash
79
+
php artisan signal:consume
80
+
```
81
+
82
+
You should see output like:
83
+
84
+
```
85
+
Starting Signal consumer in jetstream mode...
86
+
Connecting to wss://jetstream2.us-east.bsky.network...
87
+
Connected! Listening for events...
88
+
```
89
+
90
+
**Congratulations!** Your Signal is now processing every new post on Bluesky in real-time. Check your Laravel logs to see the posts coming in.
91
+
92
+
## Understanding What Just Happened
93
+
94
+
Let's break down the Signal you created:
95
+
96
+
### Event Types
97
+
98
+
```php
99
+
public function eventTypes(): array
100
+
{
101
+
return ['commit'];
102
+
}
103
+
```
104
+
105
+
This tells Signal you want **commit** events, which represent changes to repositories (like creating posts, likes, follows, etc.).
106
+
107
+
Available event types:
108
+
- `commit` - Repository commits (most common)
109
+
- `identity` - Identity changes (handle updates)
110
+
- `account` - Account status changes
111
+
112
+
### Collections
113
+
114
+
```php
115
+
public function collections(): ?array
116
+
{
117
+
return ['app.bsky.feed.post'];
118
+
}
119
+
```
120
+
121
+
This filters to only **post** collections. Without this filter, your Signal would receive all commit events for every collection type.
122
+
123
+
Common collections:
124
+
- `app.bsky.feed.post` - Posts
125
+
- `app.bsky.feed.like` - Likes
126
+
- `app.bsky.graph.follow` - Follows
127
+
- `app.bsky.feed.repost` - Reposts
128
+
129
+
[Learn more about filtering โ](filtering.md)
130
+
131
+
### Handler Method
132
+
133
+
```php
134
+
public function handle(SignalEvent $event): void
135
+
{
136
+
$record = $event->getRecord();
137
+
// Your logic here
138
+
}
139
+
```
140
+
141
+
This is where your code runs for each matching event. The `$event` object contains:
142
+
143
+
- `did` - The user's DID (decentralized identifier)
144
+
- `timeUs` - Timestamp in microseconds
145
+
- `commit` - Commit details (collection, operation, record key)
146
+
- `getRecord()` - The actual record data
147
+
148
+
## Next Steps
149
+
150
+
Now that you've built your first Signal, let's make it more useful.
151
+
152
+
### Add More Filtering
153
+
154
+
Track specific operations only:
155
+
156
+
```php
157
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
158
+
159
+
public function operations(): ?array
160
+
{
161
+
return [SignalCommitOperation::Create]; // Only new posts, not edits
162
+
}
163
+
```
164
+
165
+
[Learn more about filtering โ](filtering.md)
166
+
167
+
### Process Events Asynchronously
168
+
169
+
For expensive operations, use Laravel queues:
170
+
171
+
```php
172
+
public function shouldQueue(): bool
173
+
{
174
+
return true;
175
+
}
176
+
177
+
public function handle(SignalEvent $event): void
178
+
{
179
+
// This now runs in a background job
180
+
$this->performExpensiveAnalysis($event);
181
+
}
182
+
```
183
+
184
+
[Learn more about queues โ](queues.md)
185
+
186
+
### Store Data
187
+
188
+
Let's store posts in your database:
189
+
190
+
```php
191
+
use App\Models\Post;
192
+
193
+
public function handle(SignalEvent $event): void
194
+
{
195
+
$record = $event->getRecord();
196
+
197
+
Post::updateOrCreate(
198
+
[
199
+
'did' => $event->did,
200
+
'rkey' => $event->commit->rkey,
201
+
],
202
+
[
203
+
'text' => $record->text ?? null,
204
+
'created_at' => $record->createdAt,
205
+
]
206
+
);
207
+
}
208
+
```
209
+
210
+
### Handle Multiple Collections
211
+
212
+
Use wildcards to match multiple collections:
213
+
214
+
```php
215
+
public function collections(): ?array
216
+
{
217
+
return [
218
+
'app.bsky.feed.*', // All feed events
219
+
];
220
+
}
221
+
222
+
public function handle(SignalEvent $event): void
223
+
{
224
+
$collection = $event->getCollection();
225
+
226
+
match ($collection) {
227
+
'app.bsky.feed.post' => $this->handlePost($event),
228
+
'app.bsky.feed.like' => $this->handleLike($event),
229
+
'app.bsky.feed.repost' => $this->handleRepost($event),
230
+
default => null,
231
+
};
232
+
}
233
+
```
234
+
235
+
## Building Something Real
236
+
237
+
Let's build a simple engagement tracker:
238
+
239
+
```php
240
+
<?php
241
+
242
+
namespace App\Signals;
243
+
244
+
use App\Models\EngagementMetric;
245
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
246
+
use SocialDept\AtpSignals\Events\SignalEvent;
247
+
use SocialDept\AtpSignals\Signals\Signal;
248
+
249
+
class EngagementTrackerSignal extends Signal
250
+
{
251
+
public function eventTypes(): array
252
+
{
253
+
return ['commit'];
254
+
}
255
+
256
+
public function collections(): ?array
257
+
{
258
+
return [
259
+
'app.bsky.feed.post',
260
+
'app.bsky.feed.like',
261
+
'app.bsky.feed.repost',
262
+
];
263
+
}
264
+
265
+
public function operations(): ?array
266
+
{
267
+
return [SignalCommitOperation::Create];
268
+
}
269
+
270
+
public function shouldQueue(): bool
271
+
{
272
+
return true; // Process in background
273
+
}
274
+
275
+
public function handle(SignalEvent $event): void
276
+
{
277
+
EngagementMetric::create([
278
+
'date' => now()->toDateString(),
279
+
'collection' => $event->getCollection(),
280
+
'event_type' => 'create',
281
+
'count' => 1,
282
+
]);
283
+
}
284
+
}
285
+
```
286
+
287
+
This Signal tracks all engagement activity (posts, likes, reposts) and stores metrics for analysis.
288
+
289
+
## Testing Your Signal
290
+
291
+
Before running in production, test your Signal with sample data:
292
+
293
+
```bash
294
+
php artisan signal:test NewPostSignal
295
+
```
296
+
297
+
This will run your Signal with a sample event and show you the output.
298
+
299
+
[Learn more about testing โ](testing.md)
300
+
301
+
## Common Patterns
302
+
303
+
### Only Process Specific Users
304
+
305
+
```php
306
+
public function dids(): ?array
307
+
{
308
+
return [
309
+
'did:plc:z72i7hdynmk6r22z27h6tvur', // Specific user
310
+
];
311
+
}
312
+
```
313
+
314
+
### Add Custom Filtering Logic
315
+
316
+
```php
317
+
public function shouldHandle(SignalEvent $event): bool
318
+
{
319
+
$record = $event->getRecord();
320
+
321
+
// Only handle posts with images
322
+
return isset($record->embed);
323
+
}
324
+
```
325
+
326
+
### Handle Failures Gracefully
327
+
328
+
```php
329
+
public function failed(SignalEvent $event, \Throwable $exception): void
330
+
{
331
+
Log::error('Signal processing failed', [
332
+
'event' => $event->toArray(),
333
+
'error' => $exception->getMessage(),
334
+
]);
335
+
336
+
// Optionally notify admins, store for retry, etc.
337
+
}
338
+
```
339
+
340
+
## Running in Production
341
+
342
+
### Using Supervisor
343
+
344
+
For production, run Signal under a process monitor like Supervisor:
345
+
346
+
```ini
347
+
[program:signal-consumer]
348
+
process_name=%(program_name)s
349
+
command=php /path/to/artisan signal:consume
350
+
autostart=true
351
+
autorestart=true
352
+
user=www-data
353
+
redirect_stderr=true
354
+
stdout_logfile=/path/to/logs/signal-consumer.log
355
+
```
356
+
357
+
### Starting from Last Position
358
+
359
+
Signal automatically saves cursor positions, so it resumes from where it left off:
360
+
361
+
```bash
362
+
php artisan signal:consume
363
+
```
364
+
365
+
To start fresh and ignore stored position:
366
+
367
+
```bash
368
+
php artisan signal:consume --fresh
369
+
```
370
+
371
+
To start from a specific cursor:
372
+
373
+
```bash
374
+
php artisan signal:consume --cursor=123456789
375
+
```
376
+
377
+
## What's Next?
378
+
379
+
You now know the basics of building Signals! Explore more advanced topics:
380
+
381
+
- **[Signal Architecture](signals.md)** - Deep dive into Signal structure
382
+
- **[Advanced Filtering](filtering.md)** - Master collection patterns and wildcards
383
+
- **[Jetstream vs Firehose](modes.md)** - Choose the right mode for your use case
384
+
- **[Queue Integration](queues.md)** - Build high-performance processors
385
+
- **[Real-World Examples](examples.md)** - Learn from production use cases
386
+
387
+
## Getting Help
388
+
389
+
- Check the [examples documentation](examples.md) for more patterns
390
+
- Review the [configuration guide](configuration.md) for all options
391
+
- Open an issue on GitHub if you encounter problems
+702
docs/signals.md
+702
docs/signals.md
···
1
+
# Creating Signals
2
+
3
+
Signals are the heart of the Signal package. They define how your application responds to AT Protocol events.
4
+
5
+
## What is a Signal?
6
+
7
+
A **Signal** is a PHP class that:
8
+
9
+
1. Listens for specific types of AT Protocol events
10
+
2. Filters those events based on your criteria
11
+
3. Executes custom logic when matching events arrive
12
+
13
+
Think of Signals like Laravel event listeners, but specifically designed for the AT Protocol.
14
+
15
+
## Basic Signal Structure
16
+
17
+
Every Signal extends the base `Signal` class:
18
+
19
+
```php
20
+
<?php
21
+
22
+
namespace App\Signals;
23
+
24
+
use SocialDept\AtpSignals\Events\SignalEvent;
25
+
use SocialDept\AtpSignals\Signals\Signal;
26
+
27
+
class MySignal extends Signal
28
+
{
29
+
/**
30
+
* Define which event types to listen for.
31
+
* Required.
32
+
*/
33
+
public function eventTypes(): array
34
+
{
35
+
return ['commit'];
36
+
}
37
+
38
+
/**
39
+
* Handle the event when it arrives.
40
+
* Required.
41
+
*/
42
+
public function handle(SignalEvent $event): void
43
+
{
44
+
// Your logic here
45
+
}
46
+
}
47
+
```
48
+
49
+
Only two methods are required:
50
+
- `eventTypes()` - Which event types to listen for
51
+
- `handle()` - What to do when events arrive
52
+
53
+
## Creating Signals
54
+
55
+
### Using Artisan (Recommended)
56
+
57
+
Generate a new Signal with the make command:
58
+
59
+
```bash
60
+
php artisan make:signal MySignal
61
+
```
62
+
63
+
This creates `app/Signals/MySignal.php` with a basic template.
64
+
65
+
#### With Options
66
+
67
+
Generate a Signal with pre-configured filters:
68
+
69
+
```bash
70
+
# Create a Signal for posts only
71
+
php artisan make:signal PostSignal --type=commit --collection=app.bsky.feed.post
72
+
73
+
# Create a Signal for follows
74
+
php artisan make:signal FollowSignal --type=commit --collection=app.bsky.graph.follow
75
+
```
76
+
77
+
### Manual Creation
78
+
79
+
You can also create Signals manually in `app/Signals/`:
80
+
81
+
```php
82
+
<?php
83
+
84
+
namespace App\Signals;
85
+
86
+
use SocialDept\AtpSignals\Events\SignalEvent;
87
+
use SocialDept\AtpSignals\Signals\Signal;
88
+
89
+
class ManualSignal extends Signal
90
+
{
91
+
public function eventTypes(): array
92
+
{
93
+
return ['commit'];
94
+
}
95
+
96
+
public function handle(SignalEvent $event): void
97
+
{
98
+
//
99
+
}
100
+
}
101
+
```
102
+
103
+
Signals are automatically discovered from `app/Signals/` - no registration needed.
104
+
105
+
## Event Types
106
+
107
+
Signals can listen for three types of AT Protocol events:
108
+
109
+
### Commit Events
110
+
111
+
Repository commits represent changes to user data:
112
+
113
+
```php
114
+
use SocialDept\AtpSignals\Enums\SignalEventType;
115
+
116
+
public function eventTypes(): array
117
+
{
118
+
return [SignalEventType::Commit];
119
+
// Or: return ['commit'];
120
+
}
121
+
```
122
+
123
+
**Common commit events:**
124
+
- Creating posts, likes, follows, reposts
125
+
- Updating profile information
126
+
- Deleting content
127
+
128
+
This is the most common event type and what you'll use 99% of the time.
129
+
130
+
### Identity Events
131
+
132
+
Identity changes track handle updates:
133
+
134
+
```php
135
+
public function eventTypes(): array
136
+
{
137
+
return [SignalEventType::Identity];
138
+
// Or: return ['identity'];
139
+
}
140
+
```
141
+
142
+
**Use cases:**
143
+
- Tracking handle changes
144
+
- Updating local user records
145
+
- Monitoring account migrations
146
+
147
+
### Account Events
148
+
149
+
Account status changes track account state:
150
+
151
+
```php
152
+
public function eventTypes(): array
153
+
{
154
+
return [SignalEventType::Account];
155
+
// Or: return ['account'];
156
+
}
157
+
```
158
+
159
+
**Use cases:**
160
+
- Detecting account deactivation
161
+
- Monitoring account status
162
+
- Compliance tracking
163
+
164
+
### Multiple Event Types
165
+
166
+
Listen to multiple event types in one Signal:
167
+
168
+
```php
169
+
public function eventTypes(): array
170
+
{
171
+
return [
172
+
SignalEventType::Commit,
173
+
SignalEventType::Identity,
174
+
];
175
+
}
176
+
177
+
public function handle(SignalEvent $event): void
178
+
{
179
+
if ($event->isCommit()) {
180
+
// Handle commit
181
+
}
182
+
183
+
if ($event->isIdentity()) {
184
+
// Handle identity change
185
+
}
186
+
}
187
+
```
188
+
189
+
## The SignalEvent Object
190
+
191
+
The `SignalEvent` object contains all event data:
192
+
193
+
### Common Properties
194
+
195
+
```php
196
+
public function handle(SignalEvent $event): void
197
+
{
198
+
// User's DID (decentralized identifier)
199
+
$did = $event->did; // "did:plc:z72i7hdynmk6r22z27h6tvur"
200
+
201
+
// Event type (commit, identity, account)
202
+
$kind = $event->kind;
203
+
204
+
// Timestamp in microseconds
205
+
$timestamp = $event->timeUs;
206
+
207
+
// Convert to Carbon instance
208
+
$date = $event->getTimestamp();
209
+
}
210
+
```
211
+
212
+
### Commit Events
213
+
214
+
For commit events, access the `commit` property:
215
+
216
+
```php
217
+
public function handle(SignalEvent $event): void
218
+
{
219
+
if ($event->isCommit()) {
220
+
// Collection (e.g., "app.bsky.feed.post")
221
+
$collection = $event->commit->collection;
222
+
// Or: $collection = $event->getCollection();
223
+
224
+
// Operation (create, update, delete)
225
+
$operation = $event->commit->operation;
226
+
// Or: $operation = $event->getOperation();
227
+
228
+
// Record key (unique identifier)
229
+
$rkey = $event->commit->rkey;
230
+
231
+
// Revision
232
+
$rev = $event->commit->rev;
233
+
234
+
// The actual record data
235
+
$record = $event->commit->record;
236
+
// Or: $record = $event->getRecord();
237
+
}
238
+
}
239
+
```
240
+
241
+
### Working with Records
242
+
243
+
Records contain the actual data (posts, likes, etc.):
244
+
245
+
```php
246
+
public function handle(SignalEvent $event): void
247
+
{
248
+
$record = $event->getRecord();
249
+
250
+
// For posts (app.bsky.feed.post)
251
+
$text = $record->text ?? null;
252
+
$createdAt = $record->createdAt ?? null;
253
+
$embed = $record->embed ?? null;
254
+
$facets = $record->facets ?? null;
255
+
256
+
// For likes (app.bsky.feed.like)
257
+
$subject = $record->subject ?? null;
258
+
259
+
// For follows (app.bsky.graph.follow)
260
+
$subject = $record->subject ?? null;
261
+
}
262
+
```
263
+
264
+
Records are `stdClass` objects, so use null coalescing (`??`) for safety.
265
+
266
+
### Identity Events
267
+
268
+
For identity events, access the `identity` property:
269
+
270
+
```php
271
+
public function handle(SignalEvent $event): void
272
+
{
273
+
if ($event->isIdentity()) {
274
+
// New handle
275
+
$handle = $event->identity->handle;
276
+
277
+
// User's DID
278
+
$did = $event->did;
279
+
280
+
// Sequence number
281
+
$seq = $event->identity->seq;
282
+
283
+
// Timestamp
284
+
$time = $event->identity->time;
285
+
}
286
+
}
287
+
```
288
+
289
+
### Account Events
290
+
291
+
For account events, access the `account` property:
292
+
293
+
```php
294
+
public function handle(SignalEvent $event): void
295
+
{
296
+
if ($event->isAccount()) {
297
+
// Account status
298
+
$active = $event->account->active; // true/false
299
+
300
+
// Status reason
301
+
$status = $event->account->status ?? null;
302
+
303
+
// User's DID
304
+
$did = $event->did;
305
+
306
+
// Sequence number
307
+
$seq = $event->account->seq;
308
+
309
+
// Timestamp
310
+
$time = $event->account->time;
311
+
}
312
+
}
313
+
```
314
+
315
+
## Helper Methods
316
+
317
+
Signals provide several helper methods for common tasks:
318
+
319
+
### Type Checking
320
+
321
+
```php
322
+
public function handle(SignalEvent $event): void
323
+
{
324
+
// Check event type
325
+
if ($event->isCommit()) {
326
+
//
327
+
}
328
+
329
+
if ($event->isIdentity()) {
330
+
//
331
+
}
332
+
333
+
if ($event->isAccount()) {
334
+
//
335
+
}
336
+
}
337
+
```
338
+
339
+
### Operation Checking (Commit Events)
340
+
341
+
```php
342
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
343
+
344
+
public function handle(SignalEvent $event): void
345
+
{
346
+
$operation = $event->getOperation();
347
+
348
+
// Using enum
349
+
if ($operation === SignalCommitOperation::Create) {
350
+
// Handle new records
351
+
}
352
+
353
+
// Using commit helper
354
+
if ($event->commit->isCreate()) {
355
+
// Handle new records
356
+
}
357
+
358
+
if ($event->commit->isUpdate()) {
359
+
// Handle updates
360
+
}
361
+
362
+
if ($event->commit->isDelete()) {
363
+
// Handle deletions
364
+
}
365
+
}
366
+
```
367
+
368
+
### Data Extraction
369
+
370
+
```php
371
+
public function handle(SignalEvent $event): void
372
+
{
373
+
// Get collection (commit events only)
374
+
$collection = $event->getCollection();
375
+
376
+
// Get operation (commit events only)
377
+
$operation = $event->getOperation();
378
+
379
+
// Get record (commit events only)
380
+
$record = $event->getRecord();
381
+
382
+
// Get timestamp as Carbon
383
+
$timestamp = $event->getTimestamp();
384
+
385
+
// Convert to array
386
+
$array = $event->toArray();
387
+
}
388
+
```
389
+
390
+
## Optional Signal Methods
391
+
392
+
Signals support several optional methods for advanced behavior:
393
+
394
+
### Collections Filter
395
+
396
+
Filter by AT Protocol collections:
397
+
398
+
```php
399
+
public function collections(): ?array
400
+
{
401
+
return ['app.bsky.feed.post'];
402
+
}
403
+
```
404
+
405
+
Return `null` to handle all collections.
406
+
407
+
[Learn more about collection filtering โ](filtering.md)
408
+
409
+
### Operations Filter
410
+
411
+
Filter by operation type (commit events only):
412
+
413
+
```php
414
+
public function operations(): ?array
415
+
{
416
+
return [SignalCommitOperation::Create];
417
+
}
418
+
```
419
+
420
+
Return `null` to handle all operations.
421
+
422
+
[Learn more about operation filtering โ](filtering.md)
423
+
424
+
### DIDs Filter
425
+
426
+
Filter by specific users:
427
+
428
+
```php
429
+
public function dids(): ?array
430
+
{
431
+
return [
432
+
'did:plc:z72i7hdynmk6r22z27h6tvur',
433
+
];
434
+
}
435
+
```
436
+
437
+
Return `null` to handle all users.
438
+
439
+
[Learn more about DID filtering โ](filtering.md)
440
+
441
+
### Custom Filtering
442
+
443
+
Add complex filtering logic:
444
+
445
+
```php
446
+
public function shouldHandle(SignalEvent $event): bool
447
+
{
448
+
// Only handle posts with images
449
+
if ($event->isCommit() && $event->getCollection() === 'app.bsky.feed.post') {
450
+
$record = $event->getRecord();
451
+
return isset($record->embed);
452
+
}
453
+
454
+
return true;
455
+
}
456
+
```
457
+
458
+
### Queue Configuration
459
+
460
+
Process events asynchronously:
461
+
462
+
```php
463
+
public function shouldQueue(): bool
464
+
{
465
+
return true;
466
+
}
467
+
468
+
public function queue(): string
469
+
{
470
+
return 'high-priority';
471
+
}
472
+
473
+
public function queueConnection(): string
474
+
{
475
+
return 'redis';
476
+
}
477
+
```
478
+
479
+
[Learn more about queue integration โ](queues.md)
480
+
481
+
### Failure Handling
482
+
483
+
Handle processing failures:
484
+
485
+
```php
486
+
public function failed(SignalEvent $event, \Throwable $exception): void
487
+
{
488
+
Log::error('Signal failed', [
489
+
'signal' => static::class,
490
+
'event' => $event->toArray(),
491
+
'error' => $exception->getMessage(),
492
+
]);
493
+
}
494
+
```
495
+
496
+
## Signal Lifecycle
497
+
498
+
Understanding the Signal lifecycle helps you write better Signals:
499
+
500
+
### 1. Event Arrives
501
+
502
+
An event arrives from the AT Protocol (via Jetstream or Firehose).
503
+
504
+
### 2. Event Type Matching
505
+
506
+
Signal checks if the event type matches your `eventTypes()` definition.
507
+
508
+
### 3. Collection Filtering
509
+
510
+
If defined, Signal checks if the collection matches your `collections()` definition.
511
+
512
+
### 4. Operation Filtering
513
+
514
+
If defined, Signal checks if the operation matches your `operations()` definition.
515
+
516
+
### 5. DID Filtering
517
+
518
+
If defined, Signal checks if the DID matches your `dids()` definition.
519
+
520
+
### 6. Custom Filtering
521
+
522
+
If defined, Signal calls your `shouldHandle()` method.
523
+
524
+
### 7. Queue Decision
525
+
526
+
Signal checks `shouldQueue()` to determine if the event should be queued.
527
+
528
+
### 8. Handler Execution
529
+
530
+
Your `handle()` method is called (either synchronously or via queue).
531
+
532
+
### 9. Failure Handling (if applicable)
533
+
534
+
If an exception occurs, your `failed()` method is called (if defined).
535
+
536
+
## Best Practices
537
+
538
+
### Keep Handlers Focused
539
+
540
+
Each Signal should do one thing well:
541
+
542
+
```php
543
+
// Good - focused on one task
544
+
class TrackNewPostsSignal extends Signal
545
+
{
546
+
public function collections(): ?array
547
+
{
548
+
return ['app.bsky.feed.post'];
549
+
}
550
+
551
+
public function handle(SignalEvent $event): void
552
+
{
553
+
$this->storePost($event);
554
+
}
555
+
}
556
+
557
+
// Less ideal - doing too much
558
+
class MonitorEverythingSignal extends Signal
559
+
{
560
+
public function handle(SignalEvent $event): void
561
+
{
562
+
$this->storePost($event);
563
+
$this->sendNotification($event);
564
+
$this->updateAnalytics($event);
565
+
$this->processRecommendations($event);
566
+
}
567
+
}
568
+
```
569
+
570
+
### Use Queues for Heavy Work
571
+
572
+
Don't block the consumer with expensive operations:
573
+
574
+
```php
575
+
class AnalyzePostSignal extends Signal
576
+
{
577
+
public function shouldQueue(): bool
578
+
{
579
+
return true; // Process in background
580
+
}
581
+
582
+
public function handle(SignalEvent $event): void
583
+
{
584
+
$this->performExpensiveAnalysis($event);
585
+
}
586
+
}
587
+
```
588
+
589
+
### Validate Data Safely
590
+
591
+
Records can have missing or unexpected data:
592
+
593
+
```php
594
+
public function handle(SignalEvent $event): void
595
+
{
596
+
$record = $event->getRecord();
597
+
598
+
// Use null coalescing
599
+
$text = $record->text ?? '';
600
+
601
+
// Validate before processing
602
+
if (empty($text)) {
603
+
return;
604
+
}
605
+
606
+
// Safe to process
607
+
$this->processText($text);
608
+
}
609
+
```
610
+
611
+
### Add Logging
612
+
613
+
Log important events for debugging:
614
+
615
+
```php
616
+
public function handle(SignalEvent $event): void
617
+
{
618
+
Log::debug('Processing event', [
619
+
'signal' => static::class,
620
+
'collection' => $event->getCollection(),
621
+
'operation' => $event->getOperation()->value,
622
+
]);
623
+
624
+
// Your logic
625
+
}
626
+
```
627
+
628
+
### Handle Failures Gracefully
629
+
630
+
Always implement failure handling for queued Signals:
631
+
632
+
```php
633
+
public function failed(SignalEvent $event, \Throwable $exception): void
634
+
{
635
+
Log::error('Signal processing failed', [
636
+
'signal' => static::class,
637
+
'event_did' => $event->did,
638
+
'error' => $exception->getMessage(),
639
+
'trace' => $exception->getTraceAsString(),
640
+
]);
641
+
642
+
// Optionally: send to error tracking service
643
+
// report($exception);
644
+
}
645
+
```
646
+
647
+
## Auto-Discovery
648
+
649
+
Signals are automatically discovered from `app/Signals/` by default. You can customize discovery in `config/signal.php`:
650
+
651
+
```php
652
+
'auto_discovery' => [
653
+
'enabled' => true,
654
+
'path' => app_path('Signals'),
655
+
'namespace' => 'App\\Signals',
656
+
],
657
+
```
658
+
659
+
### Manual Registration
660
+
661
+
Disable auto-discovery and register Signals manually:
662
+
663
+
```php
664
+
'auto_discovery' => [
665
+
'enabled' => false,
666
+
],
667
+
668
+
'signals' => [
669
+
\App\Signals\NewPostSignal::class,
670
+
\App\Signals\NewFollowSignal::class,
671
+
],
672
+
```
673
+
674
+
## Testing Signals
675
+
676
+
Test your Signals before deploying:
677
+
678
+
```bash
679
+
php artisan signal:test MySignal
680
+
```
681
+
682
+
[Learn more about testing โ](testing.md)
683
+
684
+
## Listing Signals
685
+
686
+
View all registered Signals:
687
+
688
+
```bash
689
+
php artisan signal:list
690
+
```
691
+
692
+
This displays:
693
+
- Signal class names
694
+
- Event types they listen for
695
+
- Collection filters (if any)
696
+
- Queue configuration
697
+
698
+
## Next Steps
699
+
700
+
- **[Learn about filtering โ](filtering.md)** - Master collection patterns and wildcards
701
+
- **[Understand queue integration โ](queues.md)** - Build high-performance processors
702
+
- **[See real-world examples โ](examples.md)** - Learn from production use cases
+728
docs/testing.md
+728
docs/testing.md
···
1
+
# Testing Signals
2
+
3
+
Testing your Signals ensures they behave correctly before deploying to production. Signal provides tools for both manual and automated testing.
4
+
5
+
## Quick Testing with Artisan
6
+
7
+
The fastest way to test a Signal is with the `signal:test` command.
8
+
9
+
### Test a Signal
10
+
11
+
```bash
12
+
php artisan signal:test NewPostSignal
13
+
```
14
+
15
+
This runs your Signal with sample event data and displays the output.
16
+
17
+
### What It Does
18
+
19
+
1. Creates a sample `SignalEvent` matching your Signal's filters
20
+
2. Calls your Signal's `handle()` method
21
+
3. Displays output, logs, and any errors
22
+
4. Shows execution time
23
+
24
+
### Example Output
25
+
26
+
```
27
+
Testing Signal: App\Signals\NewPostSignal
28
+
29
+
Creating sample commit event for collection: app.bsky.feed.post
30
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
31
+
32
+
Event Details:
33
+
DID: did:plc:test123
34
+
Collection: app.bsky.feed.post
35
+
Operation: create
36
+
Text: Sample post for testing
37
+
38
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
39
+
40
+
Processing event...
41
+
โ Signal processed successfully
42
+
43
+
Execution time: 12ms
44
+
```
45
+
46
+
### Limitations
47
+
48
+
- Uses sample data (not real events)
49
+
- Doesn't test filtering logic comprehensively
50
+
- Can't test queue behavior
51
+
- Limited to basic scenarios
52
+
53
+
For comprehensive testing, write automated tests.
54
+
55
+
## Unit Testing
56
+
57
+
Test your Signals in isolation.
58
+
59
+
### Basic Test Structure
60
+
61
+
```php
62
+
<?php
63
+
64
+
namespace Tests\Unit\Signals;
65
+
66
+
use App\Signals\NewPostSignal;
67
+
use SocialDept\AtpSignals\Events\CommitEvent;
68
+
use SocialDept\AtpSignals\Events\SignalEvent;
69
+
use Tests\TestCase;
70
+
71
+
class NewPostSignalTest extends TestCase
72
+
{
73
+
/** @test */
74
+
public function it_handles_new_posts()
75
+
{
76
+
$signal = new NewPostSignal();
77
+
78
+
$event = new SignalEvent(
79
+
did: 'did:plc:test123',
80
+
timeUs: time() * 1000000,
81
+
kind: 'commit',
82
+
commit: new CommitEvent(
83
+
rev: 'test',
84
+
operation: 'create',
85
+
collection: 'app.bsky.feed.post',
86
+
rkey: 'test123',
87
+
record: (object) [
88
+
'text' => 'Hello World!',
89
+
'createdAt' => now()->toIso8601String(),
90
+
],
91
+
),
92
+
);
93
+
94
+
$signal->handle($event);
95
+
96
+
// Assert expected behavior
97
+
$this->assertDatabaseHas('posts', [
98
+
'text' => 'Hello World!',
99
+
]);
100
+
}
101
+
}
102
+
```
103
+
104
+
### Testing Event Types
105
+
106
+
Verify your Signal listens for correct event types:
107
+
108
+
```php
109
+
/** @test */
110
+
public function it_listens_for_commit_events()
111
+
{
112
+
$signal = new NewPostSignal();
113
+
114
+
$eventTypes = $signal->eventTypes();
115
+
116
+
$this->assertContains('commit', $eventTypes);
117
+
}
118
+
```
119
+
120
+
### Testing Filters
121
+
122
+
Verify collection filtering:
123
+
124
+
```php
125
+
/** @test */
126
+
public function it_filters_to_posts_only()
127
+
{
128
+
$signal = new NewPostSignal();
129
+
130
+
$collections = $signal->collections();
131
+
132
+
$this->assertEquals(['app.bsky.feed.post'], $collections);
133
+
}
134
+
```
135
+
136
+
### Testing Operation Filtering
137
+
138
+
```php
139
+
/** @test */
140
+
public function it_only_handles_creates()
141
+
{
142
+
$signal = new NewPostSignal();
143
+
144
+
$operations = $signal->operations();
145
+
146
+
$this->assertEquals([SignalCommitOperation::Create], $operations);
147
+
}
148
+
```
149
+
150
+
### Testing Custom Filtering
151
+
152
+
```php
153
+
/** @test */
154
+
public function it_filters_posts_with_images()
155
+
{
156
+
$signal = new ImagePostSignal();
157
+
158
+
// Event with image
159
+
$eventWithImage = $this->createEvent([
160
+
'text' => 'Check this out!',
161
+
'embed' => (object) ['type' => 'image'],
162
+
]);
163
+
164
+
$this->assertTrue($signal->shouldHandle($eventWithImage));
165
+
166
+
// Event without image
167
+
$eventWithoutImage = $this->createEvent([
168
+
'text' => 'Just text',
169
+
]);
170
+
171
+
$this->assertFalse($signal->shouldHandle($eventWithoutImage));
172
+
}
173
+
```
174
+
175
+
## Feature Testing
176
+
177
+
Test Signals in the context of your application.
178
+
179
+
### Test with Database
180
+
181
+
```php
182
+
<?php
183
+
184
+
namespace Tests\Feature\Signals;
185
+
186
+
use App\Models\Post;
187
+
use App\Signals\StorePostSignal;
188
+
use Illuminate\Foundation\Testing\RefreshDatabase;
189
+
use SocialDept\AtpSignals\Events\CommitEvent;
190
+
use SocialDept\AtpSignals\Events\SignalEvent;
191
+
use Tests\TestCase;
192
+
193
+
class StorePostSignalTest extends TestCase
194
+
{
195
+
use RefreshDatabase;
196
+
197
+
/** @test */
198
+
public function it_stores_posts_in_database()
199
+
{
200
+
$signal = new StorePostSignal();
201
+
202
+
$event = new SignalEvent(
203
+
did: 'did:plc:test123',
204
+
timeUs: time() * 1000000,
205
+
kind: 'commit',
206
+
commit: new CommitEvent(
207
+
rev: 'abc',
208
+
operation: 'create',
209
+
collection: 'app.bsky.feed.post',
210
+
rkey: 'test',
211
+
record: (object) [
212
+
'text' => 'Test post',
213
+
'createdAt' => now()->toIso8601String(),
214
+
],
215
+
),
216
+
);
217
+
218
+
$signal->handle($event);
219
+
220
+
$this->assertDatabaseHas('posts', [
221
+
'did' => 'did:plc:test123',
222
+
'text' => 'Test post',
223
+
]);
224
+
}
225
+
226
+
/** @test */
227
+
public function it_updates_existing_posts()
228
+
{
229
+
Post::create([
230
+
'did' => 'did:plc:test123',
231
+
'rkey' => 'test',
232
+
'text' => 'Old text',
233
+
]);
234
+
235
+
$signal = new StorePostSignal();
236
+
237
+
$event = $this->createUpdateEvent([
238
+
'text' => 'New text',
239
+
]);
240
+
241
+
$signal->handle($event);
242
+
243
+
$this->assertDatabaseHas('posts', [
244
+
'did' => 'did:plc:test123',
245
+
'text' => 'New text',
246
+
]);
247
+
248
+
$this->assertEquals(1, Post::count());
249
+
}
250
+
251
+
/** @test */
252
+
public function it_deletes_posts()
253
+
{
254
+
Post::create([
255
+
'did' => 'did:plc:test123',
256
+
'rkey' => 'test',
257
+
'text' => 'Test post',
258
+
]);
259
+
260
+
$signal = new StorePostSignal();
261
+
262
+
$event = $this->createDeleteEvent();
263
+
264
+
$signal->handle($event);
265
+
266
+
$this->assertDatabaseMissing('posts', [
267
+
'did' => 'did:plc:test123',
268
+
'rkey' => 'test',
269
+
]);
270
+
}
271
+
}
272
+
```
273
+
274
+
### Test with External APIs
275
+
276
+
```php
277
+
use Illuminate\Support\Facades\Http;
278
+
279
+
/** @test */
280
+
public function it_sends_notifications()
281
+
{
282
+
Http::fake();
283
+
284
+
$signal = new NotificationSignal();
285
+
286
+
$event = $this->createEvent();
287
+
288
+
$signal->handle($event);
289
+
290
+
Http::assertSent(function ($request) {
291
+
return $request->url() === 'https://api.example.com/notify' &&
292
+
$request['text'] === 'New post created';
293
+
});
294
+
}
295
+
```
296
+
297
+
## Testing Queued Signals
298
+
299
+
Test Signals that use queues.
300
+
301
+
### Test Queue Dispatch
302
+
303
+
```php
304
+
use Illuminate\Support\Facades\Queue;
305
+
306
+
/** @test */
307
+
public function it_queues_events()
308
+
{
309
+
Queue::fake();
310
+
311
+
$signal = new QueuedSignal();
312
+
313
+
$this->assertTrue($signal->shouldQueue());
314
+
315
+
// In production, this would queue
316
+
// For testing, we verify the intent
317
+
}
318
+
```
319
+
320
+
### Test with Sync Queue
321
+
322
+
Process queued jobs synchronously in tests:
323
+
324
+
```php
325
+
/** @test */
326
+
public function it_processes_queued_events()
327
+
{
328
+
// Use sync queue for immediate processing
329
+
config(['queue.default' => 'sync']);
330
+
331
+
$signal = new QueuedSignal();
332
+
333
+
$event = $this->createEvent();
334
+
335
+
$signal->handle($event);
336
+
337
+
// Assert side effects happened
338
+
$this->assertDatabaseHas('posts', [...]);
339
+
}
340
+
```
341
+
342
+
### Test Queue Configuration
343
+
344
+
```php
345
+
/** @test */
346
+
public function it_uses_high_priority_queue()
347
+
{
348
+
$signal = new HighPrioritySignal();
349
+
350
+
$this->assertTrue($signal->shouldQueue());
351
+
$this->assertEquals('high-priority', $signal->queue());
352
+
}
353
+
354
+
/** @test */
355
+
public function it_uses_redis_connection()
356
+
{
357
+
$signal = new RedisQueueSignal();
358
+
359
+
$this->assertEquals('redis', $signal->queueConnection());
360
+
}
361
+
```
362
+
363
+
## Testing Failure Handling
364
+
365
+
Test how your Signal handles errors.
366
+
367
+
### Test Failed Method
368
+
369
+
```php
370
+
use Illuminate\Support\Facades\Log;
371
+
372
+
/** @test */
373
+
public function it_logs_failures()
374
+
{
375
+
Log::spy();
376
+
377
+
$signal = new FailureHandlingSignal();
378
+
379
+
$event = $this->createEvent();
380
+
$exception = new \Exception('Something went wrong');
381
+
382
+
$signal->failed($event, $exception);
383
+
384
+
Log::shouldHaveReceived('error')
385
+
->with('Signal failed', \Mockery::any());
386
+
}
387
+
```
388
+
389
+
### Test Exception Handling
390
+
391
+
```php
392
+
/** @test */
393
+
public function it_handles_invalid_data_gracefully()
394
+
{
395
+
$signal = new RobustSignal();
396
+
397
+
$event = new SignalEvent(
398
+
did: 'did:plc:test',
399
+
timeUs: time() * 1000000,
400
+
kind: 'commit',
401
+
commit: new CommitEvent(
402
+
rev: 'test',
403
+
operation: 'create',
404
+
collection: 'app.bsky.feed.post',
405
+
rkey: 'test',
406
+
record: (object) [], // Missing required fields
407
+
),
408
+
);
409
+
410
+
// Should not throw
411
+
$signal->handle($event);
412
+
413
+
// Should handle gracefully (e.g., log and skip)
414
+
$this->assertDatabaseCount('posts', 0);
415
+
}
416
+
```
417
+
418
+
## Test Helpers
419
+
420
+
Create reusable helpers for common test scenarios.
421
+
422
+
### Event Factory Helper
423
+
424
+
```php
425
+
trait CreatesSignalEvents
426
+
{
427
+
protected function createCommitEvent(array $overrides = []): SignalEvent
428
+
{
429
+
$defaults = [
430
+
'did' => 'did:plc:test123',
431
+
'timeUs' => time() * 1000000,
432
+
'kind' => 'commit',
433
+
'commit' => new CommitEvent(
434
+
rev: 'test',
435
+
operation: $overrides['operation'] ?? 'create',
436
+
collection: $overrides['collection'] ?? 'app.bsky.feed.post',
437
+
rkey: $overrides['rkey'] ?? 'test',
438
+
record: (object) array_merge([
439
+
'text' => 'Test post',
440
+
'createdAt' => now()->toIso8601String(),
441
+
], $overrides['record'] ?? []),
442
+
),
443
+
];
444
+
445
+
return new SignalEvent(...$defaults);
446
+
}
447
+
448
+
protected function createPostEvent(array $record = []): SignalEvent
449
+
{
450
+
return $this->createCommitEvent([
451
+
'collection' => 'app.bsky.feed.post',
452
+
'record' => $record,
453
+
]);
454
+
}
455
+
456
+
protected function createLikeEvent(array $record = []): SignalEvent
457
+
{
458
+
return $this->createCommitEvent([
459
+
'collection' => 'app.bsky.feed.like',
460
+
'record' => array_merge([
461
+
'subject' => (object) [
462
+
'uri' => 'at://did:plc:test/app.bsky.feed.post/test',
463
+
'cid' => 'bafytest',
464
+
],
465
+
'createdAt' => now()->toIso8601String(),
466
+
], $record),
467
+
]);
468
+
}
469
+
470
+
protected function createFollowEvent(array $record = []): SignalEvent
471
+
{
472
+
return $this->createCommitEvent([
473
+
'collection' => 'app.bsky.graph.follow',
474
+
'record' => array_merge([
475
+
'subject' => 'did:plc:target',
476
+
'createdAt' => now()->toIso8601String(),
477
+
], $record),
478
+
]);
479
+
}
480
+
}
481
+
```
482
+
483
+
Use in tests:
484
+
485
+
```php
486
+
class MySignalTest extends TestCase
487
+
{
488
+
use CreatesSignalEvents;
489
+
490
+
/** @test */
491
+
public function it_handles_posts()
492
+
{
493
+
$event = $this->createPostEvent([
494
+
'text' => 'Custom text',
495
+
]);
496
+
497
+
// Test with event
498
+
}
499
+
}
500
+
```
501
+
502
+
### Signal Factory Helper
503
+
504
+
```php
505
+
trait CreatesSignals
506
+
{
507
+
protected function createSignal(string $class, array $config = [])
508
+
{
509
+
$signal = new $class();
510
+
511
+
// Override configuration for testing
512
+
foreach ($config as $method => $value) {
513
+
$signal->{$method} = $value;
514
+
}
515
+
516
+
return $signal;
517
+
}
518
+
}
519
+
```
520
+
521
+
## Testing Best Practices
522
+
523
+
### Use Descriptive Test Names
524
+
525
+
```php
526
+
// Good
527
+
/** @test */
528
+
public function it_stores_posts_with_valid_data()
529
+
530
+
/** @test */
531
+
public function it_skips_posts_without_text()
532
+
533
+
/** @test */
534
+
public function it_handles_duplicate_posts_gracefully()
535
+
536
+
// Less descriptive
537
+
/** @test */
538
+
public function test_handle()
539
+
```
540
+
541
+
### Test Edge Cases
542
+
543
+
```php
544
+
/** @test */
545
+
public function it_handles_empty_text()
546
+
{
547
+
$event = $this->createPostEvent(['text' => '']);
548
+
// Test behavior
549
+
}
550
+
551
+
/** @test */
552
+
public function it_handles_very_long_text()
553
+
{
554
+
$event = $this->createPostEvent(['text' => str_repeat('a', 10000)]);
555
+
// Test behavior
556
+
}
557
+
558
+
/** @test */
559
+
public function it_handles_missing_created_at()
560
+
{
561
+
$event = $this->createPostEvent(['createdAt' => null]);
562
+
// Test behavior
563
+
}
564
+
```
565
+
566
+
### Test All Operations
567
+
568
+
```php
569
+
/** @test */
570
+
public function it_handles_creates()
571
+
{
572
+
$event = $this->createEvent(['operation' => 'create']);
573
+
// Test
574
+
}
575
+
576
+
/** @test */
577
+
public function it_handles_updates()
578
+
{
579
+
$event = $this->createEvent(['operation' => 'update']);
580
+
// Test
581
+
}
582
+
583
+
/** @test */
584
+
public function it_handles_deletes()
585
+
{
586
+
$event = $this->createEvent(['operation' => 'delete']);
587
+
// Test
588
+
}
589
+
```
590
+
591
+
### Mock External Dependencies
592
+
593
+
```php
594
+
/** @test */
595
+
public function it_calls_external_api()
596
+
{
597
+
Http::fake([
598
+
'api.example.com/*' => Http::response(['success' => true]),
599
+
]);
600
+
601
+
$signal = new ApiSignal();
602
+
$event = $this->createEvent();
603
+
604
+
$signal->handle($event);
605
+
606
+
Http::assertSent(function ($request) {
607
+
return $request->url() === 'https://api.example.com/endpoint';
608
+
});
609
+
}
610
+
```
611
+
612
+
### Test Database State
613
+
614
+
```php
615
+
use Illuminate\Foundation\Testing\RefreshDatabase;
616
+
617
+
class DatabaseSignalTest extends TestCase
618
+
{
619
+
use RefreshDatabase;
620
+
621
+
/** @test */
622
+
public function it_creates_records()
623
+
{
624
+
// Fresh database for each test
625
+
}
626
+
}
627
+
```
628
+
629
+
## Continuous Integration
630
+
631
+
Run tests automatically on every commit.
632
+
633
+
### GitHub Actions
634
+
635
+
```yaml
636
+
# .github/workflows/tests.yml
637
+
name: Tests
638
+
639
+
on: [push, pull_request]
640
+
641
+
jobs:
642
+
test:
643
+
runs-on: ubuntu-latest
644
+
645
+
steps:
646
+
- uses: actions/checkout@v2
647
+
648
+
- name: Setup PHP
649
+
uses: shivammathur/setup-php@v2
650
+
with:
651
+
php-version: 8.2
652
+
653
+
- name: Install Dependencies
654
+
run: composer install
655
+
656
+
- name: Run Tests
657
+
run: php artisan test
658
+
```
659
+
660
+
### Run Signal-Specific Tests
661
+
662
+
```bash
663
+
# Run all Signal tests
664
+
php artisan test --testsuite=Signals
665
+
666
+
# Run specific test file
667
+
php artisan test tests/Unit/Signals/NewPostSignalTest.php
668
+
669
+
# Run with coverage
670
+
php artisan test --coverage
671
+
```
672
+
673
+
## Debugging Tests
674
+
675
+
### Enable Debug Output
676
+
677
+
```php
678
+
/** @test */
679
+
public function it_processes_events()
680
+
{
681
+
$signal = new NewPostSignal();
682
+
683
+
$event = $this->createEvent();
684
+
685
+
dump($event); // Output event data
686
+
687
+
$signal->handle($event);
688
+
689
+
dump(Post::all()); // Output results
690
+
}
691
+
```
692
+
693
+
### Use dd() to Stop Execution
694
+
695
+
```php
696
+
/** @test */
697
+
public function it_processes_events()
698
+
{
699
+
$event = $this->createEvent();
700
+
701
+
dd($event); // Dump and die
702
+
703
+
// This won't run
704
+
}
705
+
```
706
+
707
+
### Check Logs
708
+
709
+
```php
710
+
/** @test */
711
+
public function it_logs_processing()
712
+
{
713
+
Log::spy();
714
+
715
+
$signal = new LoggingSignal();
716
+
$event = $this->createEvent();
717
+
718
+
$signal->handle($event);
719
+
720
+
Log::shouldHaveReceived('info')->once();
721
+
}
722
+
```
723
+
724
+
## Next Steps
725
+
726
+
- **[See real-world examples โ](examples.md)** - Learn from production test patterns
727
+
- **[Review queue integration โ](queues.md)** - Test queued Signals
728
+
- **[Review signals documentation โ](signals.md)** - Understand Signal structure
header.png
header.png
This is a binary file and will not be displayed.
+141
src/Binary/Reader.php
+141
src/Binary/Reader.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Binary;
6
+
7
+
use RuntimeException;
8
+
9
+
/**
10
+
* Binary data reader with position tracking.
11
+
*
12
+
* Provides stream-like interface for reading from binary strings.
13
+
*/
14
+
class Reader
15
+
{
16
+
private int $position = 0;
17
+
18
+
public function __construct(
19
+
private readonly string $data,
20
+
) {
21
+
}
22
+
23
+
/**
24
+
* Get current position in the data.
25
+
*/
26
+
public function getPosition(): int
27
+
{
28
+
return $this->position;
29
+
}
30
+
31
+
/**
32
+
* Get total length of data.
33
+
*/
34
+
public function getLength(): int
35
+
{
36
+
return strlen($this->data);
37
+
}
38
+
39
+
/**
40
+
* Check if there's more data to read.
41
+
*/
42
+
public function hasMore(): bool
43
+
{
44
+
return $this->position < strlen($this->data);
45
+
}
46
+
47
+
/**
48
+
* Get remaining bytes count.
49
+
*/
50
+
public function remaining(): int
51
+
{
52
+
return strlen($this->data) - $this->position;
53
+
}
54
+
55
+
/**
56
+
* Peek at the next byte without advancing position.
57
+
*
58
+
* @throws RuntimeException If no more data available
59
+
*/
60
+
public function peek(): int
61
+
{
62
+
if (! $this->hasMore()) {
63
+
throw new RuntimeException('Unexpected end of data');
64
+
}
65
+
66
+
return ord($this->data[$this->position]);
67
+
}
68
+
69
+
/**
70
+
* Read a single byte and advance position.
71
+
*
72
+
* @throws RuntimeException If no more data available
73
+
*/
74
+
public function readByte(): int
75
+
{
76
+
$byte = $this->peek();
77
+
$this->position++;
78
+
79
+
return $byte;
80
+
}
81
+
82
+
/**
83
+
* Read exactly N bytes and advance position.
84
+
*
85
+
* @throws RuntimeException If not enough data available
86
+
*/
87
+
public function readBytes(int $length): string
88
+
{
89
+
if ($this->remaining() < $length) {
90
+
throw new RuntimeException("Cannot read {$length} bytes, only {$this->remaining()} remaining");
91
+
}
92
+
93
+
$bytes = substr($this->data, $this->position, $length);
94
+
$this->position += $length;
95
+
96
+
return $bytes;
97
+
}
98
+
99
+
/**
100
+
* Read a varint (variable-length integer).
101
+
*
102
+
* @throws RuntimeException If varint is malformed
103
+
*/
104
+
public function readVarint(): int
105
+
{
106
+
return Varint::decode($this->data, $this->position);
107
+
}
108
+
109
+
/**
110
+
* Get all remaining data without advancing position.
111
+
*/
112
+
public function peekRemaining(): string
113
+
{
114
+
return substr($this->data, $this->position);
115
+
}
116
+
117
+
/**
118
+
* Read all remaining data and advance position to end.
119
+
*/
120
+
public function readRemaining(): string
121
+
{
122
+
$remaining = $this->peekRemaining();
123
+
$this->position = strlen($this->data);
124
+
125
+
return $remaining;
126
+
}
127
+
128
+
/**
129
+
* Skip N bytes forward.
130
+
*
131
+
* @throws RuntimeException If trying to skip past end
132
+
*/
133
+
public function skip(int $bytes): void
134
+
{
135
+
if ($this->remaining() < $bytes) {
136
+
throw new RuntimeException("Cannot skip {$bytes} bytes, only {$this->remaining()} remaining");
137
+
}
138
+
139
+
$this->position += $bytes;
140
+
}
141
+
}
+68
src/Binary/Varint.php
+68
src/Binary/Varint.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Binary;
6
+
7
+
use RuntimeException;
8
+
9
+
/**
10
+
* Varint (Variable-Length Integer) decoder for unsigned integers.
11
+
*
12
+
* Used in CAR format to encode block lengths. Implements the same
13
+
* varint encoding used in Protocol Buffers and other binary formats.
14
+
*/
15
+
class Varint
16
+
{
17
+
/**
18
+
* Decode a varint from the beginning of the data.
19
+
*
20
+
* @param string $data Binary data containing varint
21
+
* @param int $offset Starting position (will be updated to position after varint)
22
+
* @return int Decoded unsigned integer
23
+
* @throws RuntimeException If varint is malformed or data ends unexpectedly
24
+
*/
25
+
public static function decode(string $data, int &$offset = 0): int
26
+
{
27
+
$result = 0;
28
+
$shift = 0;
29
+
$length = strlen($data);
30
+
31
+
while ($offset < $length) {
32
+
$byte = ord($data[$offset]);
33
+
$offset++;
34
+
35
+
// Take lower 7 bits and shift into result
36
+
$result |= ($byte & 0x7F) << $shift;
37
+
38
+
// If MSB is 0, we're done
39
+
if (($byte & 0x80) === 0) {
40
+
return $result;
41
+
}
42
+
43
+
$shift += 7;
44
+
45
+
// Prevent overflow (64-bit max)
46
+
if ($shift > 63) {
47
+
throw new RuntimeException('Varint too long (max 64 bits)');
48
+
}
49
+
}
50
+
51
+
throw new RuntimeException('Unexpected end of varint data');
52
+
}
53
+
54
+
/**
55
+
* Read a varint and return both the value and remaining data.
56
+
*
57
+
* @param string $data Binary data starting with varint
58
+
* @return array{0: int, 1: string} [decoded value, remaining data]
59
+
*/
60
+
public static function decodeFirst(string $data): array
61
+
{
62
+
$offset = 0;
63
+
$value = self::decode($data, $offset);
64
+
$remainder = substr($data, $offset);
65
+
66
+
return [$value, $remainder];
67
+
}
68
+
}
+132
src/CAR/BlockReader.php
+132
src/CAR/BlockReader.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\CAR;
6
+
7
+
use Generator;
8
+
use SocialDept\AtpSignals\Binary\Reader;
9
+
use SocialDept\AtpSignals\Core\CID;
10
+
11
+
/**
12
+
* CAR (Content Addressable aRchive) block reader.
13
+
*
14
+
* Reads blocks from CAR format data used in AT Protocol commits.
15
+
*/
16
+
class BlockReader
17
+
{
18
+
private Reader $reader;
19
+
20
+
public function __construct(string $data)
21
+
{
22
+
$this->reader = new Reader($data);
23
+
}
24
+
25
+
/**
26
+
* Read all blocks from CAR data.
27
+
*
28
+
* Yields [CID, block data] pairs.
29
+
*
30
+
* @return Generator<array{0: CID, 1: string}>
31
+
*/
32
+
public function blocks(): Generator
33
+
{
34
+
// Skip CAR header (we don't need it for Firehose processing)
35
+
$this->skipHeader();
36
+
37
+
// Read blocks until end of data
38
+
while ($this->reader->hasMore()) {
39
+
$block = $this->readBlock();
40
+
if ($block !== null) {
41
+
yield $block;
42
+
}
43
+
}
44
+
}
45
+
46
+
/**
47
+
* Skip CAR header.
48
+
*/
49
+
private function skipHeader(): void
50
+
{
51
+
if (! $this->reader->hasMore()) {
52
+
return;
53
+
}
54
+
55
+
// Read header length (varint)
56
+
$headerLength = $this->reader->readVarint();
57
+
58
+
// Skip header data
59
+
$this->reader->skip($headerLength);
60
+
}
61
+
62
+
/**
63
+
* Read a single block.
64
+
*
65
+
* @return array{0: CID, 1: string}|null [CID, block data] or null if no more blocks
66
+
*/
67
+
private function readBlock(): ?array
68
+
{
69
+
if (! $this->reader->hasMore()) {
70
+
return null;
71
+
}
72
+
73
+
// Read block length (varint) - this is the total length of CID + data
74
+
$blockLength = $this->reader->readVarint();
75
+
76
+
if ($blockLength === 0) {
77
+
return null;
78
+
}
79
+
80
+
// Read entire block data
81
+
$blockData = $this->reader->readBytes($blockLength);
82
+
83
+
// Parse CID from the beginning of block data
84
+
// CIDs in CAR blocks are self-delimiting (no separate length prefix)
85
+
// We need to parse the CID to find out its length
86
+
$cidReader = new Reader($blockData);
87
+
88
+
// Read CID version
89
+
$version = $cidReader->readVarint();
90
+
91
+
if ($version === 0x12) {
92
+
// CIDv0 - multihash only (starting with 0x12 for SHA-256)
93
+
$hashLength = $cidReader->readVarint();
94
+
$cidReader->readBytes($hashLength); // Skip hash bytes
95
+
} elseif ($version === 1) {
96
+
// CIDv1 - version + codec + multihash
97
+
$codec = $cidReader->readVarint();
98
+
$hashType = $cidReader->readVarint();
99
+
$hashLength = $cidReader->readVarint();
100
+
$cidReader->readBytes($hashLength); // Skip hash bytes
101
+
} else {
102
+
throw new \RuntimeException("Unsupported CID version in CAR block: {$version}");
103
+
}
104
+
105
+
// Now we know the CID length
106
+
$cidLength = $cidReader->getPosition();
107
+
$cidBytes = substr($blockData, 0, $cidLength);
108
+
$cid = CID::fromBinary($cidBytes);
109
+
110
+
// Remaining data is the block content
111
+
$content = substr($blockData, $cidLength);
112
+
113
+
return [$cid, $content];
114
+
}
115
+
116
+
/**
117
+
* Get all blocks as an associative array.
118
+
*
119
+
* @return array<string, string> Map of CID string => block data
120
+
*/
121
+
public function getBlockMap(): array
122
+
{
123
+
$blocks = [];
124
+
125
+
foreach ($this->blocks() as [$cid, $data]) {
126
+
$cidString = $cid->toString();
127
+
$blocks[$cidString] = $data;
128
+
}
129
+
130
+
return $blocks;
131
+
}
132
+
}
+133
src/CAR/RecordExtractor.php
+133
src/CAR/RecordExtractor.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\CAR;
6
+
7
+
use Generator;
8
+
use SocialDept\AtpSignals\Core\CBOR;
9
+
use SocialDept\AtpSignals\Core\CID;
10
+
11
+
/**
12
+
* Extract records from AT Protocol MST (Merkle Search Tree) blocks.
13
+
*
14
+
* Walks MST structure to extract collection/rkey records with their values.
15
+
*/
16
+
class RecordExtractor
17
+
{
18
+
/**
19
+
* @param array<string, string> $blocks Map of CID string => block data
20
+
*/
21
+
public function __construct(
22
+
private readonly array $blocks,
23
+
private readonly string $did,
24
+
) {
25
+
}
26
+
27
+
/**
28
+
* Extract all records from blocks.
29
+
*
30
+
* Yields records in format: "collection/rkey" => record data
31
+
*
32
+
* @return Generator<string, array>
33
+
*/
34
+
public function extractRecords(CID $rootCid): Generator
35
+
{
36
+
yield from $this->walkTree($rootCid, '');
37
+
}
38
+
39
+
/**
40
+
* Recursively walk MST tree.
41
+
*
42
+
* @param CID $cid Current node CID
43
+
* @param string $prefix Path prefix accumulated from parent nodes
44
+
* @return Generator<string, array>
45
+
*/
46
+
private function walkTree(CID $cid, string $prefix): Generator
47
+
{
48
+
$cidStr = $cid->toString();
49
+
50
+
// Get block data
51
+
if (! isset($this->blocks[$cidStr])) {
52
+
// Block not found - might be a pruned tree, skip it
53
+
return;
54
+
}
55
+
56
+
$blockData = $this->blocks[$cidStr];
57
+
58
+
// Decode CBOR block
59
+
$node = CBOR::decode($blockData);
60
+
61
+
if (! is_array($node)) {
62
+
return;
63
+
}
64
+
65
+
// Process left subtree if exists
66
+
if (isset($node['l']) && $node['l'] instanceof CID) {
67
+
yield from $this->walkTree($node['l'], $prefix);
68
+
}
69
+
70
+
// Process entries
71
+
if (isset($node['e']) && is_array($node['e'])) {
72
+
foreach ($node['e'] as $entry) {
73
+
if (! is_array($entry)) {
74
+
continue;
75
+
}
76
+
77
+
// Build full key from prefix + entry key
78
+
$entryPrefix = $entry['p'] ?? 0;
79
+
$keyPart = $entry['k'] ?? '';
80
+
$fullKey = substr($prefix, 0, $entryPrefix) . $keyPart;
81
+
82
+
// If entry has a tree link, walk it
83
+
if (isset($entry['t']) && $entry['t'] instanceof CID) {
84
+
yield from $this->walkTree($entry['t'], $fullKey);
85
+
}
86
+
87
+
// If entry has a value (record), yield it
88
+
if (isset($entry['v']) && $entry['v'] instanceof CID) {
89
+
$recordCid = $entry['v'];
90
+
$record = $this->getRecord($recordCid);
91
+
92
+
if ($record !== null) {
93
+
// Parse collection/rkey from key
94
+
$parts = explode('/', $fullKey, 2);
95
+
if (count($parts) === 2) {
96
+
[$collection, $rkey] = $parts;
97
+
$path = "{$collection}/{$rkey}";
98
+
99
+
yield $path => [
100
+
'uri' => "at://{$this->did}/{$path}",
101
+
'cid' => $recordCid->toString(),
102
+
'value' => $record,
103
+
];
104
+
} else {
105
+
// Debug: log when key format doesn't match expected pattern
106
+
\Illuminate\Support\Facades\Log::debug('Signal: MST key parse failed', [
107
+
'fullKey' => $fullKey,
108
+
'parts' => $parts,
109
+
'did' => $this->did,
110
+
]);
111
+
}
112
+
}
113
+
}
114
+
}
115
+
}
116
+
}
117
+
118
+
/**
119
+
* Get record data from block.
120
+
*/
121
+
private function getRecord(CID $cid): ?array
122
+
{
123
+
$cidStr = $cid->toString();
124
+
125
+
if (! isset($this->blocks[$cidStr])) {
126
+
return null;
127
+
}
128
+
129
+
$data = CBOR::decode($this->blocks[$cidStr]);
130
+
131
+
return is_array($data) ? $data : null;
132
+
}
133
+
}
+286
src/CBOR/Decoder.php
+286
src/CBOR/Decoder.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\CBOR;
6
+
7
+
use RuntimeException;
8
+
use SocialDept\AtpSignals\Binary\Reader;
9
+
use SocialDept\AtpSignals\Core\CID;
10
+
11
+
/**
12
+
* CBOR (Concise Binary Object Representation) decoder.
13
+
*
14
+
* Implements RFC 8949 CBOR with DAG-CBOR extensions for IPLD.
15
+
* Supports tag 42 for CID links.
16
+
*/
17
+
class Decoder
18
+
{
19
+
private const MAJOR_TYPE_UNSIGNED = 0;
20
+
21
+
private const MAJOR_TYPE_NEGATIVE = 1;
22
+
23
+
private const MAJOR_TYPE_BYTES = 2;
24
+
25
+
private const MAJOR_TYPE_TEXT = 3;
26
+
27
+
private const MAJOR_TYPE_ARRAY = 4;
28
+
29
+
private const MAJOR_TYPE_MAP = 5;
30
+
31
+
private const MAJOR_TYPE_TAG = 6;
32
+
33
+
private const MAJOR_TYPE_SPECIAL = 7;
34
+
35
+
private const TAG_CID = 42;
36
+
37
+
private Reader $reader;
38
+
39
+
public function __construct(string $data)
40
+
{
41
+
$this->reader = new Reader($data);
42
+
}
43
+
44
+
/**
45
+
* Decode the next CBOR item.
46
+
*
47
+
* @return mixed Decoded value
48
+
*
49
+
* @throws RuntimeException If data is malformed
50
+
*/
51
+
public function decode(): mixed
52
+
{
53
+
if (! $this->reader->hasMore()) {
54
+
throw new RuntimeException('Unexpected end of CBOR data');
55
+
}
56
+
57
+
$initialByte = $this->reader->readByte();
58
+
$majorType = $initialByte >> 5;
59
+
$additionalInfo = $initialByte & 0x1F;
60
+
61
+
return match ($majorType) {
62
+
self::MAJOR_TYPE_UNSIGNED => $this->decodeUnsigned($additionalInfo),
63
+
self::MAJOR_TYPE_NEGATIVE => $this->decodeNegative($additionalInfo),
64
+
self::MAJOR_TYPE_BYTES => $this->decodeBytes($additionalInfo),
65
+
self::MAJOR_TYPE_TEXT => $this->decodeText($additionalInfo),
66
+
self::MAJOR_TYPE_ARRAY => $this->decodeArray($additionalInfo),
67
+
self::MAJOR_TYPE_MAP => $this->decodeMap($additionalInfo),
68
+
self::MAJOR_TYPE_TAG => $this->decodeTag($additionalInfo),
69
+
self::MAJOR_TYPE_SPECIAL => $this->decodeSpecial($additionalInfo),
70
+
default => throw new RuntimeException("Unknown major type: {$majorType}"),
71
+
};
72
+
}
73
+
74
+
/**
75
+
* Check if there's more data to decode.
76
+
*/
77
+
public function hasMore(): bool
78
+
{
79
+
return $this->reader->hasMore();
80
+
}
81
+
82
+
/**
83
+
* Get current position.
84
+
*/
85
+
public function getPosition(): int
86
+
{
87
+
return $this->reader->getPosition();
88
+
}
89
+
90
+
/**
91
+
* Decode unsigned integer.
92
+
*/
93
+
private function decodeUnsigned(int $additionalInfo): int
94
+
{
95
+
return $this->decodeLength($additionalInfo);
96
+
}
97
+
98
+
/**
99
+
* Decode negative integer.
100
+
*/
101
+
private function decodeNegative(int $additionalInfo): int
102
+
{
103
+
$value = $this->decodeLength($additionalInfo);
104
+
105
+
return -1 - $value;
106
+
}
107
+
108
+
/**
109
+
* Decode byte string.
110
+
*/
111
+
private function decodeBytes(int $additionalInfo): string
112
+
{
113
+
$length = $this->decodeLength($additionalInfo);
114
+
115
+
return $this->reader->readBytes($length);
116
+
}
117
+
118
+
/**
119
+
* Decode text string.
120
+
*/
121
+
private function decodeText(int $additionalInfo): string
122
+
{
123
+
$length = $this->decodeLength($additionalInfo);
124
+
125
+
return $this->reader->readBytes($length);
126
+
}
127
+
128
+
/**
129
+
* Decode array.
130
+
*/
131
+
private function decodeArray(int $additionalInfo): array
132
+
{
133
+
$length = $this->decodeLength($additionalInfo);
134
+
$array = [];
135
+
136
+
for ($i = 0; $i < $length; $i++) {
137
+
$array[] = $this->decode();
138
+
}
139
+
140
+
return $array;
141
+
}
142
+
143
+
/**
144
+
* Decode map (object).
145
+
*/
146
+
private function decodeMap(int $additionalInfo): array
147
+
{
148
+
$length = $this->decodeLength($additionalInfo);
149
+
$map = [];
150
+
151
+
for ($i = 0; $i < $length; $i++) {
152
+
$key = $this->decode();
153
+
$value = $this->decode();
154
+
155
+
if (! is_string($key) && ! is_int($key)) {
156
+
throw new RuntimeException('Map keys must be strings or integers');
157
+
}
158
+
159
+
$map[$key] = $value;
160
+
}
161
+
162
+
return $map;
163
+
}
164
+
165
+
/**
166
+
* Decode tagged value.
167
+
*/
168
+
private function decodeTag(int $additionalInfo): mixed
169
+
{
170
+
$tag = $this->decodeLength($additionalInfo);
171
+
172
+
if ($tag === self::TAG_CID) {
173
+
// Tag 42 = CID link (DAG-CBOR)
174
+
// Next item should be byte string containing CID
175
+
$cidBytes = $this->decode();
176
+
177
+
if (! is_string($cidBytes)) {
178
+
throw new RuntimeException('CID tag must be followed by byte string');
179
+
}
180
+
181
+
// First byte should be 0x00 for CID
182
+
if (ord($cidBytes[0]) !== 0x00) {
183
+
throw new RuntimeException('Invalid CID byte string prefix');
184
+
}
185
+
186
+
return CID::fromBinary(substr($cidBytes, 1));
187
+
}
188
+
189
+
// For other tags, just return the tagged value
190
+
return $this->decode();
191
+
}
192
+
193
+
/**
194
+
* Decode special values (bool, null, floats).
195
+
*/
196
+
private function decodeSpecial(int $additionalInfo): mixed
197
+
{
198
+
return match ($additionalInfo) {
199
+
20 => false,
200
+
21 => true,
201
+
22 => null,
202
+
23 => throw new RuntimeException('Undefined special value'),
203
+
25 => $this->decodeFloat16(), // IEEE 754 Half-Precision (16-bit)
204
+
26 => $this->decodeFloat32(), // IEEE 754 Single-Precision (32-bit)
205
+
27 => $this->decodeFloat64(), // IEEE 754 Double-Precision (64-bit)
206
+
default => throw new RuntimeException("Unsupported special value: {$additionalInfo}"),
207
+
};
208
+
}
209
+
210
+
/**
211
+
* Decode IEEE 754 half-precision float (16-bit).
212
+
*/
213
+
private function decodeFloat16(): float
214
+
{
215
+
$bytes = $this->reader->readBytes(2);
216
+
$bits = unpack('n', $bytes)[1];
217
+
218
+
// Extract sign, exponent, and mantissa
219
+
$sign = ($bits >> 15) & 1;
220
+
$exponent = ($bits >> 10) & 0x1F;
221
+
$mantissa = $bits & 0x3FF;
222
+
223
+
// Handle special cases
224
+
if ($exponent === 0) {
225
+
// Subnormal or zero
226
+
$value = $mantissa / 1024.0 * (2 ** -14);
227
+
} elseif ($exponent === 31) {
228
+
// Infinity or NaN
229
+
return $mantissa === 0 ? ($sign ? -INF : INF) : NAN;
230
+
} else {
231
+
// Normalized value
232
+
$value = (1 + $mantissa / 1024.0) * (2 ** ($exponent - 15));
233
+
}
234
+
235
+
return $sign ? -$value : $value;
236
+
}
237
+
238
+
/**
239
+
* Decode IEEE 754 single-precision float (32-bit).
240
+
*/
241
+
private function decodeFloat32(): float
242
+
{
243
+
$bytes = $this->reader->readBytes(4);
244
+
245
+
return unpack('G', $bytes)[1]; // Big-endian float
246
+
}
247
+
248
+
/**
249
+
* Decode IEEE 754 double-precision float (64-bit).
250
+
*/
251
+
private function decodeFloat64(): float
252
+
{
253
+
$bytes = $this->reader->readBytes(8);
254
+
255
+
return unpack('E', $bytes)[1]; // Big-endian double
256
+
}
257
+
258
+
/**
259
+
* Decode length/value from additional info.
260
+
*/
261
+
private function decodeLength(int $additionalInfo): int
262
+
{
263
+
if ($additionalInfo < 24) {
264
+
return $additionalInfo;
265
+
}
266
+
267
+
return match ($additionalInfo) {
268
+
24 => $this->reader->readByte(),
269
+
25 => unpack('n', $this->reader->readBytes(2))[1],
270
+
26 => unpack('N', $this->reader->readBytes(4))[1],
271
+
27 => $this->readUint64(),
272
+
default => throw new RuntimeException("Invalid additional info: {$additionalInfo}"),
273
+
};
274
+
}
275
+
276
+
/**
277
+
* Read 64-bit unsigned integer.
278
+
*/
279
+
private function readUint64(): int
280
+
{
281
+
$bytes = $this->reader->readBytes(8);
282
+
$unpacked = unpack('J', $bytes)[1];
283
+
284
+
return $unpacked;
285
+
}
286
+
}
+6
-6
src/Commands/ConsumeCommand.php
+6
-6
src/Commands/ConsumeCommand.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Commands;
3
+
namespace SocialDept\AtpSignals\Commands;
4
4
5
5
use BackedEnum;
6
6
use Exception;
7
7
use Illuminate\Console\Command;
8
-
use SocialDept\Signal\Services\FirehoseConsumer;
9
-
use SocialDept\Signal\Services\JetstreamConsumer;
10
-
use SocialDept\Signal\Services\SignalRegistry;
8
+
use SocialDept\AtpSignals\Services\FirehoseConsumer;
9
+
use SocialDept\AtpSignals\Services\JetstreamConsumer;
10
+
use SocialDept\AtpSignals\Services\SignalRegistry;
11
11
12
12
class ConsumeCommand extends Command
13
13
{
···
115
115
if ($this->option('fresh')) {
116
116
$this->info('Starting fresh from the beginning');
117
117
118
-
return null;
118
+
return 0; // Explicitly 0 means "start fresh, no cursor"
119
119
}
120
120
121
121
if ($this->option('cursor')) {
···
127
127
128
128
$this->info('Resuming from stored cursor position');
129
129
130
-
return null;
130
+
return null; // null means "use stored cursor"
131
131
}
132
132
133
133
private function startConsumer(string $mode, ?int $cursor): int
+1
-1
src/Commands/InstallCommand.php
+1
-1
src/Commands/InstallCommand.php
+2
-2
src/Commands/ListSignalsCommand.php
+2
-2
src/Commands/ListSignalsCommand.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Commands;
3
+
namespace SocialDept\AtpSignals\Commands;
4
4
5
5
use Illuminate\Console\Command;
6
6
use Illuminate\Support\Collection;
7
-
use SocialDept\Signal\Services\SignalRegistry;
7
+
use SocialDept\AtpSignals\Services\SignalRegistry;
8
8
9
9
class ListSignalsCommand extends Command
10
10
{
+1
-1
src/Commands/MakeSignalCommand.php
+1
-1
src/Commands/MakeSignalCommand.php
+3
-3
src/Commands/TestSignalCommand.php
+3
-3
src/Commands/TestSignalCommand.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Commands;
3
+
namespace SocialDept\AtpSignals\Commands;
4
4
5
5
use Illuminate\Console\Command;
6
6
use InvalidArgumentException;
7
-
use SocialDept\Signal\Events\CommitEvent;
8
-
use SocialDept\Signal\Events\SignalEvent;
7
+
use SocialDept\AtpSignals\Events\CommitEvent;
8
+
use SocialDept\AtpSignals\Events\SignalEvent;
9
9
10
10
class TestSignalCommand extends Command
11
11
{
+1
-1
src/Contracts/CursorStore.php
+1
-1
src/Contracts/CursorStore.php
+1
-1
src/Contracts/EventContract.php
+1
-1
src/Contracts/EventContract.php
+85
src/Core/CAR.php
+85
src/Core/CAR.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Core;
6
+
7
+
use SocialDept\AtpSignals\CAR\BlockReader;
8
+
9
+
/**
10
+
* CAR (Content Addressable aRchive) facade.
11
+
*
12
+
* Provides static methods for parsing CAR data from AT Protocol Firehose.
13
+
*/
14
+
class CAR
15
+
{
16
+
/**
17
+
* Parse CAR blocks.
18
+
*
19
+
* Returns array of blocks keyed by CID string.
20
+
* The blocks contain raw CBOR data, not decoded.
21
+
*
22
+
* @param string $data Binary CAR data
23
+
* @param string|null $did DID for constructing URIs (not used, kept for compatibility)
24
+
* @return array<string, string> Map of CID string => block data
25
+
*/
26
+
public static function blockMap(string $data, ?string $did = null): array
27
+
{
28
+
// Read all blocks from CAR
29
+
$blockReader = new BlockReader($data);
30
+
31
+
return $blockReader->getBlockMap();
32
+
}
33
+
34
+
/**
35
+
* Extract DID from commit block.
36
+
*/
37
+
private static function extractDidFromBlocks(array $blocks): ?string
38
+
{
39
+
// The first block is typically the commit
40
+
$firstBlock = reset($blocks);
41
+
42
+
if ($firstBlock === false) {
43
+
return null;
44
+
}
45
+
46
+
$decoded = CBOR::decode($firstBlock);
47
+
48
+
if (! is_array($decoded)) {
49
+
return null;
50
+
}
51
+
52
+
return $decoded['did'] ?? null;
53
+
}
54
+
55
+
/**
56
+
* Find MST root CID from blocks.
57
+
*/
58
+
private static function findMstRoot(array $blocks, array $cids): ?CID
59
+
{
60
+
// Try to parse commit block to get data CID
61
+
$firstBlock = reset($blocks);
62
+
63
+
if ($firstBlock === false) {
64
+
return null;
65
+
}
66
+
67
+
$commit = CBOR::decode($firstBlock);
68
+
69
+
if (! is_array($commit)) {
70
+
return null;
71
+
}
72
+
73
+
// MST root is in the 'data' field of commit
74
+
if (isset($commit['data']) && $commit['data'] instanceof CID) {
75
+
return $commit['data'];
76
+
}
77
+
78
+
// Fallback: second block is often the MST root
79
+
if (count($cids) >= 2) {
80
+
return CID::fromString($cids[1]);
81
+
}
82
+
83
+
return null;
84
+
}
85
+
}
+64
src/Core/CBOR.php
+64
src/Core/CBOR.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Core;
6
+
7
+
use SocialDept\AtpSignals\CBOR\Decoder;
8
+
9
+
/**
10
+
* CBOR facade for simple decoding operations.
11
+
*
12
+
* Provides static methods matching the interface needed by FirehoseConsumer.
13
+
*/
14
+
class CBOR
15
+
{
16
+
/**
17
+
* Decode first CBOR item and return remainder.
18
+
*
19
+
* @param string $data Binary CBOR data
20
+
* @return array{0: mixed, 1: string} [decoded value, remaining data]
21
+
*/
22
+
public static function decodeFirst(string $data): array
23
+
{
24
+
$decoder = new Decoder($data);
25
+
$value = $decoder->decode();
26
+
27
+
// Calculate remaining data based on decoder position
28
+
$position = $decoder->getPosition();
29
+
$remainder = substr($data, $position);
30
+
31
+
return [$value, $remainder];
32
+
}
33
+
34
+
/**
35
+
* Decode complete CBOR data.
36
+
*
37
+
* @param string $data Binary CBOR data
38
+
* @return mixed Decoded value
39
+
*/
40
+
public static function decode(string $data): mixed
41
+
{
42
+
$decoder = new Decoder($data);
43
+
44
+
return $decoder->decode();
45
+
}
46
+
47
+
/**
48
+
* Decode all CBOR items from data.
49
+
*
50
+
* @param string $data Binary CBOR data
51
+
* @return array All decoded values
52
+
*/
53
+
public static function decodeAll(string $data): array
54
+
{
55
+
$decoder = new Decoder($data);
56
+
$items = [];
57
+
58
+
while ($decoder->hasMore()) {
59
+
$items[] = $decoder->decode();
60
+
}
61
+
62
+
return $items;
63
+
}
64
+
}
+272
src/Core/CID.php
+272
src/Core/CID.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Core;
6
+
7
+
use RuntimeException;
8
+
use SocialDept\AtpSignals\Binary\Reader;
9
+
use SocialDept\AtpSignals\Binary\Varint;
10
+
11
+
/**
12
+
* Content Identifier (CID) parser for IPLD.
13
+
*
14
+
* Supports CIDv0 (base58btc) and CIDv1 (multibase).
15
+
* Minimal implementation for reading CIDs from CBOR and CAR data.
16
+
*/
17
+
class CID
18
+
{
19
+
private const BASE32_CHARSET = 'abcdefghijklmnopqrstuvwxyz234567';
20
+
private const BASE58BTC_CHARSET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
21
+
22
+
public function __construct(
23
+
public readonly int $version,
24
+
public readonly int $codec,
25
+
public readonly string $hash,
26
+
) {
27
+
}
28
+
29
+
/**
30
+
* Parse CID from binary data.
31
+
*
32
+
* @param string $data Binary CID data
33
+
* @return self
34
+
* @throws RuntimeException If CID is malformed
35
+
*/
36
+
public static function fromBinary(string $data): self
37
+
{
38
+
$reader = new Reader($data);
39
+
40
+
// Read version
41
+
$version = $reader->readVarint();
42
+
43
+
if ($version === 0x12) {
44
+
// CIDv0 (legacy format - starts with multihash directly)
45
+
// Reset and read as v0
46
+
$reader = new Reader($data);
47
+
$hashType = $reader->readVarint(); // 0x12 = sha256
48
+
$hashLength = $reader->readVarint(); // typically 32
49
+
$hash = $reader->readBytes($hashLength);
50
+
51
+
return new self(
52
+
version: 0,
53
+
codec: 0x70, // dag-pb
54
+
hash: chr($hashType) . chr($hashLength) . $hash,
55
+
);
56
+
}
57
+
58
+
if ($version !== 1) {
59
+
throw new RuntimeException("Unsupported CID version: {$version}");
60
+
}
61
+
62
+
// Read codec
63
+
$codec = $reader->readVarint();
64
+
65
+
// Read multihash (hash type + length + hash bytes)
66
+
$hashType = $reader->readVarint();
67
+
$hashLength = $reader->readVarint();
68
+
$hashBytes = $reader->readBytes($hashLength);
69
+
70
+
// Store complete multihash
71
+
$hash = chr($hashType) . chr($hashLength) . $hashBytes;
72
+
73
+
return new self(
74
+
version: $version,
75
+
codec: $codec,
76
+
hash: $hash,
77
+
);
78
+
}
79
+
80
+
/**
81
+
* Parse CID from string (base32 or base58btc).
82
+
*
83
+
* @param string $str CID string
84
+
* @return self
85
+
* @throws RuntimeException If CID string is invalid
86
+
*/
87
+
public static function fromString(string $str): self
88
+
{
89
+
if (empty($str)) {
90
+
throw new RuntimeException('Empty CID string');
91
+
}
92
+
93
+
// Check multibase prefix
94
+
$prefix = $str[0];
95
+
96
+
if ($prefix === 'b') {
97
+
// base32 (CIDv1)
98
+
$binary = self::decodeBase32(substr($str, 1));
99
+
100
+
return self::fromBinary($binary);
101
+
}
102
+
103
+
if ($prefix === 'Q' || $prefix === '1') {
104
+
// base58btc (likely CIDv0)
105
+
$binary = self::decodeBase58($str);
106
+
107
+
return self::fromBinary($binary);
108
+
}
109
+
110
+
throw new RuntimeException("Unsupported multibase prefix: {$prefix}");
111
+
}
112
+
113
+
/**
114
+
* Convert CID to string representation.
115
+
*/
116
+
public function toString(): string
117
+
{
118
+
if ($this->version === 0) {
119
+
// CIDv0 is always base58btc without prefix
120
+
return self::encodeBase58($this->hash);
121
+
}
122
+
123
+
// CIDv1 uses base32 with 'b' prefix
124
+
$binary = chr($this->version) . $this->encodeVarint($this->codec) . $this->hash;
125
+
126
+
return 'b' . self::encodeBase32($binary);
127
+
}
128
+
129
+
/**
130
+
* Get binary representation.
131
+
*/
132
+
public function toBinary(): string
133
+
{
134
+
if ($this->version === 0) {
135
+
return $this->hash;
136
+
}
137
+
138
+
return chr($this->version) . $this->encodeVarint($this->codec) . $this->hash;
139
+
}
140
+
141
+
/**
142
+
* Decode base32 string to binary.
143
+
*/
144
+
private static function decodeBase32(string $str): string
145
+
{
146
+
$str = strtolower($str);
147
+
$result = '';
148
+
$bits = 0;
149
+
$value = 0;
150
+
151
+
for ($i = 0; $i < strlen($str); $i++) {
152
+
$char = $str[$i];
153
+
$pos = strpos(self::BASE32_CHARSET, $char);
154
+
155
+
if ($pos === false) {
156
+
throw new RuntimeException("Invalid base32 character: {$char}");
157
+
}
158
+
159
+
$value = ($value << 5) | $pos;
160
+
$bits += 5;
161
+
162
+
if ($bits >= 8) {
163
+
$result .= chr(($value >> ($bits - 8)) & 0xFF);
164
+
$bits -= 8;
165
+
}
166
+
}
167
+
168
+
return $result;
169
+
}
170
+
171
+
/**
172
+
* Encode binary to base32 string.
173
+
*/
174
+
private static function encodeBase32(string $data): string
175
+
{
176
+
$result = '';
177
+
$bits = 0;
178
+
$value = 0;
179
+
180
+
for ($i = 0; $i < strlen($data); $i++) {
181
+
$value = ($value << 8) | ord($data[$i]);
182
+
$bits += 8;
183
+
184
+
while ($bits >= 5) {
185
+
$result .= self::BASE32_CHARSET[($value >> ($bits - 5)) & 0x1F];
186
+
$bits -= 5;
187
+
}
188
+
}
189
+
190
+
if ($bits > 0) {
191
+
$result .= self::BASE32_CHARSET[($value << (5 - $bits)) & 0x1F];
192
+
}
193
+
194
+
return $result;
195
+
}
196
+
197
+
/**
198
+
* Decode base58btc string to binary.
199
+
*/
200
+
private static function decodeBase58(string $str): string
201
+
{
202
+
$decoded = gmp_init(0);
203
+
$base = gmp_init(58);
204
+
205
+
for ($i = 0; $i < strlen($str); $i++) {
206
+
$char = $str[$i];
207
+
$pos = strpos(self::BASE58BTC_CHARSET, $char);
208
+
209
+
if ($pos === false) {
210
+
throw new RuntimeException("Invalid base58 character: {$char}");
211
+
}
212
+
213
+
$decoded = gmp_add(gmp_mul($decoded, $base), gmp_init($pos));
214
+
}
215
+
216
+
$hex = gmp_strval($decoded, 16);
217
+
if (strlen($hex) % 2) {
218
+
$hex = '0' . $hex;
219
+
}
220
+
221
+
// Add leading zeros
222
+
for ($i = 0; $i < strlen($str) && $str[$i] === '1'; $i++) {
223
+
$hex = '00' . $hex;
224
+
}
225
+
226
+
return hex2bin($hex);
227
+
}
228
+
229
+
/**
230
+
* Encode binary to base58btc string.
231
+
*/
232
+
private static function encodeBase58(string $data): string
233
+
{
234
+
$num = gmp_init('0x' . bin2hex($data));
235
+
$base = gmp_init(58);
236
+
$result = '';
237
+
238
+
while (gmp_cmp($num, 0) > 0) {
239
+
[$num, $remainder] = gmp_div_qr($num, $base);
240
+
$result = self::BASE58BTC_CHARSET[gmp_intval($remainder)] . $result;
241
+
}
242
+
243
+
// Add leading '1's for leading zero bytes
244
+
for ($i = 0; $i < strlen($data) && ord($data[$i]) === 0; $i++) {
245
+
$result = '1' . $result;
246
+
}
247
+
248
+
return $result;
249
+
}
250
+
251
+
/**
252
+
* Encode varint for CID binary format.
253
+
*/
254
+
private function encodeVarint(int $value): string
255
+
{
256
+
$result = '';
257
+
258
+
while ($value >= 0x80) {
259
+
$result .= chr(($value & 0x7F) | 0x80);
260
+
$value >>= 7;
261
+
}
262
+
263
+
$result .= chr($value);
264
+
265
+
return $result;
266
+
}
267
+
268
+
public function __toString(): string
269
+
{
270
+
return $this->toString();
271
+
}
272
+
}
+1
-1
src/Enums/SignalCommitOperation.php
+1
-1
src/Enums/SignalCommitOperation.php
+1
-1
src/Enums/SignalEventType.php
+1
-1
src/Enums/SignalEventType.php
+4
-3
src/Events/AccountEvent.php
+4
-3
src/Events/AccountEvent.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Events;
3
+
namespace SocialDept\AtpSignals\Events;
4
4
5
-
use SocialDept\Signal\Contracts\EventContract;
5
+
use SocialDept\AtpSignals\Contracts\EventContract;
6
6
7
7
class AccountEvent implements EventContract
8
8
{
···
12
12
public ?string $status = null,
13
13
public int $seq = 0,
14
14
public ?string $time = null,
15
-
) {}
15
+
) {
16
+
}
16
17
17
18
public static function fromArray(array $data): self
18
19
{
+3
-3
src/Events/CommitEvent.php
+3
-3
src/Events/CommitEvent.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Events;
3
+
namespace SocialDept\AtpSignals\Events;
4
4
5
-
use SocialDept\Signal\Contracts\EventContract;
6
-
use SocialDept\Signal\Enums\SignalCommitOperation;
5
+
use SocialDept\AtpSignals\Contracts\EventContract;
6
+
use SocialDept\AtpSignals\Enums\SignalCommitOperation;
7
7
8
8
class CommitEvent implements EventContract
9
9
{
+4
-3
src/Events/IdentityEvent.php
+4
-3
src/Events/IdentityEvent.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Events;
3
+
namespace SocialDept\AtpSignals\Events;
4
4
5
-
use SocialDept\Signal\Contracts\EventContract;
5
+
use SocialDept\AtpSignals\Contracts\EventContract;
6
6
7
7
class IdentityEvent implements EventContract
8
8
{
···
11
11
public ?string $handle = null,
12
12
public int $seq = 0,
13
13
public ?string $time = null,
14
-
) {}
14
+
) {
15
+
}
15
16
16
17
public static function fromArray(array $data): self
17
18
{
+5
-4
src/Events/SignalEvent.php
+5
-4
src/Events/SignalEvent.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Events;
3
+
namespace SocialDept\AtpSignals\Events;
4
4
5
-
use SocialDept\Signal\Contracts\EventContract;
5
+
use SocialDept\AtpSignals\Contracts\EventContract;
6
6
7
7
class SignalEvent implements EventContract
8
8
{
···
13
13
public ?CommitEvent $commit = null,
14
14
public ?IdentityEvent $identity = null,
15
15
public ?AccountEvent $account = null,
16
-
) {}
16
+
) {
17
+
}
17
18
18
19
public function isCommit(): bool
19
20
{
···
40
41
return $this->commit?->record;
41
42
}
42
43
43
-
public function getOperation(): ?\SocialDept\Signal\Enums\SignalCommitOperation
44
+
public function getOperation(): ?\SocialDept\AtpSignals\Enums\SignalCommitOperation
44
45
{
45
46
return $this->commit?->operation;
46
47
}
+1
-1
src/Exceptions/ConnectionException.php
+1
-1
src/Exceptions/ConnectionException.php
+1
-1
src/Exceptions/SignalException.php
+1
-1
src/Exceptions/SignalException.php
+3
-3
src/Facades/Signal.php
+3
-3
src/Facades/Signal.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Facades;
3
+
namespace SocialDept\AtpSignals\Facades;
4
4
5
5
use Illuminate\Support\Facades\Facade;
6
-
use SocialDept\Signal\Services\SignalManager;
6
+
use SocialDept\AtpSignals\Services\SignalManager;
7
7
8
8
/**
9
9
* @method static void start(?int $cursor = null)
10
10
* @method static void stop()
11
11
* @method static string getMode()
12
12
*
13
-
* @see \SocialDept\Signal\Services\SignalManager
13
+
* @see \SocialDept\AtpSignals\Services\SignalManager
14
14
*/
15
15
class Signal extends Facade
16
16
{
+9
-5
src/Jobs/ProcessSignalJob.php
+9
-5
src/Jobs/ProcessSignalJob.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Jobs;
3
+
namespace SocialDept\AtpSignals\Jobs;
4
4
5
5
use Illuminate\Bus\Queueable;
6
6
use Illuminate\Contracts\Queue\ShouldQueue;
7
7
use Illuminate\Foundation\Bus\Dispatchable;
8
8
use Illuminate\Queue\InteractsWithQueue;
9
9
use Illuminate\Queue\SerializesModels;
10
-
use SocialDept\Signal\Events\SignalEvent;
11
-
use SocialDept\Signal\Signals\Signal;
10
+
use SocialDept\AtpSignals\Events\SignalEvent;
11
+
use SocialDept\AtpSignals\Signals\Signal;
12
12
13
13
class ProcessSignalJob implements ShouldQueue
14
14
{
15
-
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
15
+
use Dispatchable;
16
+
use InteractsWithQueue;
17
+
use Queueable;
18
+
use SerializesModels;
16
19
17
20
public function __construct(
18
21
protected Signal $signal,
19
22
protected SignalEvent $event,
20
-
) {}
23
+
) {
24
+
}
21
25
22
26
public function handle(): void
23
27
{
+3
-3
src/Services/EventDispatcher.php
+3
-3
src/Services/EventDispatcher.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Services;
3
+
namespace SocialDept\AtpSignals\Services;
4
4
5
5
use Illuminate\Support\Facades\Log;
6
6
use Illuminate\Support\Facades\Queue;
7
-
use SocialDept\Signal\Events\SignalEvent;
8
-
use SocialDept\Signal\Jobs\ProcessSignalJob;
7
+
use SocialDept\AtpSignals\Events\SignalEvent;
8
+
use SocialDept\AtpSignals\Jobs\ProcessSignalJob;
9
9
10
10
class EventDispatcher
11
11
{
+69
-58
src/Services/FirehoseConsumer.php
+69
-58
src/Services/FirehoseConsumer.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Services;
3
+
namespace SocialDept\AtpSignals\Services;
4
4
5
5
use Illuminate\Support\Arr;
6
6
use Illuminate\Support\Facades\Log;
7
-
use Revolution\Bluesky\Core\CAR;
8
-
use Revolution\Bluesky\Core\CBOR;
9
-
use SocialDept\Signal\Contracts\CursorStore;
10
-
use SocialDept\Signal\Events\AccountEvent;
11
-
use SocialDept\Signal\Events\CommitEvent;
12
-
use SocialDept\Signal\Events\IdentityEvent;
13
-
use SocialDept\Signal\Events\SignalEvent;
14
-
use SocialDept\Signal\Exceptions\ConnectionException;
15
-
use SocialDept\Signal\Support\WebSocketConnection;
7
+
use SocialDept\AtpSignals\Contracts\CursorStore;
8
+
use SocialDept\AtpSignals\Core\CAR;
9
+
use SocialDept\AtpSignals\Core\CBOR;
10
+
use SocialDept\AtpSignals\Core\CID;
11
+
use SocialDept\AtpSignals\Events\AccountEvent;
12
+
use SocialDept\AtpSignals\Events\CommitEvent;
13
+
use SocialDept\AtpSignals\Events\IdentityEvent;
14
+
use SocialDept\AtpSignals\Events\SignalEvent;
15
+
use SocialDept\AtpSignals\Exceptions\ConnectionException;
16
+
use SocialDept\AtpSignals\Support\WebSocketConnection;
16
17
17
18
class FirehoseConsumer
18
19
{
···
45
46
{
46
47
$this->shouldStop = false;
47
48
48
-
// Get cursor from storage if not provided
49
+
// Get cursor from storage if not explicitly provided
50
+
// null = use stored cursor, 0 = start fresh (no cursor), >0 = specific cursor
49
51
if ($cursor === null) {
50
52
$cursor = $this->cursorStore->get();
51
53
}
52
54
53
-
$url = $this->buildWebSocketUrl($cursor);
55
+
// If cursor is explicitly 0, don't send it (fresh start)
56
+
$url = $this->buildWebSocketUrl($cursor > 0 ? $cursor : null);
54
57
55
58
Log::info('Signal: Starting Firehose consumer', [
56
59
'url' => $url,
57
-
'cursor' => $cursor,
60
+
'cursor' => $cursor > 0 ? $cursor : 'none (fresh start)',
58
61
'mode' => 'firehose',
59
62
]);
60
63
···
80
83
*/
81
84
protected function connect(string $url): void
82
85
{
83
-
$this->connection = new WebSocketConnection;
86
+
$this->connection = new WebSocketConnection();
84
87
85
88
// Set up event handlers
86
89
$this->connection
···
176
179
$time = $payload['time'];
177
180
$timeUs = $payload['seq'] ?? 0; // Use seq as time_us equivalent
178
181
179
-
// Parse CAR blocks
182
+
// Parse CAR blocks (returns CID => block data map)
180
183
$records = $payload['blocks'];
184
+
181
185
$blocks = [];
182
186
if (! empty($records)) {
183
-
$blocks = rescue(fn () => iterator_to_array(CAR::blockMap($records)), []);
187
+
$blocks = rescue(fn () => CAR::blockMap($records, $did), [], function (\Throwable $e) {
188
+
Log::warning('Signal: Failed to parse CAR blocks', [
189
+
'error' => $e->getMessage(),
190
+
'trace' => $e->getTraceAsString(),
191
+
]);
192
+
});
184
193
}
185
194
186
195
// Process operations
···
202
211
$rkey = '';
203
212
204
213
if (str_contains($path, '/')) {
205
-
[$collection, $rkey] = explode('/', $path);
214
+
[$collection, $rkey] = explode('/', $path, 2);
206
215
}
207
216
208
-
$record = $blocks[$path] ?? [];
217
+
// Get record data from blocks using the op CID
218
+
// Convert CID to string if it's an object
219
+
$cidStr = $cid instanceof CID ? $cid->toString() : $cid;
220
+
221
+
// For delete operations, there won't be a record
222
+
$record = [];
223
+
if ($action !== 'delete' && isset($blocks[$cidStr])) {
224
+
// Decode the CBOR block to get the record data
225
+
$decoded = rescue(fn () => CBOR::decode($blocks[$cidStr]));
226
+
if (is_array($decoded)) {
227
+
$record = $this->normalizeCids($decoded);
228
+
}
229
+
}
209
230
210
231
// Convert to SignalEvent format for compatibility
211
-
$event = $this->buildSignalEvent($did, $timeUs, $action, $collection, $rkey, $rev, $cid, $record);
232
+
$event = $this->buildSignalEvent($did, $timeUs, $action, $collection, $rkey, $rev, $cidStr, $record);
212
233
213
-
// Dispatch event with cursor update and logging
214
-
$this->dispatchSignalEvent($event, 'Commit', [
215
-
'collection' => $collection,
216
-
'operation' => $action,
217
-
]);
234
+
// Dispatch event with cursor update
235
+
$this->dispatchSignalEvent($event);
218
236
}
219
237
}
220
238
···
231
249
?string $cid,
232
250
array $record
233
251
): SignalEvent {
234
-
$recordValue = $record['value'] ?? null;
252
+
// Record is already the decoded data, or empty array for deletes
253
+
$recordValue = ! empty($record) ? (object) $record : null;
235
254
236
255
$commitEvent = new CommitEvent(
237
256
rev: $rev,
238
257
operation: $operation,
239
258
collection: $collection,
240
259
rkey: $rkey,
241
-
record: $recordValue ? (object) $recordValue : null,
260
+
record: $recordValue,
242
261
cid: $cid
243
262
);
244
263
···
251
270
}
252
271
253
272
/**
254
-
* Dispatch a SignalEvent with cursor update and logging.
273
+
* Normalize CID objects to AT Protocol link format.
255
274
*/
256
-
protected function dispatchSignalEvent(SignalEvent $event, string $eventType, array $context = []): void
275
+
protected function normalizeCids(array $data): array
276
+
{
277
+
foreach ($data as $key => $value) {
278
+
if ($value instanceof CID) {
279
+
// Convert CID to AT Protocol link format
280
+
$data[$key] = ['$link' => $value->toString()];
281
+
} elseif (is_array($value)) {
282
+
$data[$key] = $this->normalizeCids($value);
283
+
}
284
+
}
285
+
286
+
return $data;
287
+
}
288
+
289
+
/**
290
+
* Dispatch a SignalEvent with cursor update.
291
+
*/
292
+
protected function dispatchSignalEvent(SignalEvent $event): void
257
293
{
258
294
// Update cursor
259
295
$this->cursorStore->set($event->timeUs);
260
296
261
-
// Check if any signals match this event
262
-
$matchingSignals = $this->signalRegistry->getMatchingSignals($event);
263
-
264
-
if ($matchingSignals->isNotEmpty()) {
265
-
Log::info("Signal: {$eventType} event matched", array_merge([
266
-
'matched_signals' => $matchingSignals->count(),
267
-
'signal_names' => $matchingSignals->map(fn ($s) => class_basename($s))->join(', '),
268
-
], $context));
269
-
}
270
-
271
297
// Dispatch to matching signals
272
298
$this->eventDispatcher->dispatch($event);
273
299
}
···
306
332
identity: $identityEvent
307
333
);
308
334
309
-
// Dispatch event with cursor update and logging
310
-
$this->dispatchSignalEvent($event, 'Identity', [
311
-
'did' => $did,
312
-
'handle' => $handle,
313
-
]);
335
+
// Dispatch event with cursor update
336
+
$this->dispatchSignalEvent($event);
314
337
}
315
338
316
339
/**
···
349
372
account: $accountEvent
350
373
);
351
374
352
-
// Dispatch event with cursor update and logging
353
-
$this->dispatchSignalEvent($event, 'Account', [
354
-
'did' => $did,
355
-
'active' => $active,
356
-
'status' => $status,
357
-
]);
375
+
// Dispatch event with cursor update
376
+
$this->dispatchSignalEvent($event);
358
377
}
359
378
360
379
/**
···
394
413
395
414
if ($this->reconnectAttempts >= $maxAttempts) {
396
415
Log::error('Signal: Max reconnection attempts reached');
416
+
397
417
throw new ConnectionException('Failed to reconnect to Firehose after '.$maxAttempts.' attempts');
398
418
}
399
419
···
441
461
if (! empty($params)) {
442
462
$url .= '?'.implode('&', $params);
443
463
}
444
-
445
-
Log::warning('Signal: Firehose mode - NO server-side collection filtering', [
446
-
'note' => 'All events will be received and filtered client-side',
447
-
'registered_collections' => $this->signalRegistry->all()
448
-
->flatMap(fn ($signal) => $signal->collections() ?? [])
449
-
->unique()
450
-
->values()
451
-
->toArray(),
452
-
]);
453
464
454
465
return $url;
455
466
}
+13
-30
src/Services/JetstreamConsumer.php
+13
-30
src/Services/JetstreamConsumer.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Services;
3
+
namespace SocialDept\AtpSignals\Services;
4
4
5
5
use Illuminate\Support\Facades\Log;
6
-
use SocialDept\Signal\Contracts\CursorStore;
7
-
use SocialDept\Signal\Events\SignalEvent;
8
-
use SocialDept\Signal\Exceptions\ConnectionException;
9
-
use SocialDept\Signal\Support\WebSocketConnection;
6
+
use SocialDept\AtpSignals\Contracts\CursorStore;
7
+
use SocialDept\AtpSignals\Events\SignalEvent;
8
+
use SocialDept\AtpSignals\Exceptions\ConnectionException;
9
+
use SocialDept\AtpSignals\Support\WebSocketConnection;
10
10
11
11
class JetstreamConsumer
12
12
{
···
39
39
{
40
40
$this->shouldStop = false;
41
41
42
-
// Get cursor from storage if not provided
42
+
// Get cursor from storage if not explicitly provided
43
+
// null = use stored cursor, 0 = start fresh (no cursor), >0 = specific cursor
43
44
if ($cursor === null) {
44
45
$cursor = $this->cursorStore->get();
45
46
}
46
47
47
-
$url = $this->buildWebSocketUrl($cursor);
48
+
// If cursor is explicitly 0, don't send it (fresh start)
49
+
$url = $this->buildWebSocketUrl($cursor > 0 ? $cursor : null);
48
50
49
51
Log::info('Signal: Starting Jetstream consumer', [
50
52
'url' => $url,
51
-
'cursor' => $cursor,
53
+
'cursor' => $cursor > 0 ? $cursor : 'none (fresh start)',
54
+
'mode' => 'firehose',
52
55
]);
53
56
54
57
$this->connect($url);
···
73
76
*/
74
77
protected function connect(string $url): void
75
78
{
76
-
$this->connection = new WebSocketConnection;
79
+
$this->connection = new WebSocketConnection();
77
80
78
81
// Set up event handlers
79
82
$this->connection
···
127
130
// Update cursor
128
131
$this->cursorStore->set($event->timeUs);
129
132
130
-
// Check if any signals match this event
131
-
$matchingSignals = $this->signalRegistry->getMatchingSignals($event);
132
-
133
-
if ($matchingSignals->isNotEmpty()) {
134
-
$collection = $event->getCollection() ?? $event->kind;
135
-
$operation = $event->getOperation() ?? 'event';
136
-
137
-
Log::info('Signal: Event matched', [
138
-
'collection' => $collection,
139
-
'operation' => $operation,
140
-
'matched_signals' => $matchingSignals->count(),
141
-
'signal_names' => $matchingSignals->map(fn ($s) => class_basename($s))->join(', '),
142
-
]);
143
-
}
144
-
145
133
// Dispatch to matching signals
146
134
$this->eventDispatcher->dispatch($event);
147
135
···
190
178
191
179
if ($this->reconnectAttempts >= $maxAttempts) {
192
180
Log::error('Signal: Max reconnection attempts reached');
181
+
193
182
throw new ConnectionException('Failed to reconnect to Jetstream after '.$maxAttempts.' attempts');
194
183
}
195
184
···
244
233
foreach ($collections as $collection) {
245
234
$params[] = 'wantedCollections='.urlencode($collection);
246
235
}
247
-
248
-
Log::info('Signal: Collection filters applied', [
249
-
'collections' => $collections->toArray(),
250
-
]);
251
-
} else {
252
-
Log::warning('Signal: No collection filters - will receive ALL events');
253
236
}
254
237
255
238
if (! empty($params)) {
+3
-2
src/Services/SignalManager.php
+3
-2
src/Services/SignalManager.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Services;
3
+
namespace SocialDept\AtpSignals\Services;
4
4
5
5
use InvalidArgumentException;
6
6
···
9
9
public function __construct(
10
10
protected FirehoseConsumer $firehoseConsumer,
11
11
protected JetstreamConsumer $jetstreamConsumer,
12
-
) {}
12
+
) {
13
+
}
13
14
14
15
/**
15
16
* Start consuming events from the AT Protocol.
+2
-2
src/Services/SignalRegistry.php
+2
-2
src/Services/SignalRegistry.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Services;
3
+
namespace SocialDept\AtpSignals\Services;
4
4
5
5
use Illuminate\Support\Collection;
6
6
use Illuminate\Support\Facades\File;
7
7
use InvalidArgumentException;
8
-
use SocialDept\Signal\Signals\Signal;
8
+
use SocialDept\AtpSignals\Signals\Signal;
9
9
10
10
class SignalRegistry
11
11
{
+19
-19
src/SignalServiceProvider.php
+19
-19
src/SignalServiceProvider.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal;
3
+
namespace SocialDept\AtpSignals;
4
4
5
5
use Illuminate\Support\ServiceProvider;
6
-
use SocialDept\Signal\Commands\ConsumeCommand;
7
-
use SocialDept\Signal\Commands\InstallCommand;
8
-
use SocialDept\Signal\Commands\ListSignalsCommand;
9
-
use SocialDept\Signal\Commands\MakeSignalCommand;
10
-
use SocialDept\Signal\Commands\TestSignalCommand;
11
-
use SocialDept\Signal\Contracts\CursorStore;
12
-
use SocialDept\Signal\Services\EventDispatcher;
13
-
use SocialDept\Signal\Services\FirehoseConsumer;
14
-
use SocialDept\Signal\Services\JetstreamConsumer;
15
-
use SocialDept\Signal\Services\SignalManager;
16
-
use SocialDept\Signal\Services\SignalRegistry;
17
-
use SocialDept\Signal\Storage\DatabaseCursorStore;
18
-
use SocialDept\Signal\Storage\FileCursorStore;
19
-
use SocialDept\Signal\Storage\RedisCursorStore;
6
+
use SocialDept\AtpSignals\Commands\ConsumeCommand;
7
+
use SocialDept\AtpSignals\Commands\InstallCommand;
8
+
use SocialDept\AtpSignals\Commands\ListSignalsCommand;
9
+
use SocialDept\AtpSignals\Commands\MakeSignalCommand;
10
+
use SocialDept\AtpSignals\Commands\TestSignalCommand;
11
+
use SocialDept\AtpSignals\Contracts\CursorStore;
12
+
use SocialDept\AtpSignals\Services\EventDispatcher;
13
+
use SocialDept\AtpSignals\Services\FirehoseConsumer;
14
+
use SocialDept\AtpSignals\Services\JetstreamConsumer;
15
+
use SocialDept\AtpSignals\Services\SignalManager;
16
+
use SocialDept\AtpSignals\Services\SignalRegistry;
17
+
use SocialDept\AtpSignals\Storage\DatabaseCursorStore;
18
+
use SocialDept\AtpSignals\Storage\FileCursorStore;
19
+
use SocialDept\AtpSignals\Storage\RedisCursorStore;
20
20
21
21
class SignalServiceProvider extends ServiceProvider
22
22
{
···
27
27
// Register cursor store
28
28
$this->app->singleton(CursorStore::class, function ($app) {
29
29
return match (config('signal.cursor_storage')) {
30
-
'redis' => new RedisCursorStore,
31
-
'file' => new FileCursorStore,
32
-
default => new DatabaseCursorStore,
30
+
'redis' => new RedisCursorStore(),
31
+
'file' => new FileCursorStore(),
32
+
default => new DatabaseCursorStore(),
33
33
};
34
34
});
35
35
36
36
// Register signal registry
37
37
$this->app->singleton(SignalRegistry::class, function ($app) {
38
-
$registry = new SignalRegistry;
38
+
$registry = new SignalRegistry();
39
39
40
40
// Register configured signals
41
41
foreach (config('signal.signals', []) as $signal) {
+4
-4
src/Signals/Signal.php
+4
-4
src/Signals/Signal.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Signals;
3
+
namespace SocialDept\AtpSignals\Signals;
4
4
5
-
use SocialDept\Signal\Events\SignalEvent;
5
+
use SocialDept\AtpSignals\Events\SignalEvent;
6
6
7
7
abstract class Signal
8
8
{
9
9
/**
10
10
* Define which event types to listen for.
11
11
*
12
-
* @return array<string|\SocialDept\Signal\Enums\SignalEventType>
12
+
* @return array<string|\SocialDept\AtpSignals\Enums\SignalEventType>
13
13
*/
14
14
abstract public function eventTypes(): array;
15
15
···
42
42
* - [SignalCommitOperation::Delete] - Only handle deletes
43
43
* - null - Handle all operations (default)
44
44
*
45
-
* @return array<string|\SocialDept\Signal\Enums\SignalCommitOperation>|null
45
+
* @return array<string|\SocialDept\AtpSignals\Enums\SignalCommitOperation>|null
46
46
*/
47
47
public function operations(): ?array
48
48
{
+2
-2
src/Storage/DatabaseCursorStore.php
+2
-2
src/Storage/DatabaseCursorStore.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Storage;
3
+
namespace SocialDept\AtpSignals\Storage;
4
4
5
5
use Illuminate\Database\Query\Builder;
6
6
use Illuminate\Support\Facades\DB;
7
-
use SocialDept\Signal\Contracts\CursorStore;
7
+
use SocialDept\AtpSignals\Contracts\CursorStore;
8
8
9
9
class DatabaseCursorStore implements CursorStore
10
10
{
+4
-4
src/Storage/FileCursorStore.php
+4
-4
src/Storage/FileCursorStore.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Storage;
3
+
namespace SocialDept\AtpSignals\Storage;
4
4
5
5
use Illuminate\Support\Facades\File;
6
-
use SocialDept\Signal\Contracts\CursorStore;
6
+
use SocialDept\AtpSignals\Contracts\CursorStore;
7
7
8
8
class FileCursorStore implements CursorStore
9
9
{
···
15
15
16
16
// Ensure directory exists
17
17
$directory = dirname($this->path);
18
-
if (!File::exists($directory)) {
18
+
if (! File::exists($directory)) {
19
19
File::makeDirectory($directory, 0755, true);
20
20
}
21
21
}
22
22
23
23
public function get(): ?int
24
24
{
25
-
if (!File::exists($this->path)) {
25
+
if (! File::exists($this->path)) {
26
26
return null;
27
27
}
28
28
+2
-2
src/Storage/RedisCursorStore.php
+2
-2
src/Storage/RedisCursorStore.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Storage;
3
+
namespace SocialDept\AtpSignals\Storage;
4
4
5
5
use Illuminate\Support\Facades\Redis;
6
-
use SocialDept\Signal\Contracts\CursorStore;
6
+
use SocialDept\AtpSignals\Contracts\CursorStore;
7
7
8
8
class RedisCursorStore implements CursorStore
9
9
{
+8
-2
src/Support/WebSocketConnection.php
+8
-2
src/Support/WebSocketConnection.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Support;
3
+
namespace SocialDept\AtpSignals\Support;
4
4
5
5
use Ratchet\Client\Connector;
6
6
use Ratchet\Client\WebSocket;
···
64
64
if ($this->onError) {
65
65
($this->onError)($e);
66
66
}
67
+
67
68
throw $e;
68
69
}
69
70
);
···
74
75
*/
75
76
public function send(string $message): bool
76
77
{
77
-
if (!$this->connected || !$this->connection) {
78
+
if (! $this->connected || ! $this->connection) {
78
79
return false;
79
80
}
80
81
81
82
try {
82
83
$this->connection->send($message);
84
+
83
85
return true;
84
86
} catch (\Exception $e) {
85
87
if ($this->onError) {
86
88
($this->onError)($e);
87
89
}
90
+
88
91
return false;
89
92
}
90
93
}
···
114
117
public function onMessage(callable $callback): self
115
118
{
116
119
$this->onMessage = $callback(...);
120
+
117
121
return $this;
118
122
}
119
123
···
123
127
public function onClose(callable $callback): self
124
128
{
125
129
$this->onClose = $callback(...);
130
+
126
131
return $this;
127
132
}
128
133
···
132
137
public function onError(callable $callback): self
133
138
{
134
139
$this->onError = $callback(...);
140
+
135
141
return $this;
136
142
}
137
143
+2
-2
stubs/signal.stub
+2
-2
stubs/signal.stub
+225
tests/Integration/FirehoseConsumerTest.php
+225
tests/Integration/FirehoseConsumerTest.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Tests\Integration;
6
+
7
+
use Orchestra\Testbench\TestCase;
8
+
use SocialDept\AtpSignals\Core\CAR;
9
+
use SocialDept\AtpSignals\Core\CBOR;
10
+
use SocialDept\AtpSignals\Core\CID;
11
+
12
+
class FirehoseConsumerTest extends TestCase
13
+
{
14
+
public function test_cbor_can_decode_firehose_message_header(): void
15
+
{
16
+
// Simulate a Firehose message header
17
+
// Map with 't' => '#commit', 'op' => 1
18
+
$header = [
19
+
't' => '#commit',
20
+
'op' => 1,
21
+
];
22
+
23
+
// Encode it manually for testing
24
+
$cbor = "\xA2"; // Map with 2 items
25
+
$cbor .= "\x61t"; // Text string 't'
26
+
$cbor .= "\x67#commit"; // Text string '#commit'
27
+
$cbor .= "\x62op"; // Text string 'op'
28
+
$cbor .= "\x01"; // Integer 1
29
+
30
+
[$decoded, $remainder] = CBOR::decodeFirst($cbor);
31
+
32
+
$this->assertIsArray($decoded);
33
+
$this->assertArrayHasKey('t', $decoded);
34
+
$this->assertArrayHasKey('op', $decoded);
35
+
$this->assertSame('#commit', $decoded['t']);
36
+
$this->assertSame(1, $decoded['op']);
37
+
}
38
+
39
+
public function test_cbor_can_decode_commit_payload(): void
40
+
{
41
+
// Simplified commit payload structure
42
+
$payload = [
43
+
'repo' => 'did:plc:test123',
44
+
'rev' => 'test-rev',
45
+
'seq' => 12345,
46
+
'time' => '2024-01-01T00:00:00Z',
47
+
'ops' => [],
48
+
];
49
+
50
+
// Encode a simple payload
51
+
$cbor = "\xA5"; // Map with 5 items
52
+
53
+
// 'repo' key
54
+
$cbor .= "\x64repo"; // Text string 'repo'
55
+
$cbor .= "\x6Fdid:plc:test123"; // Text string 'did:plc:test123'
56
+
57
+
// 'rev' key
58
+
$cbor .= "\x63rev"; // Text string 'rev'
59
+
$cbor .= "\x68test-rev"; // Text string 'test-rev'
60
+
61
+
// 'seq' key
62
+
$cbor .= "\x63seq"; // Text string 'seq'
63
+
$cbor .= "\x19\x30\x39"; // Integer 12345
64
+
65
+
// 'time' key
66
+
$cbor .= "\x64time"; // Text string 'time'
67
+
$cbor .= "\x74" . "2024-01-01T00:00:00Z"; // Text string (length 20)
68
+
69
+
// 'ops' key
70
+
$cbor .= "\x63ops"; // Text string 'ops'
71
+
$cbor .= "\x80"; // Empty array
72
+
73
+
$decoded = CBOR::decode($cbor);
74
+
75
+
$this->assertIsArray($decoded);
76
+
$this->assertSame('did:plc:test123', $decoded['repo']);
77
+
$this->assertSame('test-rev', $decoded['rev']);
78
+
$this->assertSame(12345, $decoded['seq']);
79
+
}
80
+
81
+
public function test_cid_can_be_decoded_from_cbor_tag(): void
82
+
{
83
+
// Create a CID and encode it as CBOR tag 42
84
+
$hash = hash('sha256', 'test-content', true);
85
+
$cidBinary = "\x01\x71\x12\x20" . $hash; // CIDv1, dag-cbor, sha256
86
+
$cidBytes = "\x00" . $cidBinary; // Add 0x00 prefix
87
+
88
+
// CBOR tag 42 + byte string
89
+
$length = strlen($cidBytes);
90
+
$cbor = "\xD8\x2A\x58" . chr($length) . $cidBytes;
91
+
92
+
$decoded = CBOR::decode($cbor);
93
+
94
+
$this->assertInstanceOf(CID::class, $decoded);
95
+
$this->assertSame(1, $decoded->version);
96
+
$this->assertSame(0x71, $decoded->codec);
97
+
}
98
+
99
+
public function test_car_can_extract_blocks(): void
100
+
{
101
+
// Create a minimal CAR with header and one block
102
+
$car = '';
103
+
104
+
// CAR header (minimal) - {version: 1}
105
+
$headerCbor = "\xA1\x67version\x01";
106
+
$headerLength = strlen($headerCbor);
107
+
$car .= chr($headerLength) . $headerCbor;
108
+
109
+
// Create a block with CID and data
110
+
// Block data: {test: "value"}
111
+
$blockData = "\xA1\x64test\x65value";
112
+
113
+
// Create CID: version 1, codec 0x71 (dag-cbor), sha256 hash
114
+
$cid = CID::fromBinary("\x01\x71\x12\x20" . str_repeat("\x00", 32));
115
+
$cidBinary = $cid->toBinary();
116
+
117
+
// In CAR format: varint(cid_length + data_length), CID bytes, data bytes
118
+
$totalLength = strlen($cidBinary) + strlen($blockData);
119
+
$car .= chr($totalLength) . $cidBinary . $blockData;
120
+
121
+
// Parse blocks
122
+
$blocks = CAR::blockMap($car, 'did:plc:test');
123
+
124
+
$this->assertIsArray($blocks);
125
+
$this->assertNotEmpty($blocks);
126
+
}
127
+
128
+
public function test_firehose_consumer_message_structure(): void
129
+
{
130
+
// Test the exact structure FirehoseConsumer expects
131
+
132
+
// 1. Create CBOR header
133
+
$headerMap = [
134
+
't' => '#commit',
135
+
'op' => 1,
136
+
];
137
+
138
+
$header = "\xA2"; // Map with 2 items
139
+
$header .= "\x61t\x67#commit"; // 't' => '#commit'
140
+
$header .= "\x62op\x01"; // 'op' => 1
141
+
142
+
// 2. Create CBOR payload
143
+
$payload = "\xA6"; // Map with 6 items
144
+
$payload .= "\x63seq\x19\x30\x39"; // 'seq' => 12345
145
+
$payload .= "\x66rebase\xF4"; // 'rebase' => false
146
+
$payload .= "\x64repo\x6Fdid:plc:test123"; // 'repo' => 'did:plc:test123'
147
+
$payload .= "\x66commit\xA0"; // 'commit' => {}
148
+
$payload .= "\x63rev\x68test-rev"; // 'rev' => 'test-rev'
149
+
$payload .= "\x65since\x66origin"; // 'since' => 'origin'
150
+
151
+
// Add required fields
152
+
$payload .= "\x66blocks\x40"; // 'blocks' => empty byte string
153
+
$payload .= "\x63ops\x80"; // 'ops' => []
154
+
$payload .= "\x64time\x74" . "2024-01-01T00:00:00Z"; // 'time' => timestamp
155
+
156
+
// Combine header + payload
157
+
$message = $header . $payload;
158
+
159
+
// Test decoding header
160
+
[$decodedHeader, $remainder] = CBOR::decodeFirst($message);
161
+
162
+
$this->assertIsArray($decodedHeader);
163
+
$this->assertSame('#commit', $decodedHeader['t']);
164
+
$this->assertSame(1, $decodedHeader['op']);
165
+
166
+
// Test decoding payload
167
+
$decodedPayload = CBOR::decode($remainder);
168
+
169
+
$this->assertIsArray($decodedPayload);
170
+
$this->assertArrayHasKey('seq', $decodedPayload);
171
+
$this->assertArrayHasKey('repo', $decodedPayload);
172
+
$this->assertArrayHasKey('rev', $decodedPayload);
173
+
}
174
+
175
+
public function test_complete_firehose_message_flow(): void
176
+
{
177
+
// This test simulates the complete flow that FirehoseConsumer::handleMessage() uses
178
+
179
+
// Step 1: CBOR header
180
+
$header = "\xA2\x61t\x67#commit\x62op\x01";
181
+
182
+
// Step 2: CBOR payload with all required fields
183
+
$payload = "\xA9"; // Map with 9 items
184
+
$payload .= "\x63seq\x19\x30\x39"; // seq: 12345
185
+
$payload .= "\x66rebase\xF4"; // rebase: false
186
+
$payload .= "\x64repo\x6Fdid:plc:test123"; // repo: "did:plc:test123"
187
+
$payload .= "\x66commit\xA0"; // commit: {}
188
+
$payload .= "\x63rev\x68test-rev"; // rev: "test-rev"
189
+
$payload .= "\x65since\x66origin"; // since: "origin"
190
+
$payload .= "\x66blocks\x40"; // blocks: b''
191
+
$payload .= "\x63ops\x80"; // ops: []
192
+
$payload .= "\x64time\x74" . "2024-01-01T00:00:00Z"; // time: "2024-01-01T00:00:00Z"
193
+
194
+
$message = $header . $payload;
195
+
196
+
// Simulate FirehoseConsumer::handleMessage() logic
197
+
198
+
// 1. Decode CBOR header
199
+
[$decodedHeader, $remainder] = CBOR::decodeFirst($message);
200
+
201
+
$this->assertArrayHasKey('t', $decodedHeader);
202
+
$this->assertArrayHasKey('op', $decodedHeader);
203
+
204
+
// 2. Check operation
205
+
$this->assertSame(1, $decodedHeader['op']);
206
+
207
+
// 3. Decode payload
208
+
$decodedPayload = CBOR::decode($remainder);
209
+
210
+
// 4. Verify required fields exist
211
+
$requiredFields = ['seq', 'rebase', 'repo', 'commit', 'rev', 'since', 'blocks', 'ops', 'time'];
212
+
foreach ($requiredFields as $field) {
213
+
$this->assertArrayHasKey($field, $decodedPayload);
214
+
}
215
+
216
+
// 5. Verify data types
217
+
$this->assertIsInt($decodedPayload['seq']);
218
+
$this->assertIsBool($decodedPayload['rebase']);
219
+
$this->assertIsString($decodedPayload['repo']);
220
+
$this->assertIsArray($decodedPayload['ops']);
221
+
222
+
// Success! The message structure is valid
223
+
$this->assertTrue(true);
224
+
}
225
+
}
+135
tests/Unit/CBORTest.php
+135
tests/Unit/CBORTest.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Tests\Unit;
6
+
7
+
use PHPUnit\Framework\TestCase;
8
+
use SocialDept\AtpSignals\Core\CBOR;
9
+
use SocialDept\AtpSignals\Core\CID;
10
+
11
+
class CBORTest extends TestCase
12
+
{
13
+
public function test_decode_unsigned_integers(): void
14
+
{
15
+
// Small value (0-23)
16
+
$this->assertSame(0, CBOR::decode("\x00"));
17
+
$this->assertSame(1, CBOR::decode("\x01"));
18
+
$this->assertSame(23, CBOR::decode("\x17"));
19
+
20
+
// 1-byte value
21
+
$this->assertSame(24, CBOR::decode("\x18\x18"));
22
+
$this->assertSame(255, CBOR::decode("\x18\xFF"));
23
+
24
+
// 2-byte value
25
+
$this->assertSame(256, CBOR::decode("\x19\x01\x00"));
26
+
$this->assertSame(1000, CBOR::decode("\x19\x03\xE8"));
27
+
}
28
+
29
+
public function test_decode_negative_integers(): void
30
+
{
31
+
// -1 is encoded as 0x20 (major type 1, value 0)
32
+
$this->assertSame(-1, CBOR::decode("\x20"));
33
+
34
+
// -10 is encoded as 0x29 (major type 1, value 9)
35
+
$this->assertSame(-10, CBOR::decode("\x29"));
36
+
37
+
// -100 is encoded as 0x38 0x63 (major type 1, 1-byte value 99)
38
+
$this->assertSame(-100, CBOR::decode("\x38\x63"));
39
+
}
40
+
41
+
public function test_decode_byte_strings(): void
42
+
{
43
+
// Empty byte string
44
+
$this->assertSame('', CBOR::decode("\x40"));
45
+
46
+
// 4-byte string
47
+
$this->assertSame("\x01\x02\x03\x04", CBOR::decode("\x44\x01\x02\x03\x04"));
48
+
}
49
+
50
+
public function test_decode_text_strings(): void
51
+
{
52
+
// Empty text string
53
+
$this->assertSame('', CBOR::decode("\x60"));
54
+
55
+
// "hello"
56
+
$this->assertSame('hello', CBOR::decode("\x65hello"));
57
+
58
+
// "IETF"
59
+
$this->assertSame('IETF', CBOR::decode("\x64IETF"));
60
+
}
61
+
62
+
public function test_decode_arrays(): void
63
+
{
64
+
// Empty array
65
+
$this->assertSame([], CBOR::decode("\x80"));
66
+
67
+
// [1, 2, 3]
68
+
$this->assertSame([1, 2, 3], CBOR::decode("\x83\x01\x02\x03"));
69
+
70
+
// Mixed array [1, "two", 3]
71
+
$result = CBOR::decode("\x83\x01\x63two\x03");
72
+
$this->assertSame([1, 'two', 3], $result);
73
+
}
74
+
75
+
public function test_decode_maps(): void
76
+
{
77
+
// Empty map
78
+
$this->assertSame([], CBOR::decode("\xA0"));
79
+
80
+
// {"a": 1, "b": 2}
81
+
$result = CBOR::decode("\xA2\x61a\x01\x61b\x02");
82
+
$this->assertSame(['a' => 1, 'b' => 2], $result);
83
+
}
84
+
85
+
public function test_decode_special_values(): void
86
+
{
87
+
// false
88
+
$this->assertFalse(CBOR::decode("\xF4"));
89
+
90
+
// true
91
+
$this->assertTrue(CBOR::decode("\xF5"));
92
+
93
+
// null
94
+
$this->assertNull(CBOR::decode("\xF6"));
95
+
}
96
+
97
+
public function test_decode_first_returns_value_and_remainder(): void
98
+
{
99
+
[$value, $remainder] = CBOR::decodeFirst("\x01\x02\x03");
100
+
101
+
$this->assertSame(1, $value);
102
+
$this->assertSame("\x02\x03", $remainder);
103
+
}
104
+
105
+
public function test_decode_nested_structures(): void
106
+
{
107
+
// {"key": [1, 2, {"inner": true}]}
108
+
$cbor = "\xA1\x63key\x83\x01\x02\xA1\x65inner\xF5";
109
+
$result = CBOR::decode($cbor);
110
+
111
+
$expected = [
112
+
'key' => [1, 2, ['inner' => true]],
113
+
];
114
+
115
+
$this->assertSame($expected, $result);
116
+
}
117
+
118
+
public function test_decode_cid_tag(): void
119
+
{
120
+
// Tag 42 (CID) followed by byte string with CID data
121
+
// CID bytes: 0x00 prefix + version + codec + multihash
122
+
$cidBinary = "\x01\x71\x12\x20" . str_repeat("\x00", 32); // version 1, codec 0x71, sha256, 32 zero bytes
123
+
$cidBytes = "\x00" . $cidBinary; // Add 0x00 prefix for CBOR tag 42
124
+
125
+
// CBOR: tag 42 (0xD8 0x2A) + byte string with 1-byte length (0x58 = major type 2, additional info 24)
126
+
$length = strlen($cidBytes);
127
+
$cbor = "\xD8\x2A\x58" . chr($length) . $cidBytes;
128
+
129
+
$result = CBOR::decode($cbor);
130
+
131
+
$this->assertInstanceOf(CID::class, $result);
132
+
$this->assertSame(1, $result->version);
133
+
$this->assertSame(0x71, $result->codec);
134
+
}
135
+
}
+100
tests/Unit/CIDTest.php
+100
tests/Unit/CIDTest.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Tests\Unit;
6
+
7
+
use PHPUnit\Framework\TestCase;
8
+
use SocialDept\AtpSignals\Core\CID;
9
+
10
+
class CIDTest extends TestCase
11
+
{
12
+
public function test_parse_binary_cidv1(): void
13
+
{
14
+
// CIDv1: version=1, codec=0x71 (dag-cbor), sha256 hash
15
+
$hash = str_repeat("\x00", 32);
16
+
$binary = "\x01\x71\x12\x20" . $hash;
17
+
18
+
$cid = CID::fromBinary($binary);
19
+
20
+
$this->assertSame(1, $cid->version);
21
+
$this->assertSame(0x71, $cid->codec);
22
+
$this->assertSame("\x12\x20" . $hash, $cid->hash);
23
+
}
24
+
25
+
public function test_parse_binary_cidv0(): void
26
+
{
27
+
// CIDv0: starts with 0x12 (sha256) 0x20 (32 bytes)
28
+
$hash = str_repeat("\x00", 32);
29
+
$binary = "\x12\x20" . $hash;
30
+
31
+
$cid = CID::fromBinary($binary);
32
+
33
+
$this->assertSame(0, $cid->version);
34
+
$this->assertSame(0x70, $cid->codec); // dag-pb
35
+
$this->assertSame("\x12\x20" . $hash, $cid->hash);
36
+
}
37
+
38
+
public function test_to_string_cidv1(): void
39
+
{
40
+
$hash = str_repeat("\x00", 32);
41
+
$binary = "\x01\x71\x12\x20" . $hash;
42
+
$cid = CID::fromBinary($binary);
43
+
44
+
$str = $cid->toString();
45
+
46
+
// Should start with 'b' (base32 prefix)
47
+
$this->assertStringStartsWith('b', $str);
48
+
49
+
// Should be able to parse it back
50
+
$parsed = CID::fromString($str);
51
+
$this->assertSame($cid->version, $parsed->version);
52
+
$this->assertSame($cid->codec, $parsed->codec);
53
+
}
54
+
55
+
public function test_to_binary_cidv1(): void
56
+
{
57
+
$hash = str_repeat("\x00", 32);
58
+
$binary = "\x01\x71\x12\x20" . $hash;
59
+
$cid = CID::fromBinary($binary);
60
+
61
+
$this->assertSame($binary, $cid->toBinary());
62
+
}
63
+
64
+
public function test_round_trip_binary(): void
65
+
{
66
+
$hash = hash('sha256', 'test', true);
67
+
$binary = "\x01\x71\x12\x20" . $hash;
68
+
69
+
$cid = CID::fromBinary($binary);
70
+
$encoded = $cid->toBinary();
71
+
$decoded = CID::fromBinary($encoded);
72
+
73
+
$this->assertSame($cid->version, $decoded->version);
74
+
$this->assertSame($cid->codec, $decoded->codec);
75
+
$this->assertSame($cid->hash, $decoded->hash);
76
+
}
77
+
78
+
public function test_round_trip_string(): void
79
+
{
80
+
$hash = hash('sha256', 'test', true);
81
+
$binary = "\x01\x71\x12\x20" . $hash;
82
+
$cid = CID::fromBinary($binary);
83
+
84
+
$str = $cid->toString();
85
+
$parsed = CID::fromString($str);
86
+
87
+
$this->assertSame($cid->version, $parsed->version);
88
+
$this->assertSame($cid->codec, $parsed->codec);
89
+
$this->assertSame($cid->hash, $parsed->hash);
90
+
}
91
+
92
+
public function test_to_string_magic_method(): void
93
+
{
94
+
$hash = str_repeat("\x00", 32);
95
+
$binary = "\x01\x71\x12\x20" . $hash;
96
+
$cid = CID::fromBinary($binary);
97
+
98
+
$this->assertSame($cid->toString(), (string) $cid);
99
+
}
100
+
}
+4
-5
tests/Unit/SignalRegistryTest.php
+4
-5
tests/Unit/SignalRegistryTest.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Tests\Unit;
3
+
namespace SocialDept\AtpSignals\Tests\Unit;
4
4
5
5
use Orchestra\Testbench\TestCase;
6
-
use SocialDept\Signal\Events\CommitEvent;
7
-
use SocialDept\Signal\Events\SignalEvent;
8
-
use SocialDept\Signal\Services\SignalRegistry;
9
-
use SocialDept\Signal\Signals\Signal;
6
+
use SocialDept\AtpSignals\Events\CommitEvent;
7
+
use SocialDept\AtpSignals\Events\SignalEvent;
8
+
use SocialDept\AtpSignals\Services\SignalRegistry;
10
9
11
10
class SignalRegistryTest extends TestCase
12
11
{
+17
-10
tests/Unit/SignalTest.php
+17
-10
tests/Unit/SignalTest.php
···
1
1
<?php
2
2
3
-
namespace SocialDept\Signal\Tests\Unit;
3
+
namespace SocialDept\AtpSignals\Tests\Unit;
4
4
5
5
use Orchestra\Testbench\TestCase;
6
-
use SocialDept\Signal\Events\CommitEvent;
7
-
use SocialDept\Signal\Events\SignalEvent;
8
-
use SocialDept\Signal\Signals\Signal;
6
+
use SocialDept\AtpSignals\Events\CommitEvent;
7
+
use SocialDept\AtpSignals\Events\SignalEvent;
8
+
use SocialDept\AtpSignals\Signals\Signal;
9
9
10
10
class SignalTest extends TestCase
11
11
{
12
12
/** @test */
13
13
public function it_can_create_a_signal()
14
14
{
15
-
$signal = new class extends Signal {
15
+
$signal = new class () extends Signal {
16
16
public function eventTypes(): array
17
17
{
18
18
return ['commit'];
···
31
31
/** @test */
32
32
public function it_can_filter_by_exact_collection()
33
33
{
34
-
$signal = new class extends Signal {
34
+
$signal = new class () extends Signal {
35
35
public function eventTypes(): array
36
36
{
37
37
return ['commit'];
···
66
66
/** @test */
67
67
public function it_can_filter_by_wildcard_collection()
68
68
{
69
-
$signal = new class extends Signal {
69
+
$signalClass = new class () extends Signal {
70
70
public function eventTypes(): array
71
71
{
72
72
return ['commit'];
···
83
83
}
84
84
};
85
85
86
+
// Create registry and register the signal
87
+
$registry = new \SocialDept\AtpSignals\Services\SignalRegistry();
88
+
$registry->register($signalClass::class);
89
+
86
90
// Test that it matches app.bsky.feed.post
87
91
$postEvent = new SignalEvent(
88
92
did: 'did:plc:test',
···
96
100
),
97
101
);
98
102
99
-
$this->assertTrue($signal->shouldHandle($postEvent));
103
+
$matchingSignals = $registry->getMatchingSignals($postEvent);
104
+
$this->assertCount(1, $matchingSignals);
100
105
101
106
// Test that it matches app.bsky.feed.like
102
107
$likeEvent = new SignalEvent(
···
111
116
),
112
117
);
113
118
114
-
$this->assertTrue($signal->shouldHandle($likeEvent));
119
+
$matchingSignals = $registry->getMatchingSignals($likeEvent);
120
+
$this->assertCount(1, $matchingSignals);
115
121
116
122
// Test that it does NOT match app.bsky.graph.follow
117
123
$followEvent = new SignalEvent(
···
126
132
),
127
133
);
128
134
129
-
$this->assertFalse($signal->shouldHandle($followEvent));
135
+
$matchingSignals = $registry->getMatchingSignals($followEvent);
136
+
$this->assertCount(0, $matchingSignals);
130
137
}
131
138
}
+75
tests/Unit/VarintTest.php
+75
tests/Unit/VarintTest.php
···
1
+
<?php
2
+
3
+
declare(strict_types=1);
4
+
5
+
namespace SocialDept\AtpSignals\Tests\Unit;
6
+
7
+
use PHPUnit\Framework\TestCase;
8
+
use RuntimeException;
9
+
use SocialDept\AtpSignals\Binary\Varint;
10
+
11
+
class VarintTest extends TestCase
12
+
{
13
+
public function test_decode_single_byte_values(): void
14
+
{
15
+
$this->assertSame(0, Varint::decode("\x00"));
16
+
$this->assertSame(1, Varint::decode("\x01"));
17
+
$this->assertSame(127, Varint::decode("\x7F"));
18
+
}
19
+
20
+
public function test_decode_multi_byte_values(): void
21
+
{
22
+
// 128 = 0x80 0x01
23
+
$this->assertSame(128, Varint::decode("\x80\x01"));
24
+
25
+
// 300 = 0xAC 0x02
26
+
$this->assertSame(300, Varint::decode("\xAC\x02"));
27
+
28
+
// 16384 = 0x80 0x80 0x01
29
+
$this->assertSame(16384, Varint::decode("\x80\x80\x01"));
30
+
}
31
+
32
+
public function test_decode_with_offset(): void
33
+
{
34
+
$data = "\x00\x01\x7F\x80\x01";
35
+
$offset = 0;
36
+
37
+
$this->assertSame(0, Varint::decode($data, $offset));
38
+
$this->assertSame(1, $offset);
39
+
40
+
$this->assertSame(1, Varint::decode($data, $offset));
41
+
$this->assertSame(2, $offset);
42
+
43
+
$this->assertSame(127, Varint::decode($data, $offset));
44
+
$this->assertSame(3, $offset);
45
+
46
+
$this->assertSame(128, Varint::decode($data, $offset));
47
+
$this->assertSame(5, $offset);
48
+
}
49
+
50
+
public function test_decode_first_returns_value_and_remainder(): void
51
+
{
52
+
[$value, $remainder] = Varint::decodeFirst("\x7F\x01\x02\x03");
53
+
54
+
$this->assertSame(127, $value);
55
+
$this->assertSame("\x01\x02\x03", $remainder);
56
+
}
57
+
58
+
public function test_decode_throws_on_unexpected_end(): void
59
+
{
60
+
$this->expectException(RuntimeException::class);
61
+
$this->expectExceptionMessage('Unexpected end of varint data');
62
+
63
+
Varint::decode("\x80");
64
+
}
65
+
66
+
public function test_decode_throws_on_too_long_varint(): void
67
+
{
68
+
$this->expectException(RuntimeException::class);
69
+
$this->expectExceptionMessage('Varint too long (max 64 bits)');
70
+
71
+
// Create a varint that would be longer than 64 bits (10 bytes with continuation bits)
72
+
$tooLong = str_repeat("\xFF", 10);
73
+
Varint::decode($tooLong);
74
+
}
75
+
}