Looking more into OOP and classes, I have an extremely large user class which is becoming increasingly large to manage. The total code is in excess of 950 lines and completely dismisses the Single Responsibility principle.
A few ways i thought to make this smaller is to create completely seperate classes and transfer the data in on the construction of the new class. For example:
index.php
$user = new user('username');
$userOptions = new userOptions($user);
or
$user = new user;
$userOptions = new userOptions;
$userOptions->setData(
$user->getData([
'username'=>'genericuser'
]),
);
However this seems un-natural and isn't as nice as i would expect properly formatted code to be.
Another way i thought of is to extend the base class into another one. For example:
index.php
$userOptions = new userOptions('username');
classes/useroptions.php
class userOptions extends User {
//a small section of what would be a big user class
}
However this also violates some PHP practices, with extends normally meaning a special case of which user options doesn't appear to be.
My final way of organising the document uses in the default class to seperate the large file down, for example:
classes/user.php
class User {
/**
* CREATION SECTION
*/
public function create() {
//create user
}
/**
* UPDATE SECTION
*/
public function change($whattochange) {
//what to change
}
}
However this once again seems to violate the single responsibility principle, with a lot of options being in one single class.
What is common practice to separate down a class, and how should it be done?
The current User class has the following methods inside of it:
* - $variables
* - __construct gets a new user.
* - stripeCustomer() gets the customer information if it exists
* - create($fields|array) method to create a new user
* - login($username|string, $password|string, $remember|bool) logs in a user
* - find ($param|string, $method|string) finds a user WHERE param=method
* - data() returns user data
* - isLoggedIn() returns whether a user is logged in
* - isAdmin() returns if a user is an admin
* - logout() logs out current user
* - displayName() returns first name or username
* - isVerified() checks if a user is verified
* - inCompany() checks if a user is in a company
* - inVerifiedCompany() checks if a user id in a verified company
* - verifyUser($apicode|string, $verification|string, $resend|bool) verifies a users email
* - error() returns any errors that may occur
* - getProfilePicture($size|int) gets a users profile picture from Gravatar with size of $size in pixels
* - passwordCheck($password|string) checks if two passwords match
*
* // 2FA section
* - has2FA() checks if the user has 2FA enabled
* - TOTP($type|string, $secret|string, $code|string, $backupcodes|array) everything to do with 2FA
* - countBackups() counts the amount of backup codes remaining for a user with 2FA
* - update($statement|string, $params|array, $apicode|string) updates a user
*
* // lockdown system
* - getAttempts() gets amount of attempts to break into a users account
* - isLocked() gets whether the user account is locked
* - addAttempt() adds an attempt to a users account
* - reinstateUser() unlocks a users account
* - shouldShowCaptcha() checks whether a captcha is needed
*
* // codes
* - sendRequest($code|int) sends a request to a users email with a specific code
* - verifyCode($code|string, $type|int) checks a user inputted code to one in the DB
*
* - deleteUser() deletes the specific user
* - makeAdmin() makes the user an admin
* - removeAdmin() removes the user as an admin
* - modify($changes|array, $apicode|string) modifies a user | no idea how this is different to update
I also understand that the database section of the class should be in a seperate mapper class, which would use the same style structure as my initial attempt, this will be changed soon.
Thanks in advance for all help.
For reference, i've had a look on google and found some people asking similar questions, however none seem to answer the question to a great degree.
How to break up a large class
there is a way which is called trait you can make any number of traits and include then in a class.. make seperate files of traits it will make your code easy to read..
What a trait actually is its like a class with lot of methods..
how to use it
the syntax will be something like this..
trait UserOptions{
public function create()
{
// logic goes here
}
public function destroy(){
// logic
}
}
trait UserAdmin{
public function isAdmin(){
return true;
}
}
class User{
// this is how to include traits
use UserOptions , UserAdmin ;
}
now all the methods of all the traits are included in the User class .
That is how we can break down so much of the code
Well, I am not really an expert myself. But I guess the answer to the question has two sides. One is not really technical. Let's start with it.
Refactoring is not a trivial task to do. Refactoring legacy code is twice so (I am not sure that your code is actually legacy one, but I guess the rule holds). Usually done by the people who are experts in their field. And even before they start there are a couple of steps to be done. Firstly, the current behaviour of the system must be documented. Either with some kind of end-to-end (acceptance) tests or by really thorough technical documentation (done by business analytics). Better with both. And only then you can start the process of rewriting. Without this, you simply cannot be sure the system will work as before. It the best case scenario, you will end up with explicit bugs (the ones you can clearly see right away). But you also can end up with some unexpected implicit behaviour, that you might notice only after some time.
Regarding technical side. Now you have so-called God object. It does at least persistence, authentication and authorization. So as you stated SRP (Single responsibility principle) is completely dismissed. So firstly you have to extract entity (business object):
final class User
{
private $id;
private $name;
// ...
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
public function id()
{
return $this->id;
}
public function name()
{
return $this->name;
}
}
Then extract repository to persist this entity:
final class UserRepository
{
/**
* \PDO or whatever connection you use.
*/
private $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function findByPk($id): User {}
public function create(User $user) {}
public function update(User $user) {}
public function delete(User $user) {}
}
Extract authentication and authorization into separate services. Even better, use existing packages (you can find many on GitHub).
But again all this is safe to do only if you have the way to ensure that your changes have not broken anything.
There is a book on the subject. To be honest, I have not read it myself yet, but I've listened to the podcast with the author and heard really good reviews. So you can check it out.
Instead of stitching rotten software you should think of a re-design with a migration process. The best is to create a new branch that can cope with old data structures but allows to introduce new (straight) ones. A clean cut is also better than many little migration steps. There's a risk in both: little step take long and each can cause user frustration. A cut can fail and you might need to roll back.
Related
I've recently learnt OOP in Java and I'm trying to implement what I've learned in my PHP usersystem.
This is my current User class
class User {
public $id;
public $session;
public $email;
public $lastVisit;
public function __construct($id) {
$conn = new Conn();
$array = $conn->ExecuteCmdArray("SELECT * FROM user WHERE id = '".$id."'");
$this->session = $array['session'];
$this->email = $array['email'];
$this->id = $array['id'];
$this->lastVisit = $array['last_visit'];
}
}
^ Oh, and am I supposed to execute a SQL to retrieve the data?
However, after taking a look at some examples online, I looked at their User class, but realised that in it, only functions like login etc. are present. The attributes in the class are also only the username and password.
Shouldn't it contain all the other variables e.g gender, real_name as well? It seems to me that the user class does not store the userdata, but it's only used for logging in and stuff.
Is this how a User class in PHP usually works - which means my User class is done wrongly?
Well there is no right way to design a User class. It depends on the programmers likes, needs, skills and his understanding of OOP and Software-Design.
Seperation of Concerns
Oh, and am I supposed to execute a SQL to retrieve the data?
It's not recommended. Good software design is about creating decoupled self-contained components.
When designing Software it's a good practice to think out of the box and put yourself into another developers shoes. So if you had a User object without knowing what's going behind the scenes. Would you expect it to fire Database-Queries? No it's not a Users responsibility to query the database.
OOP is also about reusability. If we take your example: If I threw your User class into my own project. It would likely break. You are instantiating a Database Connection directly in the class. What if I had other ways of handling DB access? Everything would break.
Seperation of Concerns is the keyword here. And you should adhere to it whenever it's possible.
However, after taking a look at some examples online, I looked at their User class, but realised that in it, only functions like login etc. are present.
Ask yourself these questions:
Should a User know how to log in? How his sessions are managed or is this the task of another service maybe?
Would you expect that a User can log itself in if you didn't know the class?
You see. By just thinking about the concerns of a class you can eliminate problems in design before they even occur.
I would not expect my User that he knows how to log in. I rather would expect a service of my application which is explicitly dedicated to that task, to handle it.
<?php
$authentication = new UserAuthenticationService();
$authentication->attemptLogin('username', 'password');
if( $authentication->check() )
{
$user = $authentication->getUser();
echo $user->username();
}
Build your applications so that your classes are as self-contained as possible and only have the dependencies they really need. It's one of the keys of successful software-design.
It's all about your domain
The attributes in the class are also only the username and password.
Shouldn't it contain all the other variables e.g gender, real_name as well? It seems to me that the user class does not store the userdata, but it's only used for logging in and stuff.
That depends on your use case. If you're creating an application where users can anonymously post stories of their last hangover then why would you need a real name there?
If you however are creating some enterprise-business-app-thingy you probably want to store that data. What data your objects hold is defined by you, and only by you.
Let's take our hangover-site a step further. I want only minimal information from my users so it could look like this.
<?php
class User {
private $username;
private $email;
private $gender;
public function __construct( $username )
{
$this->username = $username;
}
public function username()
{
return $this->username;
}
//[...]
public function setUsername( $username )
{
$this->username = $username;
}
//[...]
}
I don't even store the password on my User class because I don't want to. I decide that I will have some kind of persistance manager and an authentication manager which handle those.
<?php
$authentication = new UserAuthenticationService();
$persistanceMapper = new UserPersistanceMapper();
$authentication->attemptLogin('username', 'password');
if( $authentication->check() )
{
// Let's rename the User just for fun
$user = $authentication->getUser();
$user ->setName('Thomas');
$persistanceMapper->persist( $user );
}
It really depends on your likes. But what I wanted to show in the first place is, that I have objects that do only the thing(s) that they are responsible for, or supposed to do
The User is able to alter it's own state and is able to provide me with it's data. But he does not know anything about the database.
UserPersistanceMapper knows how to persist a User (in a database or wherever it's supposed to)
UserAuthenticationService knows how a users session needs to be handled
I don't have the one object that handles all of it. Instead we have self-contained objects here that are together handling our stuff.
It all comes down to your likes
Is this how a User class in PHP usually works - which means my User class is done wrongly?
Eventually there is no right and no wrong. If you want to roll that way, do it! You may gain an initial time boost if you are not thinking about design. That may be okay for simple projects that are not hard to maintain.
At the time you are building larger applications however, it's always good to reflect on your own code.
Keep in mind:
Try to put yourself into some other developers shoes while designing your application
Visualize how objects are connected to each other before you start coding
Try to keep your classes as self-contained as possible (pass eventual dependencies from the outside)
Get stuff done! If you can't come up with a clean way to do something: Just make it work. Refactor things later. But make sure you have a periodic refactoring cycle. Don't put stuff aside and think "Yeah... whatever I'll do this later". You will end up with a lot of Code Smells this way.
Further reading
Seperation of Concerns
PHP: The Right Way
SOLID Principles
The drawback with the way you made your user class is when you manage lists of users, or process things about lots of users at a time you have to move around all this extra user data, whereas sometimes you would only need it's id, and email.
We can use two or more classes! In your exemple there would be a minimal User class, with very minimal attributes and functions, and a Full_User class, extending the first one.
class User {
public $id;
public $email;
public function __construct($array) {
$this->email = $array['email'];
$this->id = $array['id'];
}
}
class Full_User {
public $session;
public $lastVisit;
public function __construct($array) {
$this->session = $array['session'];
$this->lastVisit = $array['last_visit'];
parent::__construct($array);
}
}
I'd suggest passing the array of properties to the constructor instead of executing the statement. It will allow creating objects from some other data then a database.
$id = 12;
$conn = new Conn();
$minimal_array = $conn->ExecuteCmdArray("SELECT id,mail FROM user WHERE id = '".$id."'");
$full_array = $conn->ExecuteCmdArray("SELECT id,mail,session,lastVisit FROM user WHERE id = '".$id."'");
$user_for_listing = new User($minimal_array);
$full_user = new Full_User($full_array);
Let's assume I have a Booking entity and it has a state field which may be set to one of a few values - let's make it: NEW, ACCEPTED, and REJECTED
I am looking for the "right" way to implement this. So far I used an approach like this:
class Booking
{
const STATUS_NEW = 0;
const STATUS_ACCEPTED = 1;
const STATUS_REJECTED = 2;
protected $status = self::STATUS_ACTIVE;
}
And it works ok, but I am really curious of the "proper" way of doing, also I have a few problems with this approach:
It looks awfully lot like a business logic hidden in the entity class - if entity is supposed to be a POJO, then why would it care what the status may be? So I could put it in a manager class like:
class BookingManager
{
const STATUS_NEW = 0;
const STATUS_ACCEPTED = 1;
const STATUS_REJECTED = 2;
public function setBookingStatus(Booking $b, $status) { }
}
but it still does not help with the second issue:
It is hard to re-use that data in the view, let's take a twig for example - I would have to create a Twig Extension in order to convert a number into an actual name:
Status type: {{ booking.status }}
Status name: {{ booking.status|statusName }}{# I don't like this approach #}
Status name: {{ booking.getStatusName() }} {# This seems even worse #}
So I could add getStatusName method to the BookingManager but I believe it does not belong there. I could add the same method to the Booking class and it would work just fine, but then again - business logic AND now also presentation logic is hidden in the entity.
If some of my code depends on MyVendor\MyBundle\Entity\Booking::STATUS_NEW then could it become a problem when unit-testing the code? With static method calls the problem is obvious - I cannot mock the dependency. Is there any use case where depending on static constants may be a problem?
All I could think of would be moving all this logic to the service layer and create a service like BookingStatusManager (let's ignore the fact that it could be mistakenly took for an EntityManager subclass) - it could look like this:
class BookingStatusManager
{
const STATUS_NEW = 0;
const STATUS_ACCEPTED = 1;
const STATUS_REJECTED = 2;
public function getStatusName($code) { ... }
}
but now I need an extra class for each enum-like property for each entity and it's a pain, also it still does not seem right - I need to reference static values from this class whenever I want to deal with Booking's status.
Note that this is a general question, not one specific to presented Booking example - it could be for example BorderLength entity with lengthUnit field with possible values of MILES or KILOMETERS; also, status transitions are not restricted to those caused by users, it must be possible to perform these from the code.
What in your experience is "the right way" to solve this?
I'd propose you get rid of of the constants and just create a Many-To-One association on your Booking entity with a new BookingStatus entity. Why? Well for a number of reasons:
Does not require editing of code if you wish to add a new booking status. Not only is this easier for developers but also allows the possibility of statuses to be dynamically created.
You can easily store additional information about the status in the new BookingStatus entity, e.g name. This information can also be updated without altering code.
Allows external tools to understand different statuses. For example you might want to use a external reporting tool directly on the database. It won't know what some integers mean but it will be able to understand a Many-To-One association.
Answers to your numbered questions
1: Why wouldn't you have business logic in your business model/objects?
Coupling data and behaviour is, after all, one of the very purposes of object orientation? I believe you may have misunderstood the "POJO" concept (and I'm not talking about the J standing for Java ;)), whose purpose was to not let frameworks invade your model, thus limiting the model to a framework-specific context and making unit testing or re-use in any other context difficult.
What you're talking about sounds more like DTOs, which generally is not what your model should consist of.
2: Yes, Twig is not terribly good at manipulating numbers-that-symbolizes-meaning. You'll probably get all kinds of suggestions based on things like optimization of storage (number of bytes) or database traffic/query time, but for most projects I prefer prioritizing the human experience - i.e. don't optimize for computers unless you need it.
Thus, my personal preference (in most circumstances) is instantly dev-readable "enum" fields, but in a key-like notation instead of regular words. For example, "status.accepted" as opposed to 1 or "Accepted". Key-like notations lend themselves well to i18n, using twig's |trans filter, {% trans %} tag or something similar.
3: Static "enum" references within your model is rarely a problem while unit testing the model itself.
At some point your model needs to define its semantics anyway, through the use of the building blocks you have available. While being able to abstract away implementations (particularly of services) is useful, being able to abstract away meaning is rarely (never?) fruitful. Which reminds me of this story. Don't go there. :-D
If you're still concerned about it, put the constants in an interface that the model class implements; then your tests can reference only the interface.
Suggested solution
Model, alternative 1:
class Booking {
const STATUS_NEW = 'status.new';
const STATUS_ACCEPTED = 'status.accepted';
const STATUS_REJECTED = 'status.rejected';
protected $status = self::STATUS_NEW;
}
Model, alternative 2:
interface BookingInterface {
const STATUS_NEW = 'status.new';
const STATUS_ACCEPTED = 'status.accepted';
const STATUS_REJECTED = 'status.rejected';
// ...and possibly methods that you want to expose in the interface.
}
class Booking implements BookingInterface {
protected $status = self::STATUS_NEW;
}
Twig:
Status name: {{ ("booking."~booking.status)|trans({}, 'mybundle') }}
(Of course, the booking. prefix is optional and depends on the way you want to structure your i18n keys and files.)
Resources/translations/mybundle.en.yml:
booking.status.new: New
booking.status.accepted: Accepted
booking.status.rejected: Rejected
Constants-as-entities
On Tomdarkness' suggestion of turning these constants into their own model class, I want to stress that this should be a business/domain decision, and not a question of technical preference.
If you clearly foresee the use cases for dynamically adding statuses (supplied by the system's users), then by all means a new model/entity class is the right choice. But if the statuses are used for internal state in the application, which is coupled to the actual code you're writing (and thus won't change until the code changes too), then you're better off using constants.
Constants-as-entities makes working with them much harder ("hm, how do I get the primary key of 'accepted', again?"), isn't as easily internationalizable ("hm, do I store the possible locales as hard-coded properties on the BookingStatus entity or do I make another BookingStatusI18nStrings(id, locale, value) entity?"), plus the refactoring issue you brought up yourself. In short: don't overengineer - and good luck. ;-)
I use very simple approach. Example:
class Offer extends Entity implements CrudEntityInterface
{
const STATUS_CANCELED = -1;
const STATUS_IN_PROGRESS = 0;
const STATUS_FINISHED = 1;
const STATUS_CONFIRMED = 2;
protected static $statuses_names = [
self::STATUS_CANCELED => 'canceled',
self::STATUS_IN_PROGRESS => 'in_progress',
self::STATUS_FINISHED => 'finished',
self::STATUS_CONFIRMED => 'confirmed'
];
/**
* #var integer
*
* #ORM\Column(name="status", type="integer")
* #Assert\NotBlank
*/
protected $status = self::STATUS_IN_PROGRESS;
public static function getStatuses()
{
return self::$statuses_names;
}
/**
* Set status
*
* #param integer $status
* #return Offer
*/
protected function setStatus($status)
{
if(!array_key_exists($status, self::$statuses_names)){
throw new \InvalidArgumentException('Status doesn\'t exist');
}
$this->status = $status;
return $this;
}
/**
* Get status
*
* #return integer
*/
public function getStatus()
{
return $this->status;
}
public function getStatusName()
{
return self::$statuses_names[$this->status];
}
}
All display names are always translated, in order to keep separation from model
{{ ('offer.statuses.'~offer.statusName)|trans }}
While it's not the most elegant, you can get a little pragmatic, and just use string keys in a fairly efficient, safe, way:
<?php
class Booking
{
protected $statusMap = array(
0 => 'new',
1 => 'accepted',
2 => 'rejected'
);
/**
* A premature optimization, trading memory to reduce calls to
* array_flip/array_search in a more naive implementation
*
* #var array
*/
protected $statusMapReverse;
public function __construct(){
$this->statusMapReverse = array_flip($this->statusMap);
$this->setStatus('new');
}
/**
* #ORM\Column(type="integer")
*/
protected $status;
/**
* #param string $status Valid values are 'new', 'accepted', and 'rejected'
*
* #throws InvalidBookingStatusException
*/
public function setStatus($status){
if (! in_array($status, $this->statusMap)){
throw new InvalidBookingStatusException();
}
$this->status = $this->statusMapReverse[$status];
}
/**
* #return string
*/
public function getStatus(){
return $this->statusMap[$this->status];
}
/**
* #return integer
*/
public function getStatusCode(){
return $this->status;
}
/**
* #return array
*/
public function getStatusMap(){
return $this->statusMap;
}
}
I find I use this pattern pretty frequently when I need to model this kind of enumeration-like data. It has a few nice features:
1) Status is stored as in integer in the db.
2) Calling code doesn't ever need to care about those (integer) values, unless it wants to.
3) Calling code is protected against typos/invalid status since setStatus validates strings (can only be checked at runtime, but hey)
4) Booking::getStatusMap() makes it easy to generate select boxes, etc.
You could expand setStatus to accept either a string or integer (status-code), and still validate, if you wanted to be able to set status by code.
The disadvantages (compared to using constants), are mainly:
1) Querying by status becomes a bit of a hassle. Client code needs to getStatusMap() and find codes.
2) IDEs won't be quite as helpful in telling calling code what valid statuses are (though the phpdoc annotation on setStatus() can help, if you keep it up-to-date.
3) Invalid status throws an exception at runtime, while with constants you find out at compile-time.
--
Final note, I don't see the need for a BookingManager at all. Yes, entities are just plain objects, but that doesn't mean they can't contain logic to manage their own state.
I like handling those kind of issues in a generic way (a bit of an overkill if all you need is a status, but what project ever needed only one enum-like field?);
First, I create a "GenericOptionEntity", having the following fields:
ID
Tag
Label
Entity
Field
And a one to many relationship to associate the entity (or the base entity, in case of a generic "status") to the appropriate field.
The "entity" and "field" fields help identify the proper options for form types.
The GenericOptionRepository should implement a "getOptionByTag", so that you wouldn't have to use IDs to get the proper entity. Also, a generic method for getting appropriate options for the calling method \ field.
Lastly (depending on the case), a bunch of interfaces sometimes come in handy, especially for making entities "Statusable" when you need it.
I really like this approach, mostly because:
It makes sense :)
Adding and removing options can be done by an admin, via GUI
Easy to filter the appropriate options for the view / controller tiers
Allows re-use of all the helper methods required
Does not clutter your project with mini-classes created for 4 DB rows
I would go with Tom's approach and make the status table look like this :
id
description
isAccepted (bool)
isRejected (bool)
And the values like this :
id : 1
description : New
isAccepted : 0
isRejected : 0
id : 2
description : Accepted
isAccepted : 1
isRejected : 0
id : 3
description : Rejected
isAccepted : 0
isRejected : 1
You then query with isAccepted or isRejected without having to worry about the id.
I recently worked out something similar in a MenuItem entity and three constant 'types'.
The approach I like includes adding some additional 'is' methods. I placed them right in the entity, but that approach is up to you. I'm not sure there is a single 'right way'.
<?php
class Booking
{
const STATUS_NEW = 0;
const STATUS_ACCEPTED = 1;
const STATUS_REJECTED = 2;
protected $status;
public function setBookingStatus($status)
{
$this->status = $status;
}
public function getStatus()
{
return $this->status;
}
public function isStatusNew()
{
return $this->status === self::STATUS_NEW;
}
public function isStatusAccepted()
{
return $this->status === self::STATUS_ACCEPTED;
}
public function isStatusRejected()
{
return $this->status === self::STATUS_REJECTED;
}
}
That allows you to use some easier logic in your Twig templates (YMMV):
{% if booking.statusAccepted %}
{% elseif booking.statusRejected %}
{% else %}
In this way you can keep a similar Twig syntax to your constants.
I'm working with a Symfony 2.1 application and I have a lot of parameters being sent via a POST request and I'm looking for a smarter way to take each request parameter and populate the my entity class. I'm looking to avoid writing $entity->setMyParam($my_param) expressions for n request parameters. For example, here is a snippet of my entity:
namespace Brea\ApiBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Brea\ApiBundle\Entity\Distributions
*
* #ORM\Table(name="distributions")
* #ORM\Entity
*/
class Distributions
{
/**
* #var string $recordType
*
* #ORM\Column(name="record_type", type="string", nullable=false)
* #Assert\NotBlank()
* #Assert\Choice(choices = {"a", "b", "c", "d", "e"}, message = "Choose a valid record type")
*/
private $recordType;
/**
* Set recordType
*
* #param string $recordType
*/
public function setRecordType($recordType)
{
$this->recordType = $recordType;
}
/**
* Get recordType
*
* #return string
*/
public function getRecordType()
{
return $this->recordType;
}
}
My controller attempts to take each request, camelcase the parameters and set the value of the request param to the entity:
public function createRecordAction(Request $request, $id)
{
$distribution = new Distributions();
$params = $request->request;
foreach ($request->request->all() as $param=>$value)
{
if ($param == "_method")
continue;
$function = "set".str_replace(' ','',ucwords(preg_replace('/[^A-Z^a-z^0-9]+/',' ',$param)));
$distribution->$function($value);
}
}
It works, but my misgiving about this approach is that I would need to run this code in every controller that does a similar thing. I can refactor it into a parent class as a method to avoid duplicating code, but I'm curious if this is a good practice. I looked for something in the Symfony framework that does this already but all I could find were examples of binding the request to a form.
First of all: Warning!!
As I commented earlier, I would be very careful about using the code provided in your original post since you said it's data from a POST request, which means a client could inject any kind of data in it and call functions you might not have wanted on your object (or just cause a failure in your script by sending you non-existant function names).
I would actually read the conclusion first..! :) Then come back to Alt. 1 & 2.
Alternative 1:
With that being said, an alternative solution to your problem would be to give objects the responsability to fetch their own data. With granulated enough objects, you should not end up with bloated code, and you can define in each class which parameters to look for and which functions to call (and localize changes when you make a change to a class):
class BookInformation{
private $publisher;
private $name;
private $price;
public static createFromRequest($req){
$publisher = Publisher::createFromRequest($req);
$book = new BookInformation($publisher, $req['book_name'], $req['book_price']);
$book->setABC($req['abc']);
//...
return $book;
}
public __construct($publisher, $name, $price){
//...
}
}
class Publisher{
private $name;
private $address;
public static createFromRequest($req){
return new Publisher($req['publisher_name'], $req['publisher_address']);
}
public __construct($name, $address){
//...
}
}
Like I said before, one big advantage with this method is that if you need to add new attributes to any of those classes, you don't have to edit the controllers at all and can just edit your "initialization from request method". Future changes will be localized to the modified class.
Of course, don't forget to validate any data sent from a user request (but that's just common sense).
Alternative 2:
Note that the first alternative is very similar to the Factory pattern (based on GoF's Abstract Factory), and that you could also implement a solution using that pattern:
class BookFactory{
public createBookInformation($req){
$publisher = $this->createPublisher($req);
$book = new BookInformation($publisher, $req['book_name'], $req['book_price']);
$book->setABC($req['abc']);
//...
return $book;
}
public createPublisher($req){
return new Publisher($req['publisher_name'], $req['publisher_address']);
}
//createAnythingRelatedToBooks($req)...
}
That way, you have all the initializing procedures in a very cohesive class which only responsability is to initiliaze a certain family of objects based on a request object (and that is a VERY good thing). However, if you add attributes to one of those classes, you have to edit the appropriate Factory method too.
Conclusion
Please note that these two alternatives are actually not really alternatives... They can be used along with your initial code (especially the Factory one). They really only solve your last problem (the "where to put the code" problem).
However, even if you do sanitize the POST request and only call functions that are annoted (as stated earlier), I wouldn't really suggest it because I have the feeling that more complex business rules would ruin the design pretty fast (but maybe you've got it all covered already (?)). That is, I don't think you can easily plug in business rules in the initializing process since it's all automatic (it can't do any validation of the values since it could be any kind of value) and I feel like you'd end up "undo-ing" stuff after initialization (which I personally hate.. Lots of room for errors)!
For example, take the same two classes in Alternative 1 (BookInformation and Publisher).
Let's say a Book can only have a Publisher if that Publisher is already registered in a database and that their address has been confirmed (new publishers need to be created using another interface and then have their address confirmed before they can be linked to a book).
Else, regardless of the request data, publisher should be set to XYZ. I have the feeling (I might be wrong) that to support those kind of rules, you'd have to actually construct the object (automatically) and then destroy/reassign the publisher attribute if it does not match certain rules. Now, if you have a pool of those Publisher objects in memory, you also need to remember to delete the wrongly created Publisher in that pool. And that's just one rule!
One thing you could do with your code to "fix" that issue would be to have a validation method for each setter (validXYZ()), but that's starting to look like a design that would fall apart pretty quickly if validations are dependent on other objects/data...
I don't really have anything else to discourage you from using that code, but if you do, please keep us updated on how it works out after a year or two (once there has been some maintenance/new features added, etc...).
I looked for something in the Symfony framework that does this already but all I could find were examples of binding the request to a form.
I would use Forms for this. Even if the HTTP request is not performed from an HTMl form, you can just bind the Request to a form instance: it will take care of all the data injection and validation.
And plus, in case you'll ever need HTML forms, you'll have them ready ^^.
I'm writing a user account system in PHP, with an emphasis on security, but I'm stuck on refactoring it into something cleaner and more usable.
The problem is trying to group the user account functionality together, but in separate classes. The way I'm doing it now is, there are a bunch of classes with public static methods that all take $username as the first parameter, and they use other static methods, passing the same $username as the first parameter as well. Obviously, OOP is a better way to go, especially since each method must strtolower the username in order to make DB queries, and must handle the case where the username provided doesn't exist at all.
The problem with putting everything in a "User" class is that it would be huge, and there would be a lot of completely unrelated code in the same file. For example, the Change Password code doesn't need to call methods related to validating the user's email or changing the user's (unrelated) settings.
I could have two classes: User and AuthenticatedUser, where AuthenticatedUser inherits User. User would implement all the functionality that is possible without the user being logged in, and AuthenticatedUser would be all the functionality that requires a login -- such as accessing the user's encrypted data. Maintenance code could use objects of User, and GUI code would get an AuthenticatedUser object after the user has logged in. But I don't want to cram all of the functionality into User.
Here's the list of some of operations that are related to a user account, just to show why they won't all fit in one class:
Login
Lockout user if >=X attempts in past Y minutes (includes methods for determining whether a user is locked out, adding a tick to the attempt count, etc).
Bypass the lockout with an email loop
Change password
Administrator change password (force)
Password reset (email loop) (incl. methods for initiating the reset, validating the email token, etc)
Set/get user data stored in plaintext
Set/get encrypted user data
Set/get account settings (password reset is allowed?, lock out the account on password failures?, is bypassing lockout with email loop allowed?, etc) Ideally these settings should be set/got near the code whose behavior depends on them.
Get the user's username in the proper case (as they specified it when creating account)
Email validation
A lot of functionality only used by specific code. E.g. get user "salt" used for key derivation.
A bunch more...
I was thinking I could do something like have a PasswordChanger class that inherits User, and implements the password changing functionality. So it would look like:
$proper = $user->getProperName();
...
$passChanger = $user->getPasswordChanger();
$result = $passChanger->changePassword($oldPass, $newPass);
...
$userLockout = $user->getUserLockout();
$result = $userLockout->isUserLockedOut();
...
$userEV = $user->getEmailValidation();
$result = $userEV->tryValidateEmail($token);
...
That's the best solution I've come up with so far. It lets me split up related functionality into it's own file, and saves from having to pass around the username. But it seems really weird -- I've never seen code like that before. It forces the superclass to know about all of its subclasses, which is bad design. Any ideas?
Edit: An alternative avoiding inheritance would be to have a PasswordChanger that has a a User. Like:
$passChanger = new PasswordChanger($user);
$passChanger->changePassword($old, $new); // uses $user->getUsername()
...
$emailValidator = new EmailValdiator($user);
$emailValidator->tryValidate($token);
"PasswordChanger has a User" vs. "PasswordChanger is a User." The former actually makes sense, so I like this way a little better.
Well, you've made a good start and you're asking the right questions. There is no single answer though. Design is an art rather than a science. It does sound like you are trying to re-design rather than refactor. You might find it easier if you start to refactor your code with only a loose idea of where your design might end up (a really good resource for refactoring is this book by Michael Feathers. As you refactor more and more you should find that a design emerges from the darkness! And often that design is not what you thought you'd end up with when you started out.
Oh, and btw inheritance is one of the most over-used aspects of OO. If you are thinking of inheritance, stop and think again. If you still think you need inheritance, stop and think some more. If you still think you need inheritance, it's possible that you do...
PasswordChanger and User have nothing in common so you should avoid to inherit them.
What I do in this case is something like:
class User {
var $pswChanger; //> -> Object of PasswordChanger
}
Basically put all the objects that you will need with User class within its attribute
You can of course access them with something like $this->pswChanger->method();
You might try using a decorator pattern. You can extend the UserDecorator by adding a validateDecorator, a notifyDecorator etc.
class User {
private $name;
private $password;
public function __construct($name, $pw) {
$this->name = $name ;
$this->password = $pw;
}
public function getUsername() {
return $this->name;
}
public function getPassword() {
return $this->password;
}
}
class UserDecorator {
protected $user;
protected $name;
protected $password;
public function __construct(User $user) {
$this->user= $user;
$this->setValues();
}
public function setValues() {
$this->name = $this->user->getUsername();
$this->password = $this->user->getPassword();
}
public function getUsername() {
return $this->name;
}
public function getPassword() {
return $this->password;
}
}
class PasswordChangeDecorator extends UserDecorator {
private $userdecorator;
public function __construct(UserDecorator $user) {
$this->userdecorator = $user;
}
public function changePassWord($newPw) {
$this->userdecorator->password = $newPw;
}
}
$user = new User("some random user name", "oldpw");
$userdecorator = new UserDecorator($user);
$pwdecorator = new PasswordChangeDecorator($userdecorator);
print $userdecorator->getPassword() . "\n";
// set the new password
$pwdecorator->changePassWord("newpw");
// test the outcome
print $userdecorator->getPassword();
I'm starting to code my project and I have used, in some past unfinished project, two different patterns to manage the design of a user class that will need to manage the following things:
The creation of a user
The edits to a user
The deletion of a user
The reading of user data
Despite this, we have also to consider that this user class will be extended by the session class which will just set the focused user id as the id provided by the user who is viewing the pages.
We will also have this class users that will manage instead groups of users.
The 2 options I used are the following (simplified):
Different class for different purpose
- class UserMaker($username, $password, $email);
function giveBirth(); // create the user
- class UserManager($id);
function edit($field, $value); // edit a specific user field
function save(); // save all the edits with a single query
function setTrusted(); // set that user as trusted
function setAdmin(); // set that user as admin
function setBanned(); // ban the specific user
- class UserReader($id);
function get($field); // Get the value of a single field
function getAll(); // Get all fields from that user as associative array
function isAdmin(); // self explanation
function isRegistered(); // self explanation
function isBanned(); // self explanation
Single class
- class User($id);
function static giveBirth($username, $password, $email); // create the user, notice this is static
function edit($field, $value); // edit a specific user field
function save(); // save all the edits with a single query
function setTrusted(); // set that user as trusted
function setAdmin(); // set that user as admin
function setBanned(); // ban the specific user
function get($field); // Get the value of a single field
function getAll(); // Get all fields from that user as associative array
function isAdmin(); // self explanation
function isRegistered(); // self explanation
function isBanned(); // self explanation
Basically, since the only class that does not accept $id as argument for the __construct() is UserMaker we just set the function giveBirth() as static so we can create the user.
What is the best way to design this pattern? Have you got a third-option which you feel better than these?
Well, the answer to this question relates specifically with The Single Responsibility Principle. Basically, each and every class in your application should have exactly one responsibility. That doesn't mean that it can't be used in more than one situation, but it shouldn't be responsible for more than one abstract concept.
So, from your examples, I would build a class structure similar to this:
class UserFactory()
getModel();
getUser($id, $model = null);
getACL($user);
class UserModel ()
edit($id = 0);
load($id = 0);
reload($id = 0);
save($id = 0);
class User ($data)
getAll();
getField($field);
class UserACL (User $user)
isTrustedUser();
isAdminUser();
isBannedUser();
That way, everything is organized by responsibility and role, rather than by relation. The benefit to this, is that if you want to swap out an individual component later (for example, the ACL system, or the Model storage layer), you don't need to worry about testing a huge class. Just test that particular API and you're done.
I do not know how exactly have you organized/conceived stuff in your system although from the approaches that I have used, I've found out that the most efficient way of handling stuff is by conceiving objects as services for example:
How many times in your software will the giveBirth method be called? once, twice more ? why more?
Although let's say that your user object must expose methods which can not be called in an exact place every time or you want it to have a more general effect.
Create an interface(or interfaces) with the methods headers.
Create a SINGLETON that implements the interface (all the interfaces). That is because I assume you will never have more then an instance of an user during one request.(fact that is also available for the session).
in the singleton you will have an init static method which can receive an id in order to create the user instance. You will have something like this in the index as I do assume there is a single entry to your application
UserSingleton::init($_SESSION["id"]);
after the request process initializes the user object you can use this :
$user = UserSingleton::getInstance(); // will return the instance of singleton
$user->setBanned();// or whatever you need to call;
Obs:
you can avoid the init method by creating the getInstanceMethod like this:
public function getInstance(){
if(!isset(self::$instance) {
self::$instance = new UserSingleton($_SESSION["user_id"]) ;
}
return self::$instance;
}
Take note that this must not handle stuff like interaction with the database, this should receive/send data to/from the database through external stuff. It is a helper not a business model/object. That is another concern.