I'm not new to PHP or programming at all. But recently I was thinking about website programming in PHP and how easier it was before the OOP. Anyway, I prefer OOP than the old procedural style. I want to implement a website but it seems I always have to use a global or a static variables. And I'm starting to wonder, how can I do it without those?
Anyway, what I'm talking about is having a class for each "component" of the website. For example if it was an url shortener website it would be: links, members, database.
What I'm talking about is way more complicated, at least 8 classes. Anyway, my current approach is the following:
$database = new Database(...);
$links = new Links($db);
$users = new Users($db);
Anyway, for example I want to get all the links a user posted by its ID, I need to use both links and both users components.
Is there any other way I could do this? Any other approach? except passing them as constructor parameters.
You should have the following components:
Business objects, which model and express one particular "thing" in your app:
class Link { ... }
class User { ... }
These don't "do" anything, they're just there to formalise your data structures. These objects have getter and setter methods to get and set individual attributes, which are also validated there:
public function setUrl($url) {
if (!/* validate the URL here*/) {
throw new InvalidArgumentException("$url is not a valid URL");
}
$this->url = $url;
}
Minimum required data is part of the constructor. This ensures your data integrity application-wide. It allows you to assert that when you have an instance of Link
, the data expressed by it is minimum valid data for a link.
A database link. Only the bare necessary thing to connect to a database, nothing more, nothing less. A raw PDO
or mysqli
object will do just fine.
A data-object mapper, which takes a database link and knows how to store business objects in the database and how to retrieve them:
class LinkStorage {
protected $db;
public function __construct(PDO $db) {
$this->db = $db;
}
}
This class has all the various methods of how to retrieve things from your database:
public function getById($id) {
$stmt = $this->db->prepare('SELECT ... FROM ... WHERE id = :id');
$stmt->execute(compact('id'));
if (!$data = $stmt->fetch()) {
throw new RuntimeException("Record with id $id does not exist");
}
return new Link($data['url']);
}
You can have all sorts of different queries encapsulated this way, e.g.:
/**
* Returns all links by a particular user.
* @param User $user
* @return Link[]
*/
public function getAllFromUser(User $user) {
...
}
The usage is then simple:
$db = new PDO(...);
$linkStorage = new LinkStorage($db);
$userStorage = new UserStorage($db);
$user = $userStorage->getById($id);
$links = $linkStorage->getAllFromUser($user);
This kind of code would then be encapsulated in a service class, which holds all the possible "actions" you can do in your app. registerUser(array $data)
, getLinksOfUser($id)
, newLinkFromPostData(array $data)
etc.
What I've just described is basically the model portion of an MVC-style application. The other two parts would be controllers which call the service methods, and views which output data retrieved from service methods. This approach keeps responsibilities separate and isolated and allows you to put higher-level logic and functionality together like building blocks. Business objects are the lowest building block, their structure needs to be solid and well defined for the rest to work. Data-object mappers just concern themselves with putting those objects into the database and getting them back out again. Services then put all this together in various complex ways and make things happen.
You shouldn't have any cyclic dependencies with this, as responsibilities are well separated. Your individual dependencies may still be somewhat complex. If it becomes too cumbersome to instantiate classes, you'll want to look into Factories:
$factory = new Factory;
$userStorage = $factory->newUserStorage();
All the complexity of instantiation is encapsulated in this factory. One step further are dependency injection containers, who you can configure in, for example, an XML file to specify which class depends on what, and then the DI container will take care of it for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With