marko/encryption
Encryption support for the Marko framework, providing a simple service-provider integration for encrypting and decrypting application data with configurable keys and ciphers, plus helpers for secure serialization of values across your app.
marko/encryption is a contracts-only package: it ships the interfaces (and possibly a small exception class) that define how encryption is exposed throughout the Marko framework, but no concrete implementation. You rarely install it directly — it arrives transitively when you require the framework or the actual encrypter implementation (marko/encryption-implementation or equivalent).
composer require marko/encryption
src/ — the entire surface is one or two interfaces (expect something like EncrypterInterface with encrypt() / decrypt()). Read those signatures before anything else; they are the contract you code against.use Marko\Encryption\Contracts\EncrypterInterface;
final class ApiTokenVault
{
public function __construct(private readonly EncrypterInterface $encrypter) {}
public function store(string $token): string
{
return $this->encrypter->encrypt($token);
}
}
That is the whole day-to-day loop: depend on the contract, receive the implementation from the container, never reference a concrete class.Depend on the contract, not the class. Every consumer (services, jobs, listeners, console commands) should type-hint EncrypterInterface. This is the single most important pattern because the package has zero dependents — swapping implementations is expected, and hard-coding a class defeats the purpose.
Bind the implementation once. In your application service provider:
$this->app->bind(EncrypterInterface::class, function ($app) {
return new OpenSslEncrypter($app['config']->get('encryption.key'));
});
Keep the key out of code — source it from config/env, and rotate it with a versioned keyring if the implementation supports it.
Encrypt at the boundary. Treat the encrypter as a persistence/transport edge concern. Encrypt in a repository or a dedicated EncryptedCast/value object, decrypt on read. Keep domain entities plain. Example pattern:
final class EncryptedString
{
public function __construct(private string $ciphertext) {}
public static function fromPlain(string $plain, EncrypterInterface $e): self
{
return new self($e->encrypt($plain));
}
public function reveal(EncrypterInterface $e): string
{
return $e->decrypt($this->ciphertext);
}
}
Wrap for testability. Because it's an interface, write a trivial fake in tests:
final class PlaintextEncrypter implements EncrypterInterface
{
public function encrypt(string $value): string { return $value; }
public function decrypt(string $value): string { return $value; }
}
Bind it in the test container. You avoid managing real keys in unit tests and keep assertions readable.
Queue/job payloads. If you serialize encrypted values into queued jobs, decrypt inside the job handler, not at dispatch, so keys can rotate without invalidating in-flight jobs.
Migration workflow. Encrypting an existing column means: add a nullable ciphertext column, backfill via a command that pulls EncrypterInterface from the container, dual-read during rollout, then drop the plaintext column. Do the backfill in batched chunks — encryption is CPU-bound and per-row overhead is real.
composer require marko/encryption alone and try to instantiate something, you’ll get an interface you can’t new. You must also pull an implementation package (or write your own class satisfying the interface) and bind it. The absence of dependents confirms it’s a leaf contract, not a working encrypter.new-ing a concrete encrypter means you skip key/config wiring and quietly break rotation.WHERE encrypted_col = ? won’t work. For searchable fields, store a separate deterministic hash/HMAC alongside the ciphertext, and document the trade-off.TEXT/BLOB-appropriate columns, and base64-encode if you’re putting it in URLs, JSON, or headers. Decrypting a truncated or re-encoded value throws — treat decrypt() failures as data-integrity errors, not user-input errors.v1:...) so you can rotate later. Losing the key means unrecoverable data — back it up out-of-band.decrypt() in a small try/catch that maps “invalid payload / bad MAC” to a domain error so callers can distinguish tampering from a missing key.EncryptionServiceProvider or config (config/encryption.php), override the binding there rather than subclassing an implementation. If you need a custom cipher, implement the interface and rebind — that’s the intended extension seam, and it’s why the contract exists.How can I help you explore Laravel packages today?