It seems the topic got a bit sidetracked...
In a nutshell, unless you heavily invest in a function library OOP and classes have a few beneficial effects...
1. You spend more time designing (whether on paper, UML or with Unit Tests) and less time coding. Despite the intuitive reponse, this is a good thing. It lets you see the bigger picture. Planning is not evil - anyone who says differently probably spends their entire working day backtracking and "refactoring" after they finish something. There are some exceptions - notably the Agile Programming approach of Test Driven Development - but then that saves time from another direction, and does involve planning at some point.
2. You create fewer bugs. As well as coding less, OOP promotes good design, specialisation (each class does one and only one thing - and does it very well), simple easy to test methods, etc. It's harder to create bugs that are difficult to find - esp. when you regularly test and peer review.
3. It makes maintenance easier. Using classes you are not (or should not be) duplicating identical blocks of code across multiple files. If you see duplication its usually a "code smell" telling you it belongs in a class where its centralised and its potential bugs not copied across the entire application in a dozen locations!
4. Classes should (in an ideal world) be reuseable. If you create a good Database class, you can use it in other applications you start building. Eventually you may even build up a toolbox of useful classes - making development faster. Its important enough to note this is usually not what's done in "PHP Application Frameworks". Most are so interdependent you can't use their classes in isolation in something else. Other framework libraries like ezComponents, Horde and recently Zend Framework achieve this a lot better.
5. Classes are simple - the "complexity" and "difficult transition" are pure myths. All you need is a walkthrough of OOP, a few examples, and practice. It looks complex at first because its not immediately intuitive - that changes quickly.
6. All the cool kids are doing it

Well, not all, but many.
7. OOP/Classes promote the use of proven designs - Design Patterns. They make mince meat out of common problems, and well worth the time to learn and experiment with. These are ideas - not implemented code. You implement them in your own style most f the time - they just save you the time in concocting solutions on your own.
8. I've run out of points...bleh!
An example.
I dislike lumbering third party users with a heavy API so I like to "bundle" sets of functionality - let's say database reads and writes - into tiny packages. These are based on a Design Pattern, either Data Access Object or Active Record. Both basically give the idea of assembling any database table row into a single class with a simple interface.
A user example, User is a class which does nothing but hold the data from a row on the User table. These are accessed by a few method (getters) which match table fields. The data can be read or written to the database using a few CRUD methods. These actually just delegate to methods in a singular class called Data Acess Object which all such Data classes use.
This is actually in use so it probably makes some sense reading through it.
Code: Select all
// user with id=1, get, change name. then create new user with other name.
require('TransferObject.php');
require('DataAccess.php'); // DAO class - in actual code its injected into TO class's constructor as a singleton
require('User.php'); // Data class, extends TransferObject
// no sql, no long code blocks, just a simple API 6 year olds can use...
$user = new User();
$user->getByPk(1); // fetch by primary key, usually a unique field named "user_id"
$user->setName('Maugrim');
$user->save();
// new user
$user2 = new User();
$user2->setName('agtlewis');
$user2->save(); // save either updates/creates. It's tracked inside User class whether this is an existing row, or a new row.
echo $user->getName() . ' Updated.<br>';
echo $user2->getName() . ' Created.';
I'll stick the actual code below or parts of it at least (it's not perfect, but it does work as I need it to) - its open source stuff so puzzle over this if you like... It's a mishmashed set of classes for reading and writing database rows by automatically generating the SQL and executing it (via ADOdb-Lite), based on which fields are changed (touched), whether the row already exists, etc. Row objects I call Transfer Objects (differs among developers). Maybe this'll offer some small insight into OOP over a complex operation aimed at providing a simple API to users (without them needing to know anything more) - one of the cool things about OOP esp. here where I needed a simple API for third party module writers - important for a extendable game (what I'm currently working on).
Code:
Code: Select all
class TransferObject {
var $exists = false;
var $data = array();
var $touched = array();
var $primaryKey;
var $tableName;
var $transferName;
var $dao;
function TransferObject($fields=null) {
$appHelper =& ApplicationHelper::getInstance();
$this->dao =& $appHelper->getDAO(); // just using an apphelper to store a single instance of
// DAO (this is the Singleton Pattern essentially)
// can be injected into this class in other ways.
if($fields)
{
$this->import($fields);
}
}
// shortcut for creating a new TO from a raw associative array returned by the database without
// calling all the individual setters in the child class, i.e. setThis(), setThat()...
function import($fields=null) {
foreach($fields as $key=>$val)
{
$this->setValue($key, $val);
}
}
function &getArray() {
return $this->data;
}
function getValue($key) {
return $this->data[$key];
}
function setValue($key, $val) {
$this->data[$key] = $val;
}
function getPrimaryKey() {
return $this->primaryKey;
}
function getPrimaryKeyValue() {
return $this->data[$this->primaryKey];
}
function setPrimaryKeyValue($val) {
$this->data[$this->primaryKey] = $val;
}
function getTransferName() {
return $this->transferName;
}
function getTableName() {
return $this->tableName;
}
function getExists() {
return $this->exists;
}
function setExists($var = true) {
$this->exists = $var;
}
function getTouched() {
$this->touched = array_unique($this->touched); // remove duplicate values
return $this->touched;
}
function clearTouched() {
$this->touched = array();
}
function getByPk($value=null) {
if(isset($value))
{
$this->dao->getByPk($this, $value);
}
}
function save() {
$this->dao->save($this);
}
function delete() {
$this->dao->delete($this);
}
function getRow() {
$this->dao->getRow($this);
}
function getAll() {
return $this->dao->getAll($this);
}
}
class User extends TransferObject {
function User() {
parent::TransferObject();
$this->tableName = 'myapp_user';
$this->transferName = 'User';
$this->primaryKey = 'user_id';
}
function getUserId() { return $this->data['user_id']; }
function setUserId($var) { $this->data['user_id'] = $var; $this->touched[] = 'user_id'; }
function getUserName() { return $this->data['user_name']; }
function setUserName($var) { $this->data['user_name'] = $var; $this->touched[] = 'user_name'; }
}
class DataAccess {
var $db;
function DataAccess(&$db) {
$this->db = $db;
}
function &getInstance(&$db) {
static $thisInstance;
if(is_null($thisInstance))
{
$thisInstance =& new DataAccess($db);
}
return $thisInstance;
}
// SQL here is based on the ADOdb-Lite or ADOdb ability to escape/quote variables automatically
// ? stands for locations to bind quoted/escaped variables into the statement.
// $this->db is the ADOdb-Lite instance - Execute simply passes the query to the currently set DBMS
// usually PostgreSQL, but MySQL works too.
function &getByPk(&$to, $value=null) {
$sql = 'SELECT * FROM ' . $to->getTableName() . ' WHERE ' . $to->getPrimaryKey() .' = ?';
if(isset($value))
{
$result = $this->db->Execute($sql, array( $value ));
}
else
{
$result = $this->db->Execute($sql, array( $to->getPrimaryKeyValue() ));
}
if(!$result)
{
trigger_error($sql . '<br /><br />' . get_class($this) . ': ' . $this->db->ErrorMsg(), E_USER_ERROR);
}
if(!empty( $result->fields ))
{
$to->import( $result->fields );
$to->setExists(); // TO-> this row already exists, on next save() update not create!
}
else
{
$to->data = false;
}
$result->Close();
}
function save(&$to) {
if($to->getExists()) // if it already exists we update not create!
{
$touched = $this->getTouchedFields($to);
if(empty($touched['fields']))
{
// Note: Because we assume no errors, a mistake in correctly capturing
// changed fields by the relevant child TransferObject will not be reported
// as an error - this should be part of the test suite!
return; // we don't update rows which haven't changed!
}
$sql = 'UPDATE ' . $to->getTableName() . ' SET ' . implode(',', $touched['fields'] ) . ' WHERE ' . $to->getPrimaryKey() . ' = ?';
$touched['values'][] = $to->getPrimaryKeyValue();
$result = $this->db->Execute($sql, $touched['values']);
if(!$result)
{
trigger_error($sql . '<br /><br />' . get_class($this) . ': ' . $this->db->ErrorMsg(), E_USER_ERROR);
}
$to->clearTouched(); // we saved, so remove prior tracked changed fields
}
else // if it doesn't exist we create!
{
$insertArray =& $to->getArray();
//unset($insertArray[ $to->getPrimaryKey() ]); // inserted automatically by pg sequence
$sql = 'INSERT INTO ' . $to->getTableName() . ' (' . implode(',', array_keys( $insertArray )) . ') VALUES (';
// this is way too complex for comfort, alternative?
$a2 = str_repeat('? ', count($insertArray));
$a1 = explode(' ', rtrim( $a2 ) );
$bindings = implode(',', $a1);
$sql .= $bindings . ')';
$result = $this->db->Execute($sql, array_values($insertArray));
if(!$result)
{
trigger_error($sql . '<br /><br />' . get_class($this) . ': ' . $this->db->ErrorMsg(), E_USER_ERROR);
}
if($to->getPrimaryKeyValue() === NULL)
{
$to->setPrimaryKeyValue( $this->db->Insert_ID() );
}
$to->setExists(); // let the DAO (if called again) know this row now exists
$to->clearTouched(); // we saved, so remove prior tracked changed fields
}
}
// Last 2 methods are basically for splitting fieldnames from values. Since we use variable binding in statements
// to auto escape/quote values we need fieldnames and values in 2 separate arrays for the Execute method
function getTouchedFields(&$to) { // get tracked fields which have changed for update statements/get()'s
$touched = array();
$touched['fields'] = array();
$touched['values'] = array();
$touchedArray = $to->getTouched();
$fields =& $to->getArray();
foreach($fields as $key=>$val)
{
if(in_array($key, $touchedArray))
{
$touched['fields'][] = $key . ' = ?';
$touched['values'][] = $val;
}
}
return $touched;
}
function getSetFields(&$to) { // get all tracked fields which have values (not NULL)
$setFields =& $to->getArray();
$fields = array();
$fields['fields'] = array();
$fields['values'] = array();
foreach($setFields as $key=>$val)
{
$fields['fields'][] = $key . ' = ?';
$fields['values'][] = $val;
}
return $fields;
}
}