Xifanie Xifanie - 26 days ago 9
PHP Question

Split large classes or load them entirely for a single function call?

I'd like to know if it is worthwhile to split a large class into different sections, and only use them when required. If so, what would be the best option?

I just want to say that I've been googling for hours and I'm not quite sure what to search for, so if anyone has a good, secure alternative to all this junk, please tell me! I'm just not quite the PHP expert yet... I'm also not sure about the resource impact of using a large class in a PHP file that makes very limited use of it when caching is enabled. Maybe this just isn't worth it, no matter how big the class is?

I had two models in mind... this one would need to be able to affect the "parent's" variables. I am fully aware that this code would not work, I just figured I'd type it in PHP+pseudo to explain it rather than pseudo-code alone.

class User {
public $id;
public $password;
public $password_hash;
public $post_count;

public function __construct($id) {
$this->id = $id;
}
}

subclass Password of User {
public function set($password) {
parent::$password = $password; }
public function generateHash() {
parent::$password_hash = hash("sha256", parent::$password, true); }
public function validate($password) {
return (hash("sha256", $password, true) === parent::$password_hash); }
}

subclass Post of User {
public function count() {
$db = ConnectionFactory::getFactory()->getConnection();
$sql = $db->prepare("SELECT 1 FROM `Forum_Posts` WHERE `User_ID`=:user");
$sql->execute(array('user' => parent::$id));
parent::$post_count = $sql->rowCount();
return parent::$post_count;
}
}

$some_bloke = new User(3);
$bob = new User(4);
$bob->Password->set('idonthaveaverygoodpassword');
$bob->Password->generateHash();
$lucy = new User(5);
echo $lucy->Post->count();


I also thought about using multiple traits, but it's not a pretty solution and I'm worried that it would increase memory usage pointlessly:

class User {
use BaseUser;
}

class User_Password {
use BaseUser;
use Password;
}

class User_Post {
use BaseUser;
use Post;
}

trait BaseUser {
public $id;
public $password;
public $password_hash;
public $post_count;

public function __construct($id) {
$this->id = $id;
}
}

trait Password {
//stuff
}

trait Post {
//stuff
}

$some_bloke = new User(3);
$bob = new User_Password(4);
$bob->setPassword('idonthaveaverygoodpassword');
$bob->generatePasswordHash();
$lucy = new User(5);
echo $lucy->countPosts();


I'm also very open to suggestions as to what to rename this post. I just wasn't quite sure.

Answer

A general framework, which could help with OOP independent of domain, is called SOLID. It can generically be applied to any domain by trying to comply with each principle it defines. That being said, SOLID is a just a framework, and code could fulfill solid principles while still bein difficult to work with :( I will try to answer with a pretty generic application of it to your domain:

Single Responsibility Does each class only have a single reason to change. If the hashing algorithm or salt changed would that be isolated to a single spot? Yes. What if requirements changed and organization doesn't want to keep track of the raw password in memory? No. Both the User and the Password need to change.

Open/Closed How would we create new types of Posts, Users, or passwords? What if the business would like to add an unauthenticated User, who wouldn't need a password. Could that be modeled in the system without modifying the User model? What if unauthenticated users always had 0 posts?

Liskov Substitution This states, that all objects should be replaceable with instances of their subtypes. This is often only validated once the code that consumes your domain objects is created. In this case:

$some_bloke = new User(3);
$bob = new User(4);
$bob->Password->set('idonthaveaverygoodpassword');
$bob->Password->generateHash();
$lucy = new User(5);
echo $lucy->Post->count();

If $bob were an instance of a Post it would not be able to be instantiated using a Password which is a subclass of User, same goes for $lucy being an instance of a Password instead of a User.

Interface segregation

Dependency Inversion


In general I feel like Memory usage should be a non-issue for higher level (non-embedded) applications. Even for mobile, I find, abstractions and maintainability are more important than minimizing memory usage.

Additionally, inheritance based hierarchies can easily grow unwieldy and brittle. Composition (more like your second suggestion) may help to fulfill SOLID and minimize the brittleness. User could be provided a password strategy, which would define an interface for generating passwords, and allow password generation implementation to change, and leave user unaffected. Users that wanted to generate a password could delegate to their specific strategy.