~coughphp/coughphp/2.0

« back to all changes in this revision

Viewing changes to design/TODO-ish.txt

  • 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
 
Stop flattening many-to-many collection accessors.
2
 
-------------------------------------------------
3
 
 
4
 
Use case:
5
 
 
6
 
        $product = new Product(1);
7
 
        $newProduct = clone $product;
8
 
        foreach ($product->getOs_Collection() as $os) {
9
 
                $newProduct->addOs($os);
10
 
        }
11
 
        $newProduct->save();
12
 
 
13
 
The above code updates the join fields for the product2os table, removing the oses from the other product.
14
 
 
15
 
 
16
 
 
17
 
 
18
 
 
19
 
// Generator TODO, gen getters for the primary key fields, e.g. getProductId(). getKeyId() should just be an alias OR better, build from the self::$primaryKeyDefinition = array('product_id');
20
 
 
21
 
 
22
 
setKeyId UPDATED -- now handles multi-key while allowing old usage.
23
 
getKeyId UPDATED -- now handles multi-key while allowing old usage (return just value for single-key).
24
 
hasKeyId UPDATED -- now handles multi-key PKs.
25
 
getKeyName --- Removing usage of this function internally to Cough. Should remove it completely though, because all other code that might want to use it will have to check if it is an array or not, so they may as well just getPkFieldNames() and iterate through.
26
 
 
27
 
isKey REMOVED
28
 
 
29
 
 
30
 
 
31
 
SUMMARY
32
 
-------
33
 
 
34
 
The primary key values will be stored in the $fields array.
35
 
 
36
 
The primary key field info will exist in the $fieldDefintions array, just like other fields.
37
 
 
38
 
Which fields comprise the primary key will be stored in $pkFieldNames
39
 
 
40
 
To get the primary key "id", getKeyId will still work and will return an array of [field_name] => [field_value] pairs when the PK is multi-key.
41
 
 
42
 
You can still call setKeyId, but it will act on all primary key fields, e.g. setting them all to null if you call setKeyId(null).
43
 
 
44
 
New getters:
45
 
 
46
 
        getPkFieldNames();
47
 
        getPk();
48
 
 
49
 
Still working
50
 
 
51
 
        setKeyId()
52
 
        getKeyId()
53
 
        hasKeyId()
54
 
 
55
 
Removed
56
 
 
57
 
        getKeyName()
58
 
        isKey()
59
 
        checkBy($key, $value)
60
 
 
61
 
Renamed
62
 
        checkByArray -> checkByCriteria and changed second parameter to additionSql
63
 
 
64
 
 
65
 
 
66
 
 
67
 
 
68
 
NEXT TIME:
69
 
 
70
 
 
71
 
* Fix CoughCollection save method so that CoughObject can just call save() on the collection object.
72
 
 
73
 
* Start making CoughCollection keyless...
74
 
 
75
 
        * There are some issues with this.
76
 
        * First, note that one of the reasons it needed to be keyless was because of complex joins (involving date ranges) -- there might be another way to solve that problem.
77
 
        * Another reason was for new objects that have no PK yet. There has to be a system in place for removing those and for retrieving those.
78
 
        * If we make it keyless, how will one remove items via a key? We have to search the list manually.
79
 
 
80
 
Regarding the above features, we may not be able to do them. At least, not without caveats.
81
 
 
82
 
First, there is an efficiency issue with going to KEYLESS. A remove based on an ID would have to iterate through all of them and compare the key ID. The benefit is that multi-key PK would be supported via that method. We might be able to solve that by having getKeyId flatting a multi-key PK either by imploding, building a hash, or serializing.
83
 
 
84
 
Second, not all entities have appropriate PKs. Consider the Wayne date range example. THIS IS NOW INVALID -- The wrong collection type was generated, and should either be fixed via smarter generator, generator configuration options (e.g. XML schema), OR simply overriding the defineCollections method to unset the incorrect type and add it back as the correct type, a one-to-many. Cough Generator WILL need to know to generate the join table CoughObject classes in that case, if it isn't modified to generate them all the time.
85
 
 
86
 
Third, can we accept that when you add a relationship where there is no PK on the collected element, we can't remove it? We could overcome this by allowing you to remove it via reference:
87
 
 
88
 
        $order->addOrderLine($orderLineWithNoPkYet);
89
 
        $order->removeOrderLine($orderLineWithNoPkYet); // will iterate through collection and remove it if found
90
 
 
91
 
### One to Many
92
 
 
93
 
Add:
94
 
 
95
 
        if (!is_object($elementToAdd)) { // Array of fields or ID => construct object with that data.
96
 
                $elementToAdd = new $this->elementClass($elementToAdd);
97
 
        }
98
 
        //$this->append($elementToAdd); // WRONG needs to go into a special array... but then foreach ($collection as $element) won't work :()
99
 
        
100
 
        if ($elementToAdd->hasKeyId()) {
101
 
                // add via key method
102
 
        } else {
103
 
                // append it :(
104
 
        }
105
 
 
106
 
Remove($idOrObject):
107
 
 
108
 
        protected function remove($idOrObject) {
109
 
                if (is_object($idOrObject)) {
110
 
                        if ($idOrObject->hasKeyId()) {
111
 
                                return $this->removeByKey($idOrObject->getKeyId());
112
 
                        } else {
113
 
                                return $this->removeByReference($idOrObject);
114
 
                        }
115
 
                } else {
116
 
                        return $this->removeByKey($idOrObject);
117
 
                }
118
 
        }
119
 
        
120
 
        protected function removeByKey($id) {
121
 
                if (isset($this[$idOrObject])) {
122
 
                        $object = $this[$idOrObject];
123
 
                        unset($this[$idOrObject]);
124
 
                } else {
125
 
                        $object = new $this->elementClass($idOrObject);
126
 
                }
127
 
        }
128
 
 
129
 
Ideas for removing: Don't have the Collection do anything except remove it from the "array", placing it into a removedElements array (which will be saved and keeps it out of the foreach loop in the mean time), and return a reference to the item removed. Then, the object can setup how removal should take place, e.g.
130
 
 
131
 
        public function removeOrderLine($orderLine) {
132
 
                $orderLine = $this->getCollection('orderLine')->remove($orderLine);
133
 
                $orderLine->setOrderId(null);
134
 
                return $orderLine;
135
 
        }
136
 
 
137
 
This makes changing that logic a breeze (override the above), and decouples that logic from the collection, which really is a generic collection of order lines and not a Order_OrderLine_Collection.
138
 
 
139
 
Before we continue with that, demonstrate that many-to-many will work in the above way:
140
 
 
141
 
        public function removeOs($os) {
142
 
                $os = $this->getCollection('os')->remove($os);
143
 
                $os->setJoinField('is_retired', 0); // old way, maybe still supported. If it is, it's simply going to do this:
144
 
                // $os->getProduct2Os_Object()->setIsRetired(0);
145
 
                return $os;
146
 
        }
147
 
 
148
 
Now, a complex many-to-many example:
149
 
 
150
 
        person table.
151
 
        role table.
152
 
        school table.
153
 
        
154
 
        enrollment table:
155
 
        person_id  role_id   school_id  start_date  end_date  is_retired
156
 
        Anthony    student   TTU        2005        2006      0
157
 
        Anthony    professor TTU        2006        2007      0
158
 
 
159
 
PK either has to be a separate column or a combination of everything. Let's assume it is a column on it's own.
160
 
 
161
 
AHA! The above should not be treated as a many-to-many but a one-to-many, e.g. $person->getEnrollment_Collection(). The generator currently treats this as a many to many, BUT the generator could be made to detect that we were going to generate TWO many-to-many using the same join table and thus prevent those from being generated and instead generate a one-to-many for the join table. Additionally, it could override the check function to join into the other tables, e.g.:
162
 
 
163
 
        public function checkEnrollment_Collection() {
164
 
                if ($this->hasKeyId()) {
165
 
                        $sql = '
166
 
                                SELECT
167
 
                                        enrollment.*
168
 
                                        , role.role_id                    AS `role.role_id`
169
 
                                        , role.role_type                  AS `role.role_type`
170
 
                                        , role.creation_datetime          AS `role.creation_datetime`
171
 
                                        , role.last_modified_datetime     AS `role.last_modified_datetime`
172
 
                                        , role.is_retired                 AS `role.is_retired`
173
 
                                        , school.school_id                AS `school.school_id`
174
 
                                        , school.school_name              AS `school.school_name`
175
 
                                        , school.creation_datetime        AS `school.creation_datetime`
176
 
                                        , school.last_modified_datetime   AS `school.last_modified_datetime`
177
 
                                        , school.is_retired               AS `school.is_retired`
178
 
                                FROM
179
 
                                        enrollment
180
 
                                        INNER JOIN role USING (role_id)
181
 
                                        INNER JOIN school USING (school_id)
182
 
                                WHERE
183
 
                                        ' . $this->db->generateWhere($this->getPk(), null, '') . '
184
 
                                        AND enrollment.is_retired = 0
185
 
                        ';
186
 
                } else {
187
 
                        $sql = '';
188
 
                }
189
 
                $this->_checkCollection('enrollment', '', $sql);
190
 
        }
191
 
 
192
 
2007-07-09 01:21:05: Looks good.
193
 
 
194
 
We may need to keep the tracking abilities of the collection in order for `removeAll()` to work (we were talking about having the removed elements returned back to parent and having items set, but when removeAll is called that doesn't happen...) There is a similar problem when adding. If we don't have all the keys yet, then we will have to make the sets later during the save process. This is sort of done now via the setForeignKeyFromParentPk() method, but does it handle things the way we want? Should all adds and removes be processed during the save process (the sets, that is)?
195
 
 
196
 
        $this->setJoinField('is_retired', 0);
197
 
        $this->save(); // go and save joinObject.
198
 
 
199
 
        $this->setOrderId(null);
200
 
        $this->save();
201
 
 
202
 
Just keep that in mind before moving the save logic to the collection.
203
 
        
204
 
        
205
 
 
206
 
 
207
 
 
208
 
* Can we deprecate getGetter and getSetter?
209
 
 
210
 
* Can we deprecate addToCollection, removeFromCollection, etc.? All that is require is to have generated calls to those function instead generate as either:
211
 
 
212
 
        $this->getCollection($collectionName)->add($objectsOrIDs, $joinFields);
213
 
or
214
 
        $this->getCollectionName_Collection()->add($objectsOrIDs, $joinFields);
215
 
 
216
 
* Remove: CoughCollection::getRandom()
217
 
 
218
 
* Address naming conflicts (getters are generated based on schema, but we also have getters in the class, e.g. getField, getInsertFields, getModifiedFields...)
219
 
 
220
 
* Consider putting things like '_Object' and '_Collection' as constants to a Cough class.
221
 
        class Cough {
222
 
                const OBJECT_SUFFIX = '_Object'; // empty string is more convenient, but only if your schema won't have naming collisions
223
 
                const COLLECTION_SUFFIX = '_Collection'; // 's' is more convenient, but ditto.
224
 
        }
225
 
 
226
 
* Update generator to generate getters for primary key fields and to comply with above changes.
227
 
 
228
 
* Solve issue with objects, i.e.
229
 
        1. Is there a way to set an object? For example, we have a product object with null os_id and an os object pre-loaded in memory. How can we give that to the product object? Currently have to $product->setOsId($os->getOsId()); and then it will be re-pulled from the database when something tries to $product->getOs().
230
 
 
231
 
        2. If we solve the above, go one step further and allow a single query to do this also, e.g.:
232
 
 
233
 
                SELECT
234
 
                        product.*
235
 
                        , os.*
236
 
                FROM
237
 
                        product
238
 
                        INNER JOIN os USING (os_id)
239
 
                WHERE
240
 
                        product.product_id = <product_id>
241
 
        
242
 
        The initFields / setFields should pass on the os data... We may want to solve this by having some functions do the select conversions (os.* to `os.field1`, `os.field2`, etc.) and parse them out on DB retrieval into an associative array:
243
 
 
244
 
                array(
245
 
                        'product' => array(
246
 
                                'field1' => 'value1',
247
 
                                'field2' => 'value2',
248
 
                        ),
249
 
                        'os' => array(
250
 
                                'field1' => 'value1',
251
 
                                'field2' => 'value2',
252
 
                        ),
253
 
                )
254
 
 
255
 
        3. If (2) is solved, make sure it works for collections because that is probably where it would be used, i.e. pulling a bunch of orders and their accounts, contacts, shipping, and billing info with one query, and instantiating all the objects in memory with that data.
256
 
 
257
 
        4. If (3) is solved, show an example of how to create a separate collection class that extends the default and adds the extra joins. Show how you can make other objects use that collection instead of the default, in case sometimes you just want to pull some data and sometimes you want all (if you always want all you can just override the default to always pull the data). Make sure that the _checkCollection call either works with the overridden collection definitions OR that you show how to override the checkBlah_Collection method.
258
 
 
259
 
        5. This may already be done in order to solve the above, but take a look at the join field implementation as it could be solved automatically by this (pass the join_table.field1, etc.) to the join field OBJECT. The thing that may be left to do is (1) generator should gen the object and (2) specify how many-to-many collections work. Propel uses some getRef-ish syntax.
260
 
        
261
 
                We might have solved this backwards?
262
 
                
263
 
                        $product->checkOs_Collection(); // same example, but now pretend it is many-to-many.
264
 
                
265
 
                        SELECT
266
 
                                product2os.*
267
 
                                os.*
268
 
                        ...
269
 
 
270
 
                        $product->addOs(new Os($rowDataWithBoth));
271
 
 
272
 
                If 1-3 above are solved, then it will not set join fields, but instead construct a join field object and initialize it with the values. This is good, EXCEPT: it's a little confusing... It means we could leave everything alone, actually... BUT the product object should also have a reference to the product2os object, thus the confusion. SO, the way propel solves this is just by having the product and os objects be slave to the product2os object. I'm not sure I like this as code gets a little more confusing, e.g.:
273
 
 
274
 
 
275
 
                        I'm a product. I want my Oses.
276
 
 
277
 
                        The current Cough way:
278
 
 
279
 
                                $product->getOs_Collection(); // current way = not confusing, except MAYBE how to set join fields. ($os->setJoinField could still work, but we'd probably want to DEPRECATE that and require $os->getProduct2Os_Object()->setSomeField($someValue); Keep in mind though, if you are just trying to change join field stuff you could save some queries and just change it for crying out loud:
280
 
 
281
 
                                $join = new Product2Os();
282
 
                                $join->loadByCriteria(array('product_id' => 1, 'os_id' => 2));
283
 
                                if ($join->isInflated()) {
284
 
                                        $join->setIsRetired(true);
285
 
                                        $join->save();
286
 
                                }
287
 
 
288
 
                                We want to get rid of the above though...
289
 
                                
290
 
                                $join = Produt2Os::retrieveByPk(array('product_id' => 1, 'os_id' => 2));
291
 
                                if ($join) {
292
 
                                        $join->setIsRetired(true);
293
 
                                        $join->save();
294
 
                                        // $join->delete(); // we should support this. A.S. can override the method to do nothing at AppCoughObject layer.
295
 
                                }
296
 
                        
297
 
                        The Propel way:
298
 
 
299
 
                                $osRefs = $product->getProduct2OsJoinOs();
300
 
                                $oses = array();
301
 
                                foreach ($osRefs as $osRef) {
302
 
                                        $oses[] = $osRef->getOs();
303
 
                                }
304
 
                                
305
 
 
306
 
 
307
 
$product = new Product(1); // this used to check. That functionality is now deprecated.
308
 
 
309
 
Instead, construction does only one thing: initializes fields. There are two choices:
310
 
 
311
 
instantiate the object with an associative array of [field_name] => [field_value] pairs OR
312
 
 
313
 
instantiate the object with a single value as in the above example. The single value will be treated as the primary key id. For objects with a multi-field primary key, you should pass in an array.
314
 
 
315
 
This allows you to update an items values without running two queries (SELECT & UPDATE). It's not necessarily good practice, but is a side effect of making Cough's interface more consistent.
316
 
 
317
 
        $product = new Product(1);
318
 
        $product->setProductName('New Name');
319
 
        $product->save();
320
 
 
321
 
 
322
 
 
323
 
 
324
 
 
325
 
DONE
326
 
====
327
 
 
328
 
* Move collections and objects into an array instead of attibutes so we have:
329
 
        $collectionDefinitions
330
 
        $collections
331
 
        $objectDefinitions
332
 
        $objects
333
 
 
334
 
Revision 13 Commit message
335
 
--------------------------
336
 
 
337
 
Renamed create() to insert() (so we now have matching insert/update functions).
338
 
Cleaned up how modifiedFields work (used to set duplicate entries in the modifiedFields array).
339
 
Added some "@see" documentation (phpDoc).
340
 
Cleaned up checking functions for the object. Less code, more clear, and more functionality as a side-effect.
341
 
 
342
 
(can easily override getLoadSql to get custom SQL whereas before it required overriding defineCheckStatement AND overriding setKeyId to recall that method anytime the key was changed, etc...)
343
 
 
344
 
Revision 14
345
 
-----------
346
 
 
347
 
* Split out logic for which fields get written to the database on insert and update.
348
 
        * This solves the A.S. issue without requiring them to override the entire insert function.
349
 
 
350
 
Revision ?
351
 
-----------
352
 
 
353
 
Cleaned up CoughObject::__construct() function.
354
 
 
355
 
Removed KEYLESS collection type -- KEYED full time.
356
 
 
357
 
Collection now does saving of objects instead of the logic living in CoughObject::save*ToManyCollection()
358
 
 
359
 
Simplified add/remove functions.
360
 
 
361
 
Removed getFirst, getLast, getRandom from CoughCollection (just use getPosition(); even that function is not that useful 80% of the time)
362
 
 
363
 
Removed CoughCollection::set() (and removeAll()) functions b/c the code that will handing the removing of collection elements will exist in the parent's remove function. It's also not that useful to begin with.
364
 
 - WRONG, it is useful. Consider this example:
365
 
 
366
 
        You have a form field with multiple check boxes, perhaps like this:
367
 
 
368
 
                Select Platforms: [ ] Mac [ ] Win [ ] Linux
369
 
 
370
 
        The markup for which might look like:
371
 
        
372
 
                <input type="checkbox" name="platform[]" value="1" /> Mac
373
 
                <input type="checkbox" name="platform[]" value="2" /> Win
374
 
                <input type="checkbox" name="platform[]" value="3" /> Linux
375
 
        
376
 
        Next, you want to easily save the state of checked items to the related entity (a Product). The setter can be used for this:
377
 
                
378
 
                if (isset($_POST['platform'])) {
379
 
                        $platforms = $_POST['platform'];
380
 
                } else {
381
 
                        $platforms = array(); // handles case when none where checked.
382
 
                }
383
 
                
384
 
                $product = new Product(1);
385
 
                $product->setOs_Collection($_POST['platform']);
386
 
        
387
 
        That's it!
388
 
        
389
 
        Now that we have proved the usefulness of the setCollection method, allow me to propose a solution. Continue to leave removeAll deprecated. Instead of having the set call removeAll and then add all the passed entities, instead have it run too loops:
390
 
 
391
 
                // (I've written this somewhere else, BTW):
392
 
                
393
 
                public function set($idsOrObjects) {
394
 
                        // First remove 
395
 
                        foreach ($this as $elementId => $element) {
396
 
                                if (isset($idsOrObjects[$elementId] || in_array($elementId, $idsOrObjects)) {
397
 
                                        // keep it
398
 
                                } else {
399
 
                                        $this->remove($elementId);
400
 
                                }
401
 
                        }
402
 
 
403
 
                        // Next Add
404
 
                        foreach ($idsOrObjects as $key => $value) {
405
 
                                if ($value instanceof CoughObject) {
406
 
                                        if (!$this->offsetExists($value->getKeyId())) {
407
 
                                                $this->add($value);
408
 
                                        }
409
 
                                } else {
410
 
                                        if (!$this->offsetExists($value)) {
411
 
                                                $this->add($value);
412
 
                                        }
413
 
                                }
414
 
                        }
415
 
                }
416
 
                
417
 
 
418
 
Added some magic method handling to CoughObject for objects, collections, and fields so that without generated functions (as long as the definitions are present) you can do:
419
 
 
420
 
        getFieldName();
421
 
        setFieldName($value);
422
 
        getObjectName_Object();
423
 
        checkObjectName_Object();
424
 
        getCollectionName_Collection();
425
 
        checkCollectionName_Collection();
426
 
        addObjectName($objectOrId, $joinFields); -- adds to the 'object_name' collection
427
 
        removeObjectName($objectOrId); -- removes from the 'object_name' collection
428
 
 
429
 
 
430
 
WHATS LEFT should be:
431
 
 
432
 
* Enhance "collector" behavior:
433
 
        CoughCollection::setCollectingObjectName('order');
434
 
        CoughCollection::setCollector($this);
435
 
        CoughObject::setCollectingObjectName($this->getCollectingObjectName());
436
 
        CoughObject::setCollector($this);
437
 
                $this->objects[$this->getCollectingObjectName()] = $collector;
438
 
        
439
 
        * Notice no calls to setOrder. If we just didn't want to go through work of finding the method name, neverfear, for setObject('order', $collector) is here.
440
 
 
441
 
* Enhance join fields handling via generated objects.
442
 
 
443
 
* Clean up definitions arrays (how does Propel do it?) Or just update the generator and forget about it.
444
 
 
445
 
 
446
 
 
447
 
 
448
 
 
449
 
 
450
 
 
451
 
 
452
 
GenieCough - Great Power, little bitty living space.
453
 
GenieCough - Great Power, Small Footprint.