kirschbaum-development/bee-plugin-php-client
A PHP client for the Bee Plugin email editor API, providing convenient methods to configure and integrate Beefree’s drag-and-drop email builder into PHP and Laravel applications.
Composer install (pin the minor — see Gotchas):
composer require kirschbaum-development/bee-plugin-php-client:^0.3
Note the version: this is 0.3.x, not 1.x. If you were following an earlier write-up that pinned ^1.0, that constraint does not resolve — the package has never left 0.x. Everything below assumes 0.3.1, which is the first release with PHP 8 compatibility.
Where to look first, in order:
src/ — find the client class (typically something like BeeClient / BeePluginClient) and any Contracts/Exceptions directory. Method signatures here are the real API docs, not the README.composer.json — check the PHP constraint (>=8.0) and the guzzlehttp/guzzle constraint you're inheriting. This is the file the 0.3.1 release actually changed.CHANGELOG.md / the release diff — 0.3.1 is a one-line "PHP 8 compatibility" release; read the diff to see whether it was a composer.json bump, real code fixes, or both. That tells you how much to trust the rest of the code on PHP 8.1+.README.md — usually a single "hello world" call; illustrative only.tests/ — if present, fixtures show you the real response shapes better than any docs.Configure via env, not code:
BEE_PLUGIN_API_URL=https://api.bee.example.com
BEE_PLUGIN_API_KEY=xxxxx
If the package ships a config file, publish it; if it doesn't, create your own config/bee.php and pass values into the client constructor explicitly rather than relying on globals.
First use case — smoke test before you build anything on top:
$client = new BeePluginClient(
baseUrl: config('bee.base_url'),
apiKey: config('bee.api_key'),
);
// verify auth + connectivity with the cheapest read call available
$plugins = $client->plugins()->all(); // illustrative signature — confirm in src/
dump($plugins);
Run this on the exact PHP version your app targets (8.2/8.3, not just 8.0). "PHP 8 compatibility" in a release note usually means "it constructs without fatals," not "every method is deprecation-free." If the smoke test throws, stop: the API contract or host has changed and you're now debugging a dead integration, not writing application code.
Bind a singleton in a service provider. Never new the client at call sites — it holds credentials and (usually) a Guzzle instance you don't want duplicated.
// AppServiceProvider::register()
$this->app->singleton(BeePluginClient::class, fn () => new BeePluginClient(
baseUrl: config('bee.base_url'),
apiKey: config('bee.api_key'),
));
Wrap it in a narrow, app-owned service. The package is barely maintained; if the API drifts or you swap the client for a fork, every call site shouldn't change.
final class BeePlugins
{
public function __construct(private BeePluginClient $client) {}
public function active(): Collection
{
return collect($this->client->plugins()->all())
->filter(fn ($p) => $p->status === 'active');
}
public function install(string $slug): void
{
$this->client->plugins()->install($slug);
}
}
Treat mutating calls as jobs. Install/activate/update are slow and remote — queue them with retries and a timeout, and log the request ID.
class InstallBeePlugin implements ShouldQueue
{
public $tries = 3;
public $backoff = [10, 60, 300];
public $timeout = 120;
public function handle(BeePlugins $plugins): void
{
$plugins->install($this->slug);
}
}
Return typed results where you can. The client almost certainly returns arrays or stdClass; normalize into your own DTOs at the boundary so the rest of the app isn't coupled to the wire format.
Test against a fake HTTP layer, not the real client. If it's built on Guzzle, inject a client with a MockHandler; if Laravel-flavored, Http::fake(). Assert on the outgoing request (path, auth header) and stub the response.
Http::fake([
'*/plugins' => Http::response([['slug' => 'newsletter', 'status' => 'active']], 200),
]);
$this->assertCount(1, app(BeePlugins::class)->active());
Log at the boundary, once. Wrap calls in a small decorator that logs method, duration, and status — not inside every call site.
0.x means minor bumps can break you. SemVer permits breaking changes in 0.2 → 0.3 and even in 0.3.1 → 0.3.2. Pin 0.3.* (or the exact tag) plus a committed composer.lock, and read every diff before bumping. This is a materially different risk profile from depending on a 1.x package.0.3.1 is the PHP 8 floor, not a PHP 8 guarantee. If you're on PHP 8.1+, you need at least this release — earlier 0.3.x tags predate it and may fatal or spew deprecations under 8.2 (dynamic properties, ${var} interpolation, strlen(null)). But a one-line "compatibility" commit rarely clears every deprecation. Run your smoke test on your target PHP with error_reporting(E_ALL) and watch the deprecation log.composer.json doesn't still pin guzzlehttp/guzzle:^6 or an early ^7.0. A PHP-8-compatible package that drags a Guzzle version conflicting with the rest of your graph will still fail composer update. Resolve this before you write any code.Exceptions. Distinguish transport errors (timeouts, DNS) from API errors (4xx/5xx, validation) yourself — don't let a 429 surfacing as an install failure confuse your retry logic.RetryMiddleware; respect Retry-After on 429/503..env, never committed. If the client logs request payloads on failure, make sure the auth header is scrubbed.->getBody()->getContents()), and confirm the base URL doesn't have a trailing slash mismatch (…/api vs …/api/) — the classic cause of double-slash 404s in hand-rolled clients.0.x versioning, and size, the maintenance surface of a fork is often smaller than the risk of owning an unmaintained dependency — and you'd already be pinning exact versions to get the same safety.How can I help you explore Laravel packages today?