~coughphp/coughphp/2.0

« back to all changes in this revision

Viewing changes to design/ProblemsWithStaticFactoryMethods.markdown

  • Committer: Anthony Bush
  • Date: 2008-08-23 03:35:08 UTC
  • mfrom: (262.1.18 coughphp-release-1.3)
  • Revision ID: anthony@anthonybush.com-20080823033508-uy4yn5pmio6wcetv
Accept release-1.3 branch changes into trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
<?php
2
 
 
3
 
abstract class CoughObject {
4
 
        protected static $dbName = null;
5
 
        
6
 
        public function printDbName() {
7
 
                echo 'Database Name: ' . self::$dbName . "\n";
8
 
        }
9
 
}
10
 
 
11
 
class TestObject extends CoughObject {
12
 
        protected static $dbName = 'test_db_name';
13
 
        
14
 
}
15
 
 
16
 
$testObj = new TestObject();
17
 
$testObj->printDbName();
18
 
// Because dbName is static, we get the wrong value. If we copy and paste the printDbName function into the TestObject class, then we get the right value, but that doesn't help us.
19
 
 
20
 
// The only thing I can think of so far is to either (1) don't put the `protected static $dbName = null;` into the abstract class or (2) find another way using non-static variables to make the factory retrieveByPk methods work.
21
 
// Note that (1) works but has the same problem on the next class that extends it; For example If we have BundleProduct extends Product then the Product static variables are the ones we see.
22
 
 
23
 
?>
24
 
 
25
 
These problems can be fixed if we want to make the generation a required part of building Cough classes. We can give the user the access to a schema file (e.g. XML schema like Propel) where they can perform customizations...
26
 
 
27
 
The idea is that in the above example  we can remove the protected static $dbName = null; from CoughObject and it will work. Extending TestObject and trying to change the dbName will be considered invalid, and bad practice. To have to simpilar objects on different databases the desired thing would be to make TestObject abstract, remove the protected static $dbName = 'test_db_name'; and write two new classes, TestObjectA and TestObjectB that each set the dbName to what they each need.
28
 
 
29
 
 
30
 
 
31
 
 
32
 
 
33
 
Another Solution (2007-07-25)
34
 
-----------------------------
35
 
 
36
 
We can leave the existing check/load methods in place and have the static methods call them... Note that this is somewhat inefficient because in the default case of constructByKey we construct two objects in memory, discarding one. Collections will not be effected by this, but simple stuff will:
37
 
 
38
 
        $ticket = Ticket::construct($id);
39
 
 
40
 
construct
41
 
constructByKey
42
 
constructByFields
43
 
 
44
 
        public static function construct($idOrFields) {
45
 
                if (is_array($idOrFields)) {
46
 
                        return self::constructByFields($idOrFields);
47
 
                } else {
48
 
                        return self::constructByKey($idOrFields);
49
 
                }
50
 
        }
51
 
        
52
 
        public static function constructByKey($idOrHash) {
53
 
                // We need to run SQL to get it... Only other way is to do this:
54
 
                $obj = new ConcreteClassName($idOrHash);
55
 
                if (is_array($idOrHash)) {
56
 
                        $obj->load();
57
 
                } else {
58
 
                        // don't need to load if we keep the object autoloading when a non-array value is passed.
59
 
                }
60
 
                // now discard the object we just created and pass controll to constructByFields:
61
 
                return self::constructByFields($obj->getFields());
62
 
        }
63
 
        
64
 
        public static function constructByFields($hash) {
65
 
                // DEFAULT:
66
 
                return new ConcreteClassName($hash); // note that we are saying the generator must provide this method because it won't work otherwise, i.e. class name is not available from within a static method call.
67
 
                
68
 
                // POSSIBLE OVERRIDEN BEHAVIOR.
69
 
                switch ($hash['type']) {
70
 
                        case 'type_one':
71
 
                                return new TypeOne($hash);
72
 
                        break;
73
 
                        case 'type_two':
74
 
                                return new TypeTwo($hash);
75
 
                        break;
76
 
                        default:
77
 
                                return new ConcreteClassName($hash);
78
 
                        break;
79
 
                }
80
 
        }
81
 
 
82
 
 
83
 
More talk (2007-07-25)
84
 
----------------------
85
 
 
86
 
What if we say as a rule the only "check"/"load" methods that exist ARE static methods? This means __construct should only initialize any values passed in and should not run any checks, even if the value is a single id. We can then remove all the check/load methods, basically changing each of them to be a static method. Then we only need the dbName/tableName/pkFieldNames to be static? Actually, those values could be hard-coded in the generated static methods, e.g.:
87
 
 
88
 
        public static function constructByKey($idOrHash) {
89
 
                if (is_array($idOrHash)) {
90
 
                        return self::constructByCriteria($idOrHash);
91
 
                } else {
92
 
                        $hash = array();
93
 
                        foreach (self::$pkFieldNames as $pkFieldName) {
94
 
                                $hash[$pkFieldName] = $idOrHash;
95
 
                        }
96
 
                        return self::constructByCriteria($hash);
97
 
                }
98
 
        }
99
 
 
100
 
        /**
101
 
         * Provides a way to `check` by an array of "key" => "value" pairs.
102
 
         *
103
 
         * @param array $where - an array of "key" => "value" pairs to search for
104
 
         * @param boolean $additionalSql - add ORDER BYs and LIMITs here.
105
 
         * @return mixed - the initialized object if found, null otherwise.
106
 
         * @author Anthony Bush
107
 
         **/
108
 
        public static function constructByCriteria($where = array(), $additionalSql = '') {
109
 
                $db = DatabaseFactory::getDatabase(self::$dbName);
110
 
                if ( ! empty($where)) {
111
 
                        $sql = 'SELECT * FROM ' . self::$dbName . '.' . self::$tableName
112
 
                             . ' ' . $db->generateWhere($where) . ' ' . $additionalSql;
113
 
                        return self::constructBySql($sql);
114
 
                }
115
 
                return null;
116
 
        }
117
 
        
118
 
        /**
119
 
         * Provides a way to `check` by custom SQL.
120
 
         *
121
 
         * @param string $sql - custom SQL to use during the check
122
 
         * @return mixed - the initialized object if found, null otherwise.
123
 
         * @author Anthony Bush
124
 
         **/
125
 
        public static function constructBySql($sql) {
126
 
                $db = DatabaseFactory::getDatabase(self::$dbName);
127
 
                $result = $db->query($sql);
128
 
                if ($row = $result->getRow()) {
129
 
                        return self::constructByFields($row);
130
 
                } else {
131
 
                        return null;
132
 
                }
133
 
        }
134
 
        
135
 
        public static function constructByFields($fields) {
136
 
                return new ConcreteClassName($fields);
137
 
        }