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:
| Method | Signature | Description |
|---|---|---|
create | create(Loader $config): void | Initializes the package (registering bindings, configuration) |
run | run(): void | Starts the package (defining routes, launching services) |
Lifecycleβ
When the application boots, Bow runs the configurations in this order:
- Calls
create()on all packages - Calls
run()on all packages (in the same order)
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:
<?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β
<?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β
<?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
{
/**
* 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');
}
}
PHP files placed in the config/ folder are loaded automatically by BowPHP. Access them with dot notation: $config['email-checker.blocked_domains'].
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β
- Dependency injection container - Understand BowPHP's DIC
- Services - Create and use services
- Configuration - Manage configuration files
- Routing - Define 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.