Abstraction — hiding implementation detail behind a stable interface
Beginner5 min read·eng-16-009
interviewsolid
Concept
Abstraction — hiding implementation details behind a stable, simpler interface. Users of an abstraction see only WHAT it does, not HOW it does it.
Levels of abstraction:
- A function is an abstraction:
sort($array)hides the sorting algorithm. - A class is an abstraction:
File::read($path)hides file system calls. - An interface is an abstraction:
LoggerInterface::log($msg)hides whether it logs to file, DB, or Slack. - A module/service is an abstraction: "The payment service" hides Stripe, PayPal, or any gateway.
Why abstraction is valuable:
- Reduced complexity: Callers only need to understand the interface, not the implementation.
- Changeability: Swap the implementation without changing callers. Replace
FileLoggerwithSlackLoggereverywhere by just changing what you bind in the container. - Testability: Code depending on an interface can be tested with a test double.
- Separation of concerns: The caller doesn't need to know about database connections, HTTP clients, file handles.
The right level of abstraction: Too low = leaking details. Too high = hiding things that callers need to know. Finding the right level is a design skill.
PHP mechanisms for abstraction:
- Interfaces:
interface LoggerInterface { public function log(string $msg): void; } - Abstract classes: Partial implementation, force subclasses to fill in details.
- Facades (Laravel):
Log::info('...')is a facade abstraction over the log manager. - Service classes: Abstract away external APIs.
Abstraction ≠ complexity: A GOOD abstraction simplifies. A BAD abstraction (over-engineering, wrong level) adds complexity. The goal is simplicity at the point of use.
Code Example
php
<?php
// LOW ABSTRACTION — implementation exposed everywhere
$curl = curl_init('https://api.stripe.com/v1/charges');
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Authorization: Bearer sk_test_...']);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query(['amount' => 1000, 'currency' => 'usd']));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($curl), true);
// Every payment use-case duplicates this — tight coupling to Stripe's API!
// HIGH ABSTRACTION — implementation hidden
interface PaymentGateway
{
public function charge(int $amountCents, string $currency, string $token): PaymentResult;
public function refund(string $chargeId): void;
}
// Implementation detail hidden from callers
class StripeGateway implements PaymentGateway
{
public function __construct(private readonly \Stripe\StripeClient $client) {}
public function charge(int $amountCents, string $currency, string $token): PaymentResult
{
$charge = $this->client->charges->create([
'amount' => $amountCents,
'currency' => $currency,
'source' => $token,
]);
return new PaymentResult(success: true, transactionId: $charge->id);
}
public function refund(string $chargeId): void
{
$this->client->refunds->create(['charge' => $chargeId]);
}
}
// Caller only knows the interface — abstracted from Stripe
class CheckoutService
{
public function __construct(private readonly PaymentGateway $gateway) {}
public function checkout(Cart $cart, string $token): Order
{
$result = $this->gateway->charge($cart->totalCents(), 'usd', $token);
// No knowledge of Stripe, curl, or API keys here
return Order::create([
'total' => $cart->total(),
'payment_method' => $result->transactionId,
]);
}
}
// ABSTRACTION LEVELS in PHP built-ins:
strlen('hello'); // low-level abstraction over C string operations
\Carbon\Carbon::now(); // abstraction over PHP DateTime and timezone handling
\Illuminate\Support\Facades\Storage::put($path, $contents); // abstraction over S3, local, SFTP