Skip to content

Instantly share code, notes, and snippets.

@azjezz
Last active February 22, 2019 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azjezz/6ad9c4df52645137154166053bece88f to your computer and use it in GitHub Desktop.
Save azjezz/6ad9c4df52645137154166053bece88f to your computer and use it in GitHub Desktop.
ok, so here i'm gonna try to explain interfaces and abstract classes for you in the dummy way.
interfaces are used when you are gonna have multiple implementation of the same thing.
for example :
<?php declare(strict_types=1);
namespace Dummy\Cache;
interface CacheInterface {
public function get(string $name)/*: mixed*/;
public function set(string $name, /*mixed*/ $value): void;
}
class ArrayCache implements CacheInterface {
private /*array<string, mixed>*/ $data = [];
public function get(string $name)/*: mixed*/ {
return $this->data[$name] ?? null;
}
public function set(string $name, /*mixed*/ $value): void {
$this->data[$name] = $value;
}
}
class FileCache implements CacheInterface {
private /*string*/ $dir;
public function __construct(string $dir) {
$this->dir = $dir;
}
public function get(string $name)/*: mixed*/ {
// don't actually do this in real life
$file = $this->dir . '/' . md5($name);
if (file_exists($file)) {
$data = file_get_contents($file);
return unserialize($data);
}
return null;
}
public function set(string $name, /*mixed*/ $value): void {
$file = $this->dir . '/' . md5($name);
$data = serialize($value);
file_put_contents($file, $data);
}
}
class RedisCache implements CacheInterface {
private /*Redis*/ $redis;
public function __construct(Redis $redis) {
$this->redis = $redis;
}
public function get(string $name)/*: mixed*/ {
$data = $this->redis->get($name);
if (false === $data) {
return null;
}
return unserialize($data);
}
public function set(string $name, /*mixed*/ $value): void {
$data = serialize($value);
$this->redis->set($name, $data);
}
}
?>
okay, here we have a cache interface, and 3 implementation, you would probably use array cache for testing, file for dev and redis in production.
but whats the point of interfaces here ? well, you wouldn't need to typehint against a specific implementation, in you controller, you would have this :
<?php declare(strict_types=1);
namespace App;
use Dummy\Cache\CacheInterface;
class Controller {
private $cache;
public function __construct(CacheInterface $cache) {
$this->cache = $cache;
}
public function someAction(): ResponseInterface {
// you can use ->get and ->set, knowing that it would working
// without knowing which implementation you are using
}
}
?>
since you mentioned that you are using laravel, you are already using interfaces when you configure you database to use
`mysql`, `sqlite`, or others, you are using different implementations of the same interface.
same with cache and everything, you can look at the `Contract` sub-namespace in laravel to see all the interfaces.
but what about abstract classes ?
we go back to the cache example, if you look closely, you will notice that both FileCache and RedisCache serialize and unserialize data the same way, so you have the same thing being done twice for a cache implementation, for that we can use an abstract class, example :
<?php declare(strict_types=1);
namespace Dummy\Cache;
abstract class AbstractCache implements CacheInterface {
public function get(string $name) {
$ret = $this->doGet($name);
if ($ret === null) {
return $ret;
} else {
return unserialize($ret);
}
}
public function set(string $name, $value): void {
$this->doSet($name, serialize($value));
}
abstract protected function doGet(string $key): ?string;
abstract protected function doSet(string $key, string $data): void;
}
class FileCache implements CacheInterface {
private /*string*/ $dir;
public function __construct(string $dir) {
$this->dir = $dir;
}
public function doGet(string $name): ?string {
// don't actually do this in real life
$file = $this->dir . '/' . md5($name);
if (file_exists($file)) {
return file_get_contents($file);
}
return null;
}
public function doSet(string $name, string $data): void {
$file = $this->dir . '/' . md5($name);
file_put_contents($file, $data);
}
}
class RedisCache extends AbstractCache {
private /*Redis*/ $redis;
public function __construct(Redis $redis) {
$this->redis = $redis;
}
public function doGet(string $name): ?string {
$data = $this->redis->get($name);
return $data === false ? null : $data;
}
public function doSet(string $name, string $data): void {
$this->redis->set($name, $data);
}
}
?>
okay, we have less code in both file and redis cache implementations, but what's the point ?
well, here we can mantain the serialization process without having to touch any of the cache implementaions.
but if you wanna have a more mantainable code, you would define a `SerializerInterface` with
`serialize($value): string` and `unserialize(string $serialized)` methods, and this is actually is being
done in Symfony framework and other real-world cache implementation.
i myself can build a full functional website without having to declare an interface or an abstract class, but i won't as it won't be mantainable as much.
sometimes you might see laravel, symfony and other frameworks/libraries defining interfaces for something that have only 1 implementation.
for this, let's take the symfony kernel.
we have the interface :
and the implementation :
but what's the point if there's only 1 implementation ? why type hint against the interface ?
for example, let's make a function that runs the symfony kernel in general env.
<?php declare(strict_types=1);
namespace App;
use Symfony\Component\HttpKernel;
use Symfony\Component\HttpFoundation;
function run(
HttpKernel\HttpKernelInterface $kernel,
HttpFoundation\Request $request
): void {
$response = $kernel->handle($request);
$response->send();
if ($kernel instanceof HttpKernel\TerminableInterface) {
$kernel->terminate($request, $response);
}
exit(0);
}
// later in index.php :
App\run($kernel, $request);
?>
okay, this would work with symfony, but believe it or not, if you pass a laravel kernel and request, it would work.
why ? because laravel kernel implements the symfony kernel interface, and laravel request object, extends symfonys.
not just that, you can also use this for any application/framework that does implement it, and there's too many.
just look here : https://symfony.com/projects
in the end, interfaces help us keep interoperability, and this is why we have https://www.php-fig.org/ and their PSRs ( https://www.php-fig.org/psr/ )
if have no idea if i'm doing this right
i'm not good at explaning things to people,
but idk
i hope it helped shine some light
but yea
they are useful, but don't use them everywhere, example : when building an application, you don't need an interface for `BlogPostController`
as you won't have multiple implementations but make sure to type hint against interfaces instead of implementations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment