Fix incorrect PEARSax3 test assertion.
[htmlpurifier.git] / docs / proposal-plists.txt
blobeef8ade6174a1ccf03721e3661ce92e28c0c2510
1 THE UNIVERSAL DESIGN PATTERN: PROPERTIES
2 Steve Yegge
4 Implementation:
5     get(name)
6     put(name, value)
7     has(name)
8     remove(name)
9     iteration, with filtering [this will be our namespaces]
10     parent
12 Representations:
13     - Keys are strings
14     - It's nice to not need to quote keys (if we formulate our own language,
15       consider this)
16     - Property not present representation (key missing)
17     - Frequent removal/re-add may have null help. If null is valid, use
18       another value. (PHP semantics are weird here)
20 Data structures:
21     - LinkedHashMap is wonderful (O(1) access and maintains order)
22     - Using a special property that points to the parent is usual
23     - Multiple inheritance possible, need rules for which to lookup first
24     - Iterative inheritance is best
25     - Consider performance!
27 Deletion
28     - Tricky problem with inheritance
29     - Distinguish between "not found" and "look in my parent for the property"
30     [Maybe HTML Purifier won't allow deletion]
32 Read/write asymmetry (it's correct!)
34 Read-only plists
35     - Allow ability to freeze [this is what we have already]
36     - Don't overuse it
38 Performance:
39     - Intern strings (PHP does this already)
40     - Don't be case-insensitive
41     - If all properties in a plist are known a-priori, you can use a "perfect"
42       hash function. Often overkill.
43     - Copy-on-read caching "plundering" reduces lookup, but uses memory and can
44       grow stale. Use as last resort.
45     - Refactoring to fields. Watch for API compatibility, system complexity,
46       and lack of flexibility.
47     - Refrigerator: external data-structure to hold plists
49 Transient properties:
50     [Don't need to worry about this]
51     - Use a separate plist for transient properties
52     - Non-numeric override; numeric should ADD
53     - Deletion: removeTransientProperty() and transientlyRemoveProperty()
55 Persistence:
56     - XML/JSON are good
57     - Text-based is good for readability, maintainability and bootstrapping
58     - Compressed binary format for network transport [not necessary]
59     - RDBMS or XML database
61 Querying: [not relevant]
62     - XML database is nice for XPath/XQuery
63     - jQuery for JSON
64     - Just load it all into a program
66 Backfills/Data integrity:
67     - Use usual methods
68     - Lazy backfill is a nice hack
70 Type systems:
71     - Flags: ReadOnly, Permanent, DontEnum
72     - Typed properties isn't that useful [It's also Not-PHP]
73     - Seperate meta-list of directive properties IS useful
74     - Duck typing is useful for systems designed fully around properties pattern
76 Trade-off:
77     + Flexibility
78     + Extensibility
79     + Unit-testing/prototype-speed
80     - Performance
81     - Data integrity
82     - Navagability/Query-ability
83     - Reversability (hard to go back)
85 HTML Purifier
87 We are not happy with our current system of defining configuration directives,
88 because it has become clear that things will get a lot nicer if we allow
89 multiple namespaces, and there are some features that naturally lend themselves
90 to inheritance, which we do not really support well.
92 One of the considered implementation changes would be to go from a structure
93 like:
95 array(
96     'Namespace' => array(
97         'Directive' => 'val1',
98         'Directive2' => 'val2',
99     )
104 array(
105     'Namespace.Directive' => 'val1',
106     'Namespace.Directive2' => 'val2',
109 The below implementation takes more memory, however, and it makes it a bit
110 complicated to grab all values from a namespace.
112 The alternate implementation choice is to allow nested plists. This keeps
113 iteration easy, but is problematic for inheritance (it would be difficult
114 to distinguish a plist from an array) and retrieval (when specifying multiple
115 namespaces we would need some multiple de-referencing).
117 ----
119 We can bite the performance hit, and just do iteration with filter
120 (the strncmp call should be relatively cheap). Then, users should be able
121 to optimize doing something like:
123 $config = HTMLPurifier_Config::createDefault();
124 if (!file_exists('config.php')) {
125     // set up $config
126     $config->save('config.php');
127 } else {
128     $config->load('config.php');
131 Or maybe memcache, or something. This means that "// set up $config" must
132 not have any dynamic parts, or the user has to invalidate the cache when
133 they do update it. We have to think about this a little more carefully; the
134 file call might be more expensive.
136 ----
138 This might get expensive, however, when we actually care about iterating
139 over the configuration and want the actual values. So what about nesting the
140 lists?
142 "ns.sub.directive" => values['ns']['sub']['directive']
144 We can distinguish between plists and arrays by using ArrayObjects for the
145 plists, and regular arrays for the arrays? Alternatively, use ArrayObjects
146 for the arrays, and regular arrays for the plists.
148 ----
150 Implementation demands, and what has caused them:
152 1. DefinitionCache, the HTML, CSS and URI namespaces have caches attached to them
153    Results:
154     - getBatchSerial()
155         - getBatch() : in general, the ability to traverse just a namespace
157 2. AutoFormat/Filter, this is a plugin architecture, directives not hard-coded
158     - getBatch()
160 3. Configuration form
161     - Namespaces used to organize directives
163 Other than that, we have a pure plist. PERHAPS we should maintain separate things
164 for these different demands.
166 Issue 2: Directives for configuring the plugins are regular plists, but
167 when enabling them, while it's "plist-ish", what you're really doing is adding
168 them to an array of "autoformatters"/"filters" to enable. We can setup
169 magic BC as well as in the new interface, but there should also be an
170 add('AutoFormat', 'AutoParagraph'); which does the right thing.
172 One thing to consider is whether or not inheritance rules will apply to these.
173 I'd say yes. That means that they're still plisty, in fact, the underlying
174 implementation will probably be a plist. However, they will get their OWN
175 plists, and will NOT support nesting.
177 Issue 1: Our current implementation is generally not efficient; md5(serialize($foo))
178 is pretty expensive. So, I don't think there will be any problems if it
179 gets "less" efficient, as long as we give users a properly fast alternative;
180 DefinitionRev gives us a way to do this, by simply telling the user they must
181 update it whenever they update Configuration directives as well. (There are
182 obvious BC concerns here).
184 In such a case, we simply iterate over our plist (performing full retrievals
185 for each value), grab the entries we care about, and then serialize and hash.
186 It's going to be slow either way, due to the ability of plists to inherit.
187 If we ksort(), we don't have to traverse the entire array, however, the
188 cost of a ksort() call may not be worth it.
190 At this point, last time, I started worrying about the performance implications
191 of allowing inheritance, and wondering whether or not I wanted to squash
192 the plist. At first blush, our code might be under the assumption that
193 accessing properties is cheap; but actually we prefer to copy out the value
194 into a member variable if it's going to be used many times. With this is mind
195 I don't think CPU consumption from a few nested function calls is going to
196 be a problem. We *are* going to enforce a function only interface.
198 The next issue at hand is how we're going to manage the "special" plists,
199 which should still be able to be inherited. Basically, it means that multiple
200 plists would be attached to the configuration object, which is not the
201 best for memory performance. The alternative is to keep them all in one
202 big plist, and then eat the one-time cost of traversing the entire plist
203 to grab the appropriate values.
205 I think at this point we can write the generic interface, and then set up separate
206 plists if that ends up being necessary for performance (it probably won't.) Now
207 lets code our generic plist implementation.
209 ----
211 Iterating over the plist presents some problems. The way we've chosen to solve
212 this is to squash all of the parents.
214 ----
216 But I don't need iteration.
218     vim: et sw=4 sts=4