PHP OOP best practices or how to code right?
It seems you are reaching for the concept of of a static
variable all instances of a class share the same static variable so using the new twice will not be an issue.
You can see the code on ideaone.
// your code goes here
class Application {
static $db_connected = false;
}
class Database extends Application {
function connect() {
if(!static::$db_connected) {
static::$db_connected = true;
}
}
}
$app = new Application;
$db = new Database;
var_dump(Application::$db_connected);
var_dump(Database::$db_connected);
$db->connect();
var_dump(Application::$db_connected);
var_dump(Database::$db_connected);
Your comment make me think you are looking for a better pattern all together. I would like to throw out some key principles namely OCP and LSP SOLID.
In this case you would avoid having Application
being an instance of Database
but instead use dependency injection. Here is the refactored code.
class Database {
private $db_connect = false;
public function connect () {
if(!$this->db_connect) { /* do connection */ }
}
}
class Application {
private $db;
public function setDatabse(Database $db) {
$this->db = $db;
}
public function getDatabase() {
return $this->db;
}
}
$db = new Database;
$app = new Application;
$app->setDatabase($db);
$app->getDatabase()->connect();
Object Oriented PHP Best Practices
Using explicit getters and setters for properties on the object (like the example you gave for set_name
) instead of directly accessing them gives you (among others) the following advantages:
- You can change the 'internal' implementation without having to modify any external calls. This way 'outside' code does not need change so often (because you provide a consistent means of access).
- You provide very explicitly which properties are meant to be used / called from outside the class. This will prove very useful if other people start using your class.
The above reasons is why this could be considered best practice although it's not really necessary to do so (and could be considered overkill for some uses ; for example when your object is doing very little 'processing' but merely acts as a placeholder for 'data').
OOP Best Practices (specifically PHP)
I definitely prefer option 1. Option 2 does not follow the idea behind OOP because user information is not part of your listing, so keep it in a separate object.
Keep the following rules in mind:
- Load an object without its relationships at once.
- If you need a related object load it at the time you need it according to rule 1
Many ORMs work this way, for example Doctrine ( http://www.doctrine-project.org ). It's called lazy loading.
When you need one property of an object you will find yourself loading a second property of that same object some lines further. In many cases you will find yourself doing numerous database queries within a complete script for just one object.
PHP. The best practices of creating objects
You want to use a repository class for this:
class StoneRepository
{
private $stoneFactory;
public function __construct(StoneFactory $factory) {
$this->stoneFactory = $factory;
}
/**
* @param integer $id
* @return Stone
*/
public function findStoneById($id) {
// Fetch your data from the database
$row = ...;
// Use the factory to create a new stone from the data
// that has already been fetched
$stone = $this->stoneFactory->create($row);
// Return the new stone
return $stone;
}
}
The greatest advantage here is that you split your database logic from your model. This is great as you get a clean model with no awareness of the database. And you're free to do queries however you like, also keeping them clean and separated.
Then you can use this to fetch the stone with the given id in, for example, your controller:
$factory = new StoneFactory();
$repository = new StoneRepository($factory);
$stone = $repository->findStoneById($id);
Fetching collections
Fetching collections is just as easy:
class StoneRepository
{
...
public function findAllStones() {
// Again, fetch the data
$rows = ...;
// Transform each row into a Stone object
$collection = array_map(function ($row) {
return $this->stoneFactory->create($row);
}, $rows);
return $collection;
}
}
Returning objects with relations
Sooner or later you might want to fetch some related objects. There are a few approaches you can take here. The simplest one is to actually just fetch each type of object separately and build your model up in the controller. For example, if you wanted a Bag
object with related Stone
objects in it:
$bag = $bagRepository->findBagById($bagId);
$stones = $stoneRepository->findStonesByBagId($bagId);
$bag->addStones($stones);
Another approach would be to use a join
and actually build the Bag
and the Stones
within BagRepository
. As it's a little bit more complicated, I'm not going to write it down here.
CRUD
You should just as well use the repository to insert/update/deleta the data in the database based on your model. Again, it's just as simple as adding an insert
, update
and delete
methods which accept a Stone
object and persist it's data in the database.
PHP best practice for action based on an object type
Thanks to @Ryan Vincent I found this resource (https://sourcemaking.com/design_patterns/strategy/php) and changed the Strategy design pattern a bit. For avoiding hard-coded type values check how the dynamic class loading is done in StrategyContext::__construct
method. New class instance is initiated by the $type
variable name. Class names should be strings
in PHP so this way forces types to be strings
not only numbers.
Different than the original example in the article, I attached StrategyContext
to the book object and wrap the get methods with the strategy to have better use of book object.
Unfortunately if the business logic will be in your code somehow you need to hardcode it. With this method you don't hardcode for each type but you need to create a strategy class for each type. In the example we have StrategyCaps
, StrategyStars
and StrategyExclaim
strategies. So our types are limited to Caps
, Stars
and Exclaim
.
I didn't try this piece of code in production environment but you can have a starting point via the example.
Also for dynamic loading, you can benefit from this question too.instantiate a class from a variable in PHP?
Hope it helps,
<?php
interface StrategyInterface {
public function showTitle($title);
public function showAuthor($author);
}
class StrategyContext implements StrategyInterface {
private $strategy = NULL;
public function __construct($type) {
//Dynamic class loading per type
$classname="Strategy{$type}";
if(class_exists($classname)) {
$this->strategy = new $classname();
} else {
throw new Exception("Strategy not found", 1);
}
}
public function showTitle($title) {
return $this->strategy->showTitle($title);
}
public function showAuthor($author) {
return $this->strategy->showAuthor($author);
}
}
class StrategyCaps implements StrategyInterface {
public function showTitle($title) {
return strtoupper ($title);
}
public function showAuthor($author) {
return strtoupper ($author);
}
}
class StrategyExclaim implements StrategyInterface {
public function showTitle($title) {
return Str_replace(' ','!',$title);
}
public function showAuthor($author) {
return Str_replace(' ','!',$author);
}
}
class StrategyStars implements StrategyInterface {
public function showTitle($title) {
return Str_replace(' ','*',$title);
}
public function showAuthor($author) {
return Str_replace(' ','*',$author);
}
}
class Book {
private $author;
private $title;
private $strategy;
function __construct($strategy, $title_in, $author_in) {
$this->strategy = new StrategyContext($strategy);
$this->author = $author_in;
$this->title = $title_in;
}
function getAuthor() {
return $this->strategy->showAuthor($this->author);
}
function getTitle() {
return $this->strategy->showTitle($this->title);
}
function getAuthorAndTitle() {
return $this->getTitle() . ' by ' . $this->getAuthor();
}
}
writeln('BEGIN TESTING STRATEGY PATTERN');
writeln('');
$type = 'Caps';
$book = new Book($type, 'PHP for Cats','Zeev Suraski');
writeln($book->getAuthorAndTitle());
$type = 'Exclaim';
$book = new Book($type, 'PHP for Unicorns','Rasmus Lerdorf');
writeln($book->getAuthorAndTitle());
$type = 'Stars';
$book = new Book($type, 'PHP for Ponys','Andi Gutmans');
writeln($book->getAuthorAndTitle());
function writeln($line_in) {
echo $line_in.PHP_EOL;
}
Update:
So if you are using an ORM we can assume that Book
is your model class. I don't have any knowledge about Eloquent and how it handles data binding etc. so I'll make it as simple as I can. So I assume you can use a constructor with the binded data from database.
Keep your StrategyContext
and the actual strategy classes -where your biz logic will be coded- as a service and use dependency injection while finding out which strategy you will use. This way you can bind all your strategies depending on your type
variable, into your Model object.
Updated version of Book class,
class Book {
private $author = "Zeev Suraski";
private $title = "PHP for Cats";
private $strategy;
private $type = 'Caps';
function __construct() {
$this->strategy = new StrategyContext($this->type); //Dependency injection here
}
function getAuthor() {
return $this->strategy->showAuthor($this->author);
}
function getTitle() {
return $this->strategy->showTitle($this->title);
}
function getAuthorAndTitle() {
return $this->getTitle() . ' by ' . $this->getAuthor();
}
function setType($type) {
$this->type = $type;
}
function setStrategy($type=null) {
if($type==null) {
$this->strategy = new StrategyContext($this->type); //Dependency injection here
} else {
$this->strategy = new StrategyContext($type); //Dependency injection here
$this->setType($type);
}
}
}
writeln('BEGIN TESTING STRATEGY PATTERN');
writeln('');
$book = new Book();
writeln($book->getAuthorAndTitle());
$type = 'Exclaim';
$book->setType($type);
$book->setStrategy();
writeln($book->getAuthorAndTitle());
$type = 'Stars';
$book->setStrategy($type);
writeln($book->getAuthorAndTitle());
function writeln($line_in) {
echo $line_in.PHP_EOL;
}
Return Objects (PHP Best Practices)
It seems like a perfectly reasonable approach to me.
In terms of being consistent in how I am handling the return data from methods
you could make the response class implement a Response interface, then you'll know that all types of response classes will adhere to the same rules so you can safely use it throughout your application:
interface MyResponseInterface
{
public function getResponse();
public function getDebugMessage();
public function getSuccess();
public function getMysqliConnection();
}
class JsqlResponse implements MyResponseInterface
{
// ...
}
Then you know that whenever your object returns either a JsqlResponse
, a TimeResponse
, a MemberResponse
etc, that they shall all implement your response interface, and as such your public getters will be available, e.g.:
/** @var MyResponseInterface $return */
$return = $db->connect(...);
if($return->getSuccess()) {
echo $return->getResponse();
} else {
echo $return->getDebugMessage();
}
Note: In my example of different kinds of responses you could return, I'd imagine (hypothetically) that Time and Member may not need a MySQL connection, so perhaps you could omit that from the MyResponseInterface
and create a new interface for database connections, e.g. MyDatabaseInterface
. Generic response classes would provide responses, debug messages and a success method.
PHP: Best practice regarding public visibility in OOP
Best practice is to pick up a coding standard and follow it (and put info about it somewhere in your code).
I guess PSR is most commonly used in PHP:
https://github.com/php-fig/fig-standards/tree/master/accepted
and according to PSR-2:
"Visibility MUST be declared on all properties."
so second option would be a way to go.
you can also check this:
http://www.phptherightway.com/
Object Oriented Programming - Best Practice?
I would say option B. In that way you separate concerns: you have a Note
that can be reused anywhere (and not necessarily on a networked application), and you have a manager class that only cares with server communication.
You may also think on implement logic for multiple servers. For example, you may want to comunicate with data formats like JSON or XML. You may implement an interface (example, interface INoteManager
) and then implement two classes with servers for each of the data types I mentioned (example, NoteManagerXml
and NoteManagerJson
).
The main point on this question is sepration of concerns. Hope I've helped! :)
Related Topics
Scrape Web Site Generated by JavaScript
The Correct Way to Delete All Files Older Than 2 Days in PHP
Where Exactly Do I Put a Session_Start
How Is Output(Echo) Array Without Use of JSON_Encode(Codeigniter)
Symfony2 Collection of Entities - How to Add/Remove Association with Existing Entities
Set Application_Env via Virtual Host Config and Read This in PHP
Cannot Call Function SQLsrv_Connect()
PHP File Uploads Doesnot Read $_Files['Image']
Display Single Column Value of MySQLi Query
Concatenate PHP Function Output to a String Like Variables
How to Implement "Maintenance Mode" on Already Established Website
Check to See If an Email Is Already in the Database Using Prepared Statements
How to Flush Data to Browser But Continue Executing
Any Aws Eb Laravel Route Getting 404 Not Found Nginx/1.16.1