Skip to main content
Version: 5.x

Developing external packages

Introduction​

BowPHP's package system lets you extend your application by plugging in reusable external modules. This modular architecture makes maintenance easier and helps you share functionality across projects.

Creating a package​

Generating the configuration​

To create a new package, use the add:configuration command:

php bow add:configuration PluginConfiguration

The PluginConfiguration.php file will be created in the app/Configurations folder. This file contains two essential methods:

MethodSignatureDescription
createcreate(Loader $config): voidInitializes the package (registering bindings, configuration)
runrun(): voidStarts the package (defining routes, launching services)

Lifecycle​

When the application boots, Bow runs the configurations in this order:

  1. Calls create() on all packages
  2. Calls run() on all packages (in the same order)
Warning

In the create() method, do not rely on dependencies from other packages that have not yet been configured.

Accessing the container​

In the configuration file, the dependency injection container is accessible through $this->container.

Complete example​

Let's build an email-checking package with a dedicated service.

Step 1: Create the configuration​

php bow add:configuration EmailCheckerConfiguration

Step 2: Create the service​

Create the app/Packages/EmailChecker folder and add the EmailCheckerService.php file:

app/Packages/EmailChecker/EmailCheckerService.php
<?php

declare(strict_types=1);

namespace App\Packages\EmailChecker;

use Bow\Support\Str;

class EmailCheckerService
{
/**
* List of blocked domains
*
* @var array
*/
private array $blockedDomains = [];

/**
* Constructor
*
* @param array $blockedDomains
*/
public function __construct(array $blockedDomains = [])
{
$this->blockedDomains = $blockedDomains;
}

/**
* Check whether the email is valid
*
* @param string $email
* @return bool
*/
public function isValid(string $email): bool
{
return Str::isMail($email);
}

/**
* Check whether the domain is allowed
*
* @param string $email
* @return bool
*/
public function isDomainAllowed(string $email): bool
{
if (!$this->isValid($email)) {
return false;
}

$domain = substr($email, strpos($email, '@') + 1);

return !in_array($domain, $this->blockedDomains);
}

/**
* Fully validate an email
*
* @param string $email
* @return array
*/
public function validate(string $email): array
{
if (!$this->isValid($email)) {
return [
'valid' => false,
'message' => 'Format d\'email invalide',
'email' => $email
];
}

if (!$this->isDomainAllowed($email)) {
return [
'valid' => false,
'message' => 'Ce domaine n\'est pas autorisΓ©',
'email' => $email
];
}

return [
'valid' => true,
'message' => 'Email valide',
'email' => $email
];
}
}

Step 3: Create the controller​

app/Packages/EmailChecker/EmailCheckerController.php
<?php

declare(strict_types=1);

namespace App\Packages\EmailChecker;

use Bow\Http\Request;

class EmailCheckerController
{
/**
* Constructor
*
* @param EmailCheckerService $service
*/
public function __construct(
private EmailCheckerService $service
) {
}

/**
* Check a single email
*
* @param Request $request
* @return array
*/
public function check(Request $request): array
{
$email = $request->get('email', '');

return $this->service->validate($email);
}

/**
* Check several emails
*
* @param Request $request
* @return array
*/
public function checkBulk(Request $request): array
{
$emails = $request->get('emails', []);
$results = [];

foreach ($emails as $email) {
$results[] = $this->service->validate($email);
}

return [
'total' => count($emails),
'valid' => count(array_filter($results, fn($r) => $r['valid'])),
'results' => $results
];
}
}

Step 4: Create the configuration file​

config/email-checker.php
<?php

return [
/**
* List of blocked domains
*/
'blocked_domains' => [
'spam.com',
'fake-email.com',
'temporary-mail.org',
],
];

Step 5: Configure the package​

app/Configurations/EmailCheckerConfiguration.php
<?php

declare(strict_types=1);

namespace App\Configurations;

use App\Packages\EmailChecker\EmailCheckerController;
use App\Packages\EmailChecker\EmailCheckerService;
use Bow\Configuration\Configuration;
use Bow\Configuration\Loader;

class EmailCheckerConfiguration extends Configuration
{
/**
* Initialize the package
*
* @param Loader $config
* @return void
*/
public function create(Loader $config): void
{
// Files in config/ are loaded automatically.
// bind() caches the service after the first resolution β€” this is
// Bow's "singleton". For a new instance on every call,
// use factory() instead.
$this->container->bind(EmailCheckerService::class, function () use ($config) {
$blockedDomains = $config['email-checker.blocked_domains'] ?? [];

return new EmailCheckerService($blockedDomains);
});
}

/**
* Start the package
*
* @return void
*/
public function run(): void
{
$router = $this->container->make('router');

// Route to check a single email
$router->post('/email/check', 'App\\Packages\\EmailChecker\\EmailCheckerController::check');

// Route to check several emails
$router->post('/email/check-bulk', 'App\\Packages\\EmailChecker\\EmailCheckerController::checkBulk');
}
}
Automatic configuration loading

PHP files placed in the config/ folder are loaded automatically by BowPHP. Access them with dot notation: $config['email-checker.blocked_domains'].

Dependency injection

The EmailCheckerController controller automatically receives the EmailCheckerService service thanks to dependency injection. The container resolves constructor dependencies automatically.

Step 6: Register the configuration​

Add your configuration in app/Kernel.php:

public function configurations(): array
{
return [
// ... other configurations
\App\Configurations\EmailCheckerConfiguration::class,
];
}

Step 7: Test the package​

Start the development server:

php bow run:server

Test it with curl:

# Check a valid email
curl -X POST -d "email=john@example.com" http://localhost:8080/email/check
# {"valid": true, "message": "Email valide", "email": "john@example.com"}

# Check an invalid email
curl -X POST -d "email=invalid-email" http://localhost:8080/email/check
# {"valid": false, "message": "Format d'email invalide", "email": "invalid-email"}

# Check a blocked domain
curl -X POST -d "email=user@spam.com" http://localhost:8080/email/check
# {"valid": false, "message": "Ce domaine n'est pas autorisΓ©", "email": "user@spam.com"}

# Check several emails
curl -X POST \
-H "Content-Type: application/json" \
-d '{"emails": ["john@example.com", "invalid", "user@spam.com"]}' \
http://localhost:8080/email/check-bulk
# {"total": 3, "valid": 1, "results": [...]}

Package structure​

Here is the final structure of the package:

app/
β”œβ”€β”€ Configurations/
β”‚ └── EmailCheckerConfiguration.php
└── Packages/
└── EmailChecker/
β”œβ”€β”€ EmailCheckerController.php
└── EmailCheckerService.php
config/
└── email-checker.php

See also​

Is something missing?

If you run into problems with the documentation or have suggestions to improve the documentation or the project in general, please open an issue for us, or send a tweet mentioning the Twitter account @bowframework or directly on github.