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 allows features to be shared 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:
| Method | Signature | Description |
|---|---|---|
create | create(Loader $config): void | Initializes the package (registering bindings, configuration) |
run | run(): void | Launches the package (defining routes, starting services) |
Life cycleβ
When the application starts, Bow runs the configurations in this order:
- Call
create()on all packages - Call
run()on all packages (in the same order)
In the create() method, do not use dependencies from other packages that are not yet configured.
Accessing the containerβ
In the configuration file, the dependency injection container is accessible via $this->container.
Complete exampleβ
Let's create an email verification 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:
<?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;
}
/**
* Checks whether the email is valid
*
* @param string $email
* @return bool
*/
public function isValid(string $email): bool
{
return Str::isMail($email);
}
/**
* Checks 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 validates 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β
<?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
) {
}
/**
* Checks a single email
*
* @param Request $request
* @return array
*/
public function check(Request $request): array
{
$email = $request->get('email', '');
return $this->service->validate($email);
}
/**
* Checks multiple 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β
<?php
return [
/**
* List of blocked domains
*/
'blocked_domains' => [
'spam.com',
'fake-email.com',
'temporary-mail.org',
],
];
Step 5: Configure the packageβ
<?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
{
/**
* Package initialization
*
* @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);
});
}
/**
* Package launch
*
* @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 multiple emails
$router->post('/email/check-bulk', 'App\\Packages\\EmailChecker\\EmailCheckerController::checkBulk');
}
}
PHP files placed in the config/ folder are loaded automatically by BowPHP. Access them using dot notation: $config['email-checker.blocked_domains'].
The EmailCheckerController controller automatically receives the EmailCheckerService service thanks to dependency injection. The container automatically resolves the constructor's dependencies.
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 with curl:
# Check a valid email
curl -X POST -d "[email protected]" http://localhost:8080/email/check
# {"valid": true, "message": "Email valide", "email": "[email protected]"}
# 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 protected]" http://localhost:8080/email/check
# {"valid": false, "message": "Ce domaine n'est pas autorisΓ©", "email": "[email protected]"}
# Check multiple emails
curl -X POST \
-H "Content-Type: application/json" \
-d '{"emails": ["[email protected]", "invalid", "[email protected]"]}' \
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β
- Dependency injection container - Understanding BowPHP's DIC
- Services - Creating and using services
- Configuration - Managing configuration files
- Routing - Defining routes in your application
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.