~coughphp/coughphp/2.0

« back to all changes in this revision

Viewing changes to design/Cough API.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
 
 
2
 
Cough API
3
 
=========
4
 
 
5
 
Definitions
6
 
Construction
7
 
 
8
 
Definitions
9
 
-----------
10
 
 
11
 
The minimal data set that must be defined is the database name, table name, fields, and primary key:
12
 
 
13
 
        class ConcreteCoughObject extends CoughObject {
14
 
                protected $dbName = 'db_name';
15
 
                protected $tableName = 'table_name';
16
 
                protected $fields = array(
17
 
                        'field1' => 'field1_default_value',
18
 
                        'field2' => 'field2_default_value',
19
 
                        'field3' => 'field3_default_value',
20
 
                        // ...
21
 
                        'fieldn' => 'fieldn_default_value'
22
 
                );
23
 
                protected $pkFieldNames = array('field1'); // multi-key PK example: array('field1','field2');
24
 
        }
25
 
 
26
 
Optional definitions include object and collection definitions:
27
 
 
28
 
        class ConcreteCoughObject extends CoughObject {
29
 
                protected $objectDefinitions = array(); // hash of [object_name] => [object definition]
30
 
                protected $collectionDefinitions = array(); // hash of [object_name] => [object definition]
31
 
        }
32
 
 
33
 
CoughObject calls a `initDefinitions()` function in the constructor which calls the following methods that may be used to override any of the above definitions:
34
 
 
35
 
        protected function initDefinitions() {
36
 
                $this->defineObjects();
37
 
                $this->defineCollections();
38
 
        }
39
 
 
40
 
If there is a need to populate object definitions by appending to the definitions that are already present, it is preferred to do it in `defineObjects` or `defineCollections` like so:
41
 
 
42
 
        protected function defineObjects() {
43
 
                parent::defineObjects();
44
 
                $this->objectDefinitions['object_name'] = array(); // fill in the array with the definitions
45
 
        }
46
 
        protected function defineCollections() {
47
 
                parent::defineCollections();
48
 
                $this->collectionDefinitions['collection_name'] = array(); // fill in the array with the definitions
49
 
        }
50
 
 
51
 
If needing to change the database name, table name or fields / default field values, just set them at the member variable level. If needing to do it at run-time, do it in the `initDefinitions()` method like so:
52
 
 
53
 
        protected function initDefinitions() {
54
 
                parent::initDefinitions();
55
 
                $possibleDbs = array('database1','database2');
56
 
                $dbIndex = array_rand($possibleDatabases);
57
 
                $this->dbName = $possibleDatabases[$dbIndex];
58
 
        }
59
 
 
60
 
Construction
61
 
------------
62
 
 
63
 
Construction of a CoughObject can be done in a few ways:
64
 
 
65
 
Using a single value that is the primary key id of the object:
66
 
 
67
 
        $object = new Object($id);
68
 
        $object = Object::construct($id); // TODO: if ID can not be found, should NULL be returned or an empty object?
69
 
 
70
 
The above methods will initialize the key ID and attempt to look up their related data in the database. If that data is already available, the following methods will work.
71
 
 
72
 
Using pre-existing data in array form (format of [field_name] => [field_value]):
73
 
 
74
 
        $object = new Object($hash);
75
 
        $object = Object::construct($hash);
76
 
 
77
 
The `construct()` static method will call one of two other static methods:
78
 
 
79
 
        public static function constructByPk($idOrIdArray) {
80
 
                if (is_array($idOrIdArray)) {
81
 
                        $object = new Object($idOrIdArray);
82
 
                        $object->load();
83
 
                } else {
84
 
                        $object = new Object($idOrIdArray);
85
 
                }
86
 
                return $object; // TODO if load gets no data, should we return NULL?
87
 
        }
88
 
 
89
 
If you have a multi-key primary key, then you will have to use the `constructByPk()` method.
90
 
 
91
 
        public static function constructByFields($hash) {
92
 
                return new Order($hash);
93
 
        }
94
 
 
95
 
 
96
 
### Example of overriding the static method (TODO: Moved to advanced section?) ###
97
 
 
98
 
In this static method example, either an Order or a Quote object is returned.
99
 
 
100
 
        $ticket = Ticket::construct($hash);
101
 
 
102
 
The above example might work with an overridden `constructByFields` method (which `construct` will call), defined as follows:
103
 
 
104
 
        class Ticket extends CoughObject {
105
 
                const TYPE_QUOTE = 1;
106
 
                const TYPE_ORDER = 2;
107
 
                public static function constructByFields($fields) {
108
 
                        switch ($fields['ticket_type_id']) {
109
 
                                case Ticket::TYPE_QUOTE:
110
 
                                        return new Quote($fields);
111
 
                                break;
112
 
                                case Ticket::TYPE_ORDER:
113
 
                                        return new Order($fields);
114
 
                                break;
115
 
                                default:
116
 
                                        return null;
117
 
                                break;
118
 
                        }
119
 
                }
120
 
        }
121
 
 
122
 
The hash data might look like:
123
 
 
124
 
        $hash = array(
125
 
                'ticket_id' => 123,
126
 
                'ticket_type_id' => Ticket::TYPE_QUOTE,
127
 
                'customer_id' => 312,
128
 
                'order_placed_datetime' => '2007-01-01 00:00:00'
129
 
        );
130
 
 
131
 
 
132
 
        
133
 
        $order = new Order($fields); // initializes the object with pre-existing data. `isInflated` will return true, as it is assumed the pre-existing data was pulled from the source.
134
 
 
135
 
        $order = new Order($id); // loads from database. Find out if load succeed via `$order->isInflated()` <- TODO: Rename that function.
136
 
 
137
 
 
138
 
        $order->load(); // loads from the database using the current key id. This method is useful when trying to construct a multi-PK object
139
 
                // for example:
140
 
                $order = new Order($multiKeyHash);
141
 
                $order->load();
142
 
 
143
 
                $ticket = Ticket::construct($hash); // will switch through the hash to figure out with type of object to return, an Order or a Quote.
144
 
 
145
 
 
146
 
Accessing Join attributes (this is collection-related topic)
147
 
-------------------------
148
 
 
149
 
Need to standardize the way of accessing join fields.
150
 
 
151
 
 
152
 
Static Construction Implementation
153
 
----------------------------------
154
 
 
155
 
After given this some thought, why don't we try this:
156
 
 
157
 
        protected static $dbName = 'asdlfjk';
158
 
 
159
 
the problem is that the member definition can only appear once, otherwise functions in parent classes where it is defined will not see the change. We can not overcome this by adding a `public static defineDbCOnfig()` method because the same problem will occur: parent classes will call the wrong one.
160
 
 
161
 
This might be why Propel uses a "Peer" class which contains static methods?
162
 
 
163
 
But, maybe one appearance is enough? If we are using the generator,then it can generate all these static methods and attributes for us. The question is how valuable is being able to override dbName, tableName, and all definitions (for fields, objects, and collections)? One would think the information is not dynamic at run time so there is no value in being able to override it -- you'd just change the values that are there. But, maybe someone wants to reuse some logic and they do it by extending an existing class and customizing some logic?  Sounds like in that case you should be using a different design pattern, perhaps the Strategy Pattern.
164
 
 
165
 
What if we generate, in the starter classes, the static methods and member variables that are needed?  This allows Cough to call the methods, allows the end user to customize them, and the only drawback I can think of is that hand-writing a Cough class might be harder (because of the static methods, the member variables are easy and required already).
166
 
 
167
 
Perhaps we should take a look at how much work there is in the static methods, and see if we can't have some of the static methods in the core Cough class... (e.g. can you do self::$dbName in a parent class when there isn't one defined until a sub class?)
168
 
 
169
 
Even if we don't want to require static methods, we could also provide an option for it. The problem is that we are trying to allow you to have a factory method that returns an object of the right type, but core Cough needs to know what that method is otherwise it will be stuck constructing only one type of object and not use your factory method (e.g. CoughCollection code that creates your elements... Now, we currently have an option to pass in the element name (if wanting to override it, but maybe we could set a variable for the factory method, if any, or even provide code that can be evaled))
170
 
 
171
 
 
172
 
Loading / Setting objects.
173
 
--------------------------
174
 
 
175
 
Listen closely, because this might solve the confusion about what setCollectionName_Collection() does (set the reference to the collection or call set on the collection which will "set" the state of the collection to what you give it, i.e. perform any needed adds and removes).
176
 
 
177
 
Here we go.
178
 
 
179
 
What if the load methods for objets and collections support parameters? For example, if you have preloaded a related object you need a way to set it so that an extra lookup isn't done. We were considering:
180
 
 
181
 
 
182
 
        <?php
183
 
        $manuf = new Manufacturer(1);
184
 
        $product = new Product(1);
185
 
        $product->setManufacturer_Object($manuf);
186
 
        
187
 
        // And then if you call get it doesn't load because the object is already available.
188
 
        $product->getManufacturer();
189
 
        ?>
190
 
 
191
 
But what about:
192
 
 
193
 
        <?php
194
 
        $manuf = new Manufacturer(1);
195
 
        $product = new Product(1);
196
 
        $product->loadManufacturer_Object($manuf);
197
 
        ?>
198
 
 
199
 
What we are saying here is that the load method will check arguments and only perform the load if nothing was passed in. If something was passed in, it will still be setting the object, it just won't do a database lookup.
200
 
 
201
 
NOTE: object loading should setObject (i.e. we are abstracting away the object data structure this time around, so use setObject/getObject)
202
 
 
203
 
Example object load method:
204
 
 
205
 
        <?php
206
 
        public function loadManufacturer_Object($hashOrObject = null) {
207
 
                if (is_null($hashOrObject)) {
208
 
                        // Do db lookup to get hash.
209
 
                        $sql = Manufacturer::getLoadSqlWithoutWhere();
210
 
                        $sql . = ' WHERE ' . $this->getDb()->generateWhere($this->getPk())
211
 
                }
212
 
                else if (is_array($hashOrObject)) {
213
 
                        // We got the data
214
 
                }
215
 
                else if (is_object($hashOrObject)) {
216
 
                        // We got the object, just set it:
217
 
                        $this->setObject('manufacturer', $hashOrObject);
218
 
                }
219
 
        }
220
 
        ?>
221
 
 
222
 
Static methods will need a static db object too... we should provide a getter for non static methods and static methods alike:
223
 
 
224
 
        self::getDb()->generateWhere($this->getPk())
225
 
 
226
 
The method might look like:
227
 
 
228
 
        public static function getDb() {
229
 
                if (is_null(self::$db)) {
230
 
                        self::$db = DatabaseFactory::getDatabase(self::$dbName);
231
 
                }
232
 
                return self::$db;
233
 
        }
234
 
 
235
 
If we go that route we might need to require that all generated classes implement a CoughStaticInterface or something that says the following methods must be defined:
236
 
 
237
 
        public static function getDb();
238
 
        public static function constructByKey($pk);
239
 
        public static function constructByFields($hash);
240
 
        public static function construct(); // ?
241
 
        // and more...
242
 
 
243