The Certificates facade
RoundlyConsulting\Certificates\Facades\Certificates is the recommended entry point. Its root is CertificatesManager, a container singleton, and every state-changing method runs through an action — so the facade, an injected manager and Certificates::fake() all see the same calls. A tour of the surface:
use RoundlyConsulting\Certificates\DataTransferObjects\IssueCertificateData;
use RoundlyConsulting\Certificates\Facades\Certificates;
// Issue
Certificates::issueIfMissing('app.example.com');
Certificates::issue(IssueCertificateData::make('app.example.com'));
// Lifecycle — a registry Certificate or a domain (its most recent row)
Certificates::renew('app.example.com');
Certificates::renewLater('app.example.com');
Certificates::revoke($certificate, 'key compromise');
Certificates::expire('app.example.com');
// The whole registry
Certificates::expiring(14); // Collection<int, Certificate>, soonest first
Certificates::renewDue(); // RenewalReport
Certificates::sync('kubernetes'); // int rows written
Certificates::prune(30); // int rows soft-deleted
// One domain — fluent issuance, reads and the same lifecycle verbs
Certificates::for('app.example.com')->using('acme')->issue();
Certificates::for('app.example.com')->renew();
// Reads, drivers and scoping
Certificates::find('app.example.com');
Certificates::statusReport('app.example.com', fresh: true);
Certificates::driver('acme');
Certificates::extend('vault', fn () => new VaultCertificateProvider());
Certificates::on('tenant')->renewDue();Issuance and lifecycle
Every lifecycle verb takes a registry Certificate or a domain, which resolves to its most recent row on the bound connection — matched as the primary domain exactly, so a host that is only a SAN of another certificate is not found. An unknown domain, or a status that cannot make the move (renewing a revoked certificate, say), throws CertificateException — renewLater() included: it checks the status before it queues anything:
| Method | Returns | Purpose |
|---|---|---|
issue(IssueCertificateData $data) | Certificate | Provision and record from a DTO; throws ProvisioningInProgressException while another process provisions the same certificate, and CertificateException when another domain already holds its name. |
issueIfMissing(string $domain) | Certificate | The active certificate already covering the domain — its own row, else a SAN certificate listing it — or a new issuance. |
generate(string $domain) | bool | Provision — full lifecycle with a registry, straight through the provider without one; false while the certificate’s lock is held. |
renew(Certificate|string $certificate) | Certificate | Issued/Renewed/Failed (or a stuck Renewing row) → Renewed, now, through the certificate’s own driver, under its provisioning lock — a concurrent renewal throws ProvisioningInProgressException. |
renewLater(Certificate|string $certificate) | Certificate | Check the status now, then queue RenewCertificateJob on renewal.queue. |
renewDue(?int $thresholdDays = null, bool $queue = false) | RenewalReport | Renew or queue everything expiring within the threshold or already past expiry (failed and stuck renewing rows included); one failure never stops the rest. |
revoke(Certificate|string $certificate, ?string $reason = null) | Certificate | Issued/Renewed → Revoked, fires CertificateRevoked. Registry only — the CA is not contacted. |
expire(Certificate|string $certificate) | Certificate | Issued/Renewed → Expired, fires CertificateExpired. |
sync(?string $driver = null) | int | Pull a driver’s live certificates into the registry, settling in-flight rows with their events; never un-revokes a row. Returns rows written. |
prune(int $days = 30, CertificateStatus|string|null $status = null) | int | Soft-delete stale expired, failed and revoked rows — or exactly $status. |
The domain handle, reads and drivers
| Method | Returns | Purpose |
|---|---|---|
for(string|array $domain) | CertificateBuilder | The domain handle — fluent issuance, reads and the lifecycle verbs. |
find(string $domain, ?string $driver = null) | ?Certificate | Latest registry record with the domain as its primary domain. |
status(string $domain, ?string $driver = null) | ?CertificateStatus | Registry status of the certificate covering the domain — its own row first, else the newest SAN certificate listing it. |
statusReport(string $domain, ?string $driver = null, bool $fresh = false) | ?CertificateStatusReport | Live, cache-aware status from the provider. |
expiring(?int $days = null, ?string $driver = null) | Collection<Certificate> | Issued, Renewed and Failed rows (and stuck Renewing ones) expiring within $days or already past expiry (default renewal.threshold_days), soonest first. |
get() | Collection<RemoteCertificate> | Certificates the default driver manages. |
exists(string $domain, ?string $driver = null) | bool | Provider check on $driver (the default driver when null). |
monitorExpiry(Certificate $certificate, ?Model $notifiable = null) | PendingScheduledCheck | Schedule an alerts expiry monitor on the certificate’s own connection. |
driver(?string $name = null) | CertificateProvider | Resolve a provider (default when null). |
extend(string $driver, Closure $callback) | CertificatesManager | Register a custom driver on CertificateProviderManager. |
on(?string $connection) | CertificatesManager | Connection-bound clone for multi-tenant registries. |
certificateName(string $domain) | string | Deterministic, lowercase DNS-1123 certificate name (*. becomes wildcard-, at most 253 characters). |
fake() | CertificatesFake | Swap in the in-memory recording fake (see Testing). |
Certificates::for($domain) is a handle for one domain (or a SAN set led by its first domain): fluent issuance, reads, and renew(), renewLater(), revoke() and expire() on that domain’s row — see Fluent builder. With using($driver) it only ever touches that driver’s row.
The facade also exposes the fake-only seed(), failRenewalOf() and every assert* method once Certificates::fake() is active (see Testing). The manager is macroable, so your own methods show up on the facade too (see Extending).
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.