PHPackages                             eristemena/dialogflow-fulfillment-webhook-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. eristemena/dialogflow-fulfillment-webhook-php

AbandonedArchivedLibrary

eristemena/dialogflow-fulfillment-webhook-php
=============================================

Dialogflow Fulfillment Webhook v1 &amp; v2

1.3.0(6y ago)103492.7k↓25.3%48[24 issues](https://github.com/eristemena/dialogflow-fulfillment-webhook-php/issues)[1 PRs](https://github.com/eristemena/dialogflow-fulfillment-webhook-php/pulls)1MITPHPPHP &gt;=5.4

Since May 9Pushed 5y ago11 watchersCompare

[ Source](https://github.com/eristemena/dialogflow-fulfillment-webhook-php)[ Packagist](https://packagist.org/packages/eristemena/dialogflow-fulfillment-webhook-php)[ RSS](/packages/eristemena-dialogflow-fulfillment-webhook-php/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (6)Dependencies (3)Versions (15)Used By (1)

Dialogflow Fulfillment PHP Library
==================================

[](#dialogflow-fulfillment-php-library)

[![Latest Version on Packagist](https://camo.githubusercontent.com/6eb3a0a0d198656e21454405a5b49b260f5cb5c7dd25217d96a225ee9a9ed815/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6572697374656d656e612f6469616c6f67666c6f772d66756c66696c6c6d656e742d776562686f6f6b2d7068702e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/eristemena/dialogflow-fulfillment-webhook-php)[![Build Status](https://camo.githubusercontent.com/3a917c9d7c3157612d64509ebbf9ba66af2c821d72ee238a8bf359121b59a41a/68747470733a2f2f7472617669732d63692e6f72672f6572697374656d656e612f6469616c6f67666c6f772d66756c66696c6c6d656e742d776562686f6f6b2d7068702e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/eristemena/dialogflow-fulfillment-webhook-php)[![codecov](https://camo.githubusercontent.com/a37dfb277409f2447c97295cd023bf071c7a4a8ab2729ec195dc127dfdb0acfd/68747470733a2f2f636f6465636f762e696f2f67682f6572697374656d656e612f6469616c6f67666c6f772d66756c66696c6c6d656e742d776562686f6f6b2d7068702f6272616e63682f6d61737465722f67726170682f62616467652e737667)](https://codecov.io/gh/eristemena/dialogflow-fulfillment-webhook-php)[![StyleCI](https://camo.githubusercontent.com/e212f6071738d2c4ee5c3e9de97915611120fdcce2d2b17aa015295ec5b07f08/68747470733a2f2f7374796c6563692e696f2f7265706f732f3133323730333836362f736869656c643f6272616e63683d6d6173746572)](https://styleci.io/repos/132703866)[![Monthly Downloads](https://camo.githubusercontent.com/839fc9315d4ad5d6868bb91f78f5c1a8f90421623cd14c69f1a8fa62c545c573/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646d2f6572697374656d656e612f6469616c6f67666c6f772d66756c66696c6c6d656e742d776562686f6f6b2d7068702e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/eristemena/dialogflow-fulfillment-webhook-php)

This Library is inspired by [dialogflow/dialogflow-fulfillment-nodejs](https://github.com/dialogflow/dialogflow-fulfillment-nodejs).

It supports Dialogflow's fulfillment webhook JSON requests and responses for v1 and v2 agents.

For full class reference please refer to the [doc](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/README.md).

- [Installation](#installation)
- [Usage](#usage)
    - [Initiate Agent](#initiate-agent)
    - [Get Request Info](#get-request-info)
    - [Send Reply](#send-reply)
    - [Rich Message](#rich-message)
        - [Text](#text)
        - [Image](#image)
        - [Card](#card)
        - [Suggestion](#suggestion)
        - [Custom payload](#custom-payload)
    - [Actions on Google](#actions-on-google)
        - [Send Reply](#send-reply-1)
        - [Responses](#responses)
            - [Simple Response](#simple-response)
            - [Image](#image-1)
            - [Basic Card](#basic-card)
            - [List](#list)
            - [Carousel](#carousel)
            - [Browsing Carousel](#browsing-carousel)
            - [Suggestion Chip](#suggestion-chip)
            - [Media Responses](#media-responses)
        - [Helpers](#helpers)
            - [User information](#user-information)
            - [Date and Time](#date-and-time)
            - [Place and Location](#place-and-location)
            - [Confirmation](#confirmation)
        - [Surface Capabilities](#surface-capabilities)

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

[](#installation)

Install via composer: `composer require eristemena/dialogflow-fulfillment-webhook-php`.

Usage
-----

[](#usage)

### Initiate Agent

[](#initiate-agent)

To initiate agent, use `\Dialogflow\WebhookClient` constructor with input parameter as array of request coming from Dialogflow.

In Vanilla PHP, this can be done as follow,

```
use Dialogflow\WebhookClient;

$agent = new WebhookClient(json_decode(file_get_contents('php://input'),true));

// or

$agent = WebhookClient::fromData($_POST);
```

or if you're using Laravel,

```
$agent = \Dialogflow\WebhookClient::fromData($request->json()->all());
```

### Get Request Info

[](#get-request-info)

- [Intent](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetaction)

```
$intent = $agent->getIntent();
```

- [Action](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetaction)

```
$action = $agent->getAction();
```

- [Query](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetquery)

```
$query = $agent->getQuery();
```

- [Parameters](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetparameters)

```
$parameters = $agent->getParameters();
```

- [Session](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetsession)

```
$session = $agent->getSession();
```

- [Contexts](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetcontexts)

```
$contexts = $agent->getContexts();
```

- [Language](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetlocale)

```
$language = $agent->getLocale();
```

- Request Source (ex: `google`, `facebook`, `slack`, etc)

```
$originalRequest = $agent->getRequestSource();
```

- [Original Request](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetoriginalrequest), platform specific payload

```
$originalRequest = $agent->getOriginalRequest();
```

- [Agent Version](https://github.com/eristemena/dialog-fulfillment-webhook-php/blob/master/docs/WebhookClient.md#webhookclientgetagentversion) (1 or 2)

```
$agentVersion = $agent->getAgentVersion();
```

### Send Reply

[](#send-reply)

To send a reply, use `reply()` method.

```
$agent->reply('Hi, how can I help?');
```

Then use `render()` to get response in array. All you have to do is to print the array as JSON,

```
header('Content-type: application/json');
echo json_encode($agent->render());
```

or in Laravel,

```
return response()->json($agent->render());
```

The response payload will be automatically formatted according to Agent Version of the request.

### Rich Message

[](#rich-message)

#### Text

[](#text)

```
$text = \Dialogflow\RichMessage\Text::create()
    ->text('This is text')
    ->ssml('This is ssml')
;
$agent->reply($text);
```

#### Image

[](#image)

```
$image = \Dialogflow\RichMessage\Image::create('https://www.example.com/image.png');
$agent->reply($image);
```

#### Card

[](#card)

```
$card = \Dialogflow\RichMessage\Card::create()
    ->title('This is title')
    ->text('this is text body')
    ->image('https://www.example.com/image.png')
    ->button('This is a button', 'https://docs.dialogflow.com/')
;
$agent->reply($card);
```

#### Suggestion

[](#suggestion)

```
$suggestion = \Dialogflow\RichMessage\Suggestion::create(['Suggestion one', 'Suggestion two']);
$agent->reply($suggestion);
```

#### Custom payload

[](#custom-payload)

```
if ($agent->getRequestSource()=='google') {
    $agent->reply(\Dialogflow\RichMessage\Payload::create([
        'expectUserResponse' => false
    ]));
}
```

### Actions on Google

[](#actions-on-google)

This library also supports [Actions on Google](https://developers.google.com/actions/assistant/basics) specific functionalities. It's still under development, so more features will be added in the future.

To use Actions on Google Dialogflow Conversation object, you must first need to ensure the `requestSource` is coming from Google Assistant,

```
if ($agent->getRequestSource()=='google') {
    $conv = $agent->getActionConversation();

    // here you can use the rest of Actions on Google responses and helpers

    $agent->reply($conv);
}
```

or you can just call `getActionConversation()` method, and it will return `null` if the request doesn't come from Google Assistant.

```
$conv = $agent->getActionConversation();

if ($conv) {
	// here you can use the rest of Actions on Google responses and helpers
} else {
	// the request does not come from Google Assistant
}
```

#### Send Reply

[](#send-reply-1)

Using Dialogflow Conversation object, you can send a reply in two ways,

1. Send a reply and close the conversation

```
$conv->close('Have a nice day!');
```

2. Send a reply and wait for user's response

```
$conv->ask('Hi, how can I help?');
```

#### Responses

[](#responses)

##### Simple Response

[](#simple-response)

Please see the documentation [here](https://developers.google.com/actions/assistant/responses#simple_responses).

```
use Dialogflow\Action\Responses\SimpleResponse;

$conv->ask(SimpleResponse::create()
     ->displayText('Hello, how can i help?')
     ->ssml('Hello, how can i help?')
);
```

##### Image

[](#image-1)

```
use Dialogflow\Action\Responses\Image;

$conv->close(Image::create('https://picsum.photos/400/300'));
```

##### Basic Card

[](#basic-card)

Please see the documentation [here](https://developers.google.com/actions/assistant/responses#basic_card).

```
use Dialogflow\Action\Responses\BasicCard;

$conv->close(BasicCard::create()
    ->title('This is a title')
    ->formattedText('This is a subtitle')
    ->image('https://picsum.photos/400/300')
    ->button('This is a button', 'https://docs.dialogflow.com/')
);
```

##### List

[](#list)

Please see the documentation [here](https://developers.google.com/actions/assistant/responses#list).

> The single-select list presents the user with a vertical list of multiple items and allows the user to select a single one. Selecting an item from the list generates a user query (chat bubble) containing the title of the list item.

```
use Dialogflow\Action\Questions\ListCard;
use Dialogflow\Action\Questions\ListCard\Option;

$conv->ask('Please choose below');

$conv->ask(ListCard::create()
    ->title('This is a title')
    ->addOption(Option::create()
        ->key('OPTION_1')
        ->title('Option 1')
        ->synonyms(['option one','one'])
        ->description('Select option 1')
        ->image('https://picsum.photos/48/48')
    )
    ->addOption(Option::create()
        ->key('OPTION_2')
        ->title('Option 2')
        ->synonyms(['option two','two'])
        ->description('Select option 2')
        ->image('https://picsum.photos/48/48')
    )
);
```

To capture the option selected by user, create a Dialogflow intent with the `actions_intent_OPTION` event. Assuming you name the intent as `Get Option`, you can get the argument as follow,

```
if ('Get Option'==$agent->getIntent()) {
    $conv = $agent->getActionConversation();
    $option = $conv->getArguments()->get('OPTION');

    switch ($option) {
        case 'OPTION_1':
            $conv->close('You choose option 1');
            break;

        case 'OPTION_2':
            $conv->close('You choose option 2');
            break;

        default:
            $conv->close('Sorry, i do not understand');
            break;
    }
}
```

##### Carousel

[](#carousel)

Please see the documentation [here](https://developers.google.com/actions/assistant/responses#carousel).

> The carousel scrolls horizontally and allows for selecting one item. Compared to the list selector, it has large tiles-allowing for richer content. The tiles that make up a carousel are similar to the basic card with image. Selecting an item from the carousel will simply generate a chat bubble as the response just like with list selector.

```
use Dialogflow\Action\Questions\Carousel;
use Dialogflow\Action\Questions\Carousel\Option;

$conv->ask('Please choose below');

$conv->ask(
    Carousel::create()
    ->Option(
        Option::create()
        ->key('OPTION_1')
        ->title('Option 1')
        ->synonyms(['option one', 'one'])
        ->description('Select option 1')
        ->image('https://picsum.photos/300/300')
    )
    ->Option(
        Option::create()
        ->key('OPTION_2')
        ->title('Option 2')
        ->synonyms(['option two', 'two'])
        ->description('Select option 2')
        ->image('https://picsum.photos/300/300')
    )
    ->Option(
        Option::create()
        ->key('OPTION_3')
        ->title('Option 3')
        ->synonyms(['option three', 'three'])
        ->description('Select option 3')
        ->image('https://picsum.photos/300/300')
    )
    ->Option(
        Option::create()
        ->key('OPTION_4')
        ->title('Option 4')
        ->synonyms(['option four', 'four'])
        ->description('Select option 4')
        ->image('https://picsum.photos/300/300')
    )
);
```

To check if the user granted you the information and then access the data, create a Dialogflow intent with the `actions_intent_OPTION` event. Assuming you name the intent as `Get Option`, you can get the argument as follow,

```
if ('Get Option'==$agent->getIntent()) {
    $conv = $agent->getActionConversation();
    $option = $conv->getArguments()->get('OPTION');

    switch ($option) {
        case 'OPTION_1':
            $conv->close('You choose option 1');
            break;

        case 'OPTION_2':
            $conv->close('You choose option 2');
            break;

        case 'OPTION_3':
            $conv->close('You choose option 3');
            break;

        case 'OPTION_4':
            $conv->close('You choose option 4');
            break;

        default:
            $conv->close('Sorry, i do not understand');
            break;
    }
}
```

##### Browsing Carousel

[](#browsing-carousel)

Please see the documentation [here](https://developers.google.com/actions/assistant/responses#browsing_carousel).

> A browsing carousel is a rich response, similar to the carousel response as it scrolls horizontally and allows users to select a tile. Browsing carousels are designed specifically for web content by opening the selected tile in a web browser (or an AMP browser if all tiles are AMP-enabled). The browsing carousel will also persist on the user's Assistant surface for browsing later.

```
use Dialogflow\Action\Responses\BrowseCarousel;
use Dialogflow\Action\Responses\BrowseCarousel\Option;

$conv->ask('Please choose below');

$conv->ask(
    BrowseCarousel::create()
    ->imageDisplayOptions('CROPPED')
    ->addOption(
        Option::create()
        ->title('Title of item 1')
        ->description('Description of item 1')
        ->footer('Item 1 footer')
        ->url('http://www.example.com')
        ->image('https://picsum.photos/300/300')
    )
    ->addOption(
        Option::create()
        ->title('Title of item 2')
        ->description('Description of item 2')
        ->footer('Item 2 footer')
        ->url('http://www.example.com')
        ->image('https://picsum.photos/300/300')
    )
    ->addOption(
        Option::create()
        ->title('Title of item 3')
        ->description('Description of item 3')
        ->footer('Item 3 footer')
        ->url('http://www.example.com')
        ->image('https://picsum.photos/300/300')
    )
    ->addOption(
        Option::create()
        ->title('Title of item 4')
        ->description('Description of item 4')
        ->footer('Item 4 footer')
        ->url('http://www.example.com')
        ->image('https://picsum.photos/300/300')
    )
);
```

No follow-up fulfillment is necessary for user interactions with browse carousel items, since the carousel handles the browser handoff.

##### Suggestion Chip

[](#suggestion-chip)

Please see the documentation [here](https://developers.google.com/actions/assistant/responses#suggestion_chip).

```
use Dialogflow\Action\Responses\LinkOutSuggestion;
use Dialogflow\Action\Responses\Suggestions;

$conv->ask('Please choose');
$conv->ask(new Suggestions(['Suggestion 1', 'Suggestion 2']));
$conv->ask(new Suggestions('Suggestion 3'));
$conv->ask(new LinkOutSuggestion('Website', 'http://www.example.com'));
```

##### Media Responses

[](#media-responses)

Please see the documentation [here](https://developers.google.com/actions/assistant/responses#media_responses).

> Media responses let your Actions play audio content with a playback duration longer than the 120-second limit of SSML. The primary component of a media response is the single-track card.

```
use Dialogflow\Action\Responses\MediaObject;
use Dialogflow\Action\Responses\MediaResponse;
use Dialogflow\Action\Responses\Suggestions;

$conv->ask('Here you go');
$conv->ask(
    new MediaResponse(
        MediaObject::create('http://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3')
        ->name('Jazz in Paris')
        ->description('A funky Jazz tune')
        ->icon('http://storage.googleapis.com/automotive-media/album_art.jpg')
        ->image('http://storage.googleapis.com/automotive-media/album_art.jpg')
    )
);
$conv->ask(new Suggestions(['Pause', 'Stop', 'Start over']));
```

#### Helpers

[](#helpers)

##### User information

[](#user-information)

Please see the documentation [here](https://developers.google.com/actions/assistant/helpers#user_information).

```
use Dialogflow\Action\Questions\Permission;

$conv->ask(Permission::create('To address you by name and know your location', ['NAME', 'DEVICE_PRECISE_LOCATION']));
```

To check if the user granted you the information and then access the data, create a Dialogflow intent with the `actions_intent_PERMISSION` event. Assuming you name the intent as `Get Permission`, you can get the information as follow,

```
if ('Get Permission'==$agent->getIntent()) {
    $conv = $agent->getActionConversation();
    $approved = $conv->getArguments()->get('PERMISSION');

    if ($approved) {
        $name = $conv->getUser()->getName()->getDisplay();
        $latlng = $conv->getDevice()->getLocation()->getCoordinates();
        $lat = $latlng->getLatitude();
        $lng = $latlng->getLongitude();

        $conv->close('Got it, your name is ' . $name . ' and your coordinates are ' . $lat . ', ' . $lng);
    } else {
        $conv->close('Never mind then');
    }
}
```

##### Date and Time

[](#date-and-time)

Please see the documentation [here](https://developers.google.com/actions/assistant/helpers#date_and_time).

```
use Dialogflow\Action\Questions\DateTime;

$conv->ask(new DateTime('When do you want to come in?', 'What is the best date to schedule your appointment?', 'What time of day works best for you?'));
```

To check if the user granted access and then access the data, create a Dialogflow intent with the `actions_intent_DATETIME` event. Assuming you name the intent as `Get Date Time`, you can get the information as follow,

```
if ('Get Date Time'==$agent->getIntent()) {
    $conv = $agent->getActionConversation();
    $date = $conv->getArguments()->get('DATETIME');

    if ($date) {
        $conv->close('Ok, got it, i will see you at ' . $date->format('r'));
    } else {
        $conv->close('Never mind then');
    }
}
```

##### Place and Location

[](#place-and-location)

Please see the documentation [here](https://developers.google.com/actions/assistant/helpers#place_and_location).

```
use Dialogflow\Action\Questions\Place;

$conv->ask(new Place('Where do you want to have lunch?', 'To find lunch locations'));
```

To check if the user granted access and then access the data, create a Dialogflow intent with the `actions_intent_PLACE` event. Assuming you name the intent as `Get Place`, you can get the information as follow,

```
if ('Get Place'==$agent->getIntent()) {
    $conv = $agent->getActionConversation();
    $place = $conv->getArguments()->get('PLACE');

    if ($place) {
        $conv->close('Ok, got it, we\'ll meet at ' . $place->getFormattedAddress());
    } else {
        $conv->close('Never mind then');
    }
}
```

##### Confirmation

[](#confirmation)

Please see the documentation [here](https://developers.google.com/actions/assistant/helpers#confirmation).

```
use Dialogflow\Action\Questions\Confirmation;

$conv->ask(new Confirmation('Can you confirm?'));
```

To check if the user confirmed or not, create a Dialogflow intent with the `actions_intent_CONFIRMATION` event. Assuming you name the intent as `Get Confirmation`, you can get the information as follow,

```
if ('Get Confirmation'==$agent->getIntent()) {
    $conv = $agent->getActionConversation();
    $confirmed = $conv->getArguments()->get('CONFIRMATION');

    if ($confirmed) {
        $conv->close('Ok, it is confirmed');
    } else {
        $conv->close('Alright then, it is canceled');
    }
}
```

#### Surface Capabilities

[](#surface-capabilities)

Google Assistant can be used on a variety of surfaces such as mobile devices that support audio and display experiences or a Google Home device that supports audio-only experiences.

To design and build conversations that work well on all surfaces, use [surface capabilities](https://developers.google.com/actions/assistant/surface-capabilities) to control and scope your conversations properly.

```
$surface = $conv->getSurface();

if ($surface->hasScreen()) {
    // surface has screen
} elseif ($surface->hasAudio()) {
    // surface has audio
} elseif ($surface->hasMediaPlayback()) {
    // surface can play audio
} elseif ($surface->hasWebBrowser()) {
    // user can interact with the content in a web browser
}
```

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance15

Infrequent updates — may be unmaintained

Popularity52

Moderate usage in the ecosystem

Community24

Small or concentrated contributor base

Maturity66

Established project with proven stability

 Bus Factor1

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

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

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

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

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

###  Release Activity

Cadence

Every ~31 days

Recently: every ~71 days

Total

14

Last Release

2530d ago

Major Versions

0.0.9 → 1.0.02018-09-03

### Community

Maintainers

![](https://www.gravatar.com/avatar/e94ba030d171cd847ef09495b82a11ac82d3daf6a57e185a80ca0c8c18d23be0?d=identicon)[eristemena](/maintainers/eristemena)

---

Top Contributors

[![eristemena](https://avatars.githubusercontent.com/u/1290321?v=4)](https://github.com/eristemena "eristemena (93 commits)")[![mikepsinn](https://avatars.githubusercontent.com/u/2808553?v=4)](https://github.com/mikepsinn "mikepsinn (3 commits)")[![germanozambelli](https://avatars.githubusercontent.com/u/32094129?v=4)](https://github.com/germanozambelli "germanozambelli (1 commits)")[![kubacode](https://avatars.githubusercontent.com/u/4929328?v=4)](https://github.com/kubacode "kubacode (1 commits)")[![okaufmann](https://avatars.githubusercontent.com/u/4414498?v=4)](https://github.com/okaufmann "okaufmann (1 commits)")

---

Tags

bot-frameworkchatbotdialogflownluphpwebhookclient libraryfulfillmentapi.aiDialogflow

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/eristemena-dialogflow-fulfillment-webhook-php/health.svg)

```
[![Health](https://phpackages.com/badges/eristemena-dialogflow-fulfillment-webhook-php/health.svg)](https://phpackages.com/packages/eristemena-dialogflow-fulfillment-webhook-php)
```

###  Alternatives

[spatie/laravel-webhook-server

Send webhooks in Laravel apps

1.1k8.8M22](/packages/spatie-laravel-webhook-server)[iboldurev/dialogflow

Dialogflow php sdk

16540.5k1](/packages/iboldurev-dialogflow)[scullwm/mailhookbundle

A bundle to catch API webhook from differents mail service

4020.5k](/packages/scullwm-mailhookbundle)

PHPackages © 2026

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