Comment for bug that needs to get fixed.
[htmlpurifier.git] / docs / dev-includes.txt
4 The Problem
5 -----------
7 HTML Purifier contains a number of extra components that are not used all
8 of the time, only if the user explicitly specifies that we should use
9 them.
11 Some of these optional components are optionally included (Filter,
12 Language, Lexer, Printer), while others are included all the time
13 (Injector, URIFilter, HTMLModule, URIScheme). We will stipulate that these
14 are all developer specified: it is conceivable that certain Tokens are not
15 used, but this is user-dependent and should not be trusted.
17 We should come up with a consistent way to handle these things and ensure
18 that we get the maximum performance when there is bytecode caches and
19 when there are not. Unfortunately, these two goals seem contrary to each
20 other.
22 A peripheral issue is the performance of ConfigSchema, which has been
23 shown take a large, constant amount of initialization time, and is
24 intricately linked to the issue of includes due to its pervasive use
25 in our plugin architecture.
27 Pros and Cons
28 -------------
30 We will assume that user-based extensions will be included by them.
32 Conditional includes:
33   Pros:
34     - User management is simplified; only a single directive needs to be set
35     - Only necessary code is included
36   Cons:
37     - Doesn't play nicely with opcode caches
38     - Adds complexity to standalone version
39     - Optional configuration directives are not exposed without a little
40       extra coaxing (not implemented yet)
42 Include it all:
43   Pros:
44     - User management is still simple
45     - Plays nicely with opcode caches and standalone version
46     - All configuration directives are present
47   Cons:
48     - Lots of (how much?) extra code is included
49     - Classes that inherit from external libraries will cause compile
50       errors
52 Build an include stub (Let's do this!):
53   Pros:
54     - Only necessary code is included
55     - Plays nicely with opcode caches and standalone version
56     - require (without once) can be used, see above
57     - Could further extend as a compilation to one file
58   Cons:
59     - Not implemented yet
60     - Requires user intervention and use of a command line script
61     - Standalone script must be chained to this
62     - More complex and compiled-language-like
63     - Requires a whole new class of system-wide configuration directives,
64       as configuration objects can be reused
65     - Determining what needs to be included can be complex (see above)
66     - No way of autodetecting dynamically instantiated classes
67     - Might be slow
69 Include stubs
70 -------------
72 This solution may be "just right" for users who are heavily oriented
73 towards performance. However, there are a number of picky implementation
74 details to work out beforehand.
76 The number one concern is how to make the HTML Purifier files "work
77 out of the box", while still being able to easily get them into a form
78 that works with this setup. As the codebase stands right now, it would
79 be necessary to strip out all of the require_once calls. The only way
80 we could get rid of the require_once calls is to use __autoload or
81 use the stub for all cases (which might not be a bad idea).
83     Aside
84     -----
85     An important thing to remember, however, is that these require_once's
86     are valuable data about what classes a file needs. Unfortunately, there's
87     no distinction between whether or not the file is needed all the time,
88     or whether or not it is one of our "optional" files. Thus, it is
89     effectively useless.
91     Deprecated
92     ----------
93     One of the things I'd like to do is have the code search for any classes
94     that are explicitly mentioned in the code. If a class isn't mentioned, I
95     get to assume that it is "optional," i.e. included via introspection.
96     The choice is either to use PHP's tokenizer or use regexps; regexps would
97     be faster but a tokenizer would be more correct. If this ends up being
98     unfeasible, adding dependency comments isn't a bad idea. (This could
99     even be done automatically by search/replacing require_once, although
100     we'd have to manually inspect the results for the optional requires.)
102     NOTE: This ends up not being necessary, as we're going to make the user
103     figure out all the extra classes they need, and only include the core
104     which is predetermined.
106 Using the autoload framework with include stubs works nicely with
107 introspective classes: instead of having to have require_once inside
108 the function, we can let autoload do the work; we simply need to
109 new $class or accept the object straight from the caller. Handling filters
110 becomes a simple matter of ticking off configuration directives, and
111 if ConfigSchema spits out errors, adding the necessary includes. We could
112 also use the autoload framework as a fallback, in case the user forgets
113 to make the include, but doesn't really care about performance.
115     Insight
116     -------
117     All of this talk is merely a natural extension of what our current
118     standalone functionality does. However, instead of having our code
119     perform the includes, or attempting to inline everything that possibly
120     could be used, we boot the issue to the user, making them include
121     everything or setup the fallback autoload handler.
123 Configuration Schema
124 --------------------
126 A common deficiency for all of the conditional include setups (including
127 the dynamically built include PHP stub) is that if one of this
128 conditionally included files includes a configuration directive, it
129 is not accessible to configdoc. A stopgap solution for this problem is
130 to have it piggy-back off of the data in the merge-library.php script
131 to figure out what extra files it needs to include, but if the file also
132 inherits classes that don't exist, we're in big trouble.
134 I think it's high time we centralized the configuration documentation.
135 However, the type checking has been a great boon for the library, and
136 I'd like to keep that. The compromise is to use some other source, and
137 then parse it into the ConfigSchema internal format (sans all of those
138 nasty documentation strings which we really don't need at runtime) and
139 serialize that for future use.
141 The next question is that of format. XML is very verbose, and the prospect
142 of setting defaults in it gives me willies. However, this may be necessary.
143 Splitting up the file into manageable chunks may alleviate this trouble,
144 and we may be even want to create our own format optimized for specifying
145 configuration. It might look like (based off the PHPT format, which is
146 nicely compact yet unambiguous and human-readable):
148 Core.HiddenElements
149 TYPE:    lookup
150 DEFAULT: array('script', 'style') // auto-converted during processing
151 --ALIASES--
152 Core.InvisibleElements, Core.StupidElements
155   Blah blah
156 </p>
158 The first line is the directive name, the lines after that prior to the
159 first --HEADER-- block are single-line values, and then after that
160 the multiline values are there. No value is restricted to a particular
161 format: DEFAULT could very well be multiline if that would be easier.
162 This would make it insanely easy, also, to add arbitrary extra parameters,
163 like:
165 VERSION:  3.0.0
166 ALLOWED:  'none', 'light', 'medium', 'heavy' // this is wrapped in array()
167 EXTERNAL: CSSTidy // this would be documented somewhere else with a URL
169 The final loss would be that you wouldn't know what file the directive
170 was used in; with some clever regexps it should be possible to
171 figure out where $config->get($ns, $d); occurs. Reflective calls to
172 the configuration object is mitigated by the fact that getBatch is
173 used, so we can simply talk about that in the namespace definition page.
174 This might be slow, but it would only happen when we are creating
175 the documentation for consumption, and is sugar.
177 We can put this in a schema/ directory, outside of HTML Purifier. The serialized
178 data gets treated like entities.ser.
180 The final thing that needs to be handled is user defined configurations.
181 They can be added at runtime using ConfigSchema::registerDirectory()
182 which globs the directory and grabs all of the directives to be incorporated
183 in. Then, the result is saved. We may want to take advantage of the
184 DefinitionCache framework, although it is not altogether certain what
185 configuration directives would be used to generate our key (meta-directives!)
187     Further thoughts
188     ----------------
189     Our master configuration schema will only need to be updated once
190     every new version, so it's easily versionable. User specified
191     schema files are far more volatile, but it's far too expensive
192     to check the filemtimes of all the files, so a DefinitionRev style
193     mechanism works better. However, we can uniquely identify the
194     schema based on the directories they loaded, so there's no need
195     for a DefinitionId until we give them full programmatic control.
197     These variables should be directly incorporated into ConfigSchema,
198     and ConfigSchema should handle serialization. Some refactoring will be
199     necessary for the DefinitionCache classes, as they are built with
200     Config in mind. If the user changes something, the cache file gets
201     rebuilt. If the version changes, the cache file gets rebuilt. Since
202     our unit tests flush the caches before we start, and the operation is
203     pretty fast, this will not negatively impact unit testing.
205 One last thing: certain configuration directives require that files
206 get added. They may even be specified dynamically. It is not a good idea
207 for the HTMLPurifier_Config object to be used directly for such matters.
208 Instead, the userland code should explicitly perform the includes. We may
209 put in something like:
211 REQUIRES: HTMLPurifier_Filter_ExtractStyleBlocks
213 To indicate that if that class doesn't exist, and the user is attempting
214 to use the directive, we should fatally error out. The stub includes the core files,
215 and the user includes everything else. Any reflective things like new
216 $class would be required to tie in with the configuration.
218 It would work very well with rarely used configuration options, but it
219 wouldn't be so good for "core" parts that can be disabled. In such cases
220 the core include file would need to be modified, and the only way
221 to properly do this is use the configuration object. Once again, our
222 ability to create cache keys saves the day again: we can create arbitrary
223 stub files for arbitrary configurations and include those. They could
224 even be the single file affairs. The only thing we'd need to include,
225 then, would be HTMLPurifier_Config! Then, the configuration object would
226 load the library.
228     An aside...
229     -----------
230     One questions, however, the wisdom of letting PHP files write other PHP
231     files. It seems like a recipe for disaster, or at least lots of headaches
232     in highly secured setups, where PHP does not have the ability to write
233     to its root. In such cases, we could use sticky bits or tell the user
234     to manually generate the file.
236     The other troublesome bit is actually doing the calculations necessary.
237     For certain cases, it's simple (such as URIScheme), but for AttrDef
238     and HTMLModule the dependency trees are very complex in relation to
239     %HTML.Allowed and friends. I think that this idea should be shelved
240     and looked at a later, less insane date.
242 An interesting dilemma presents itself when a configuration form is offered
243 to the user. Normally, the configuration object is not accessible without
244 editing PHP code; this facility changes thing. The sensible thing to do
245 is stipulate that all classes required by the directives you allow must
246 be included.
248 Unit testing
249 ------------
251 Setting up the parsing and translation into our existing format would not
252 be difficult to do. It might represent a good time for us to rethink our
253 tests for these facilities; as creative as they are, they are often hacky
254 and require public visibility for things that ought to be protected.
255 This is especially applicable for our DefinitionCache tests.
257 Migration
258 ---------
260 Because we are not *adding* anything essentially new, it should be trivial
261 to write a script to take our existing data and dump it into the new format.
262 Well, not trivial, but fairly easy to accomplish. Primary implementation
263 difficulties would probably involve formatting the file nicely.
265 Backwards-compatibility
266 -----------------------
268 I expect that the ConfigSchema methods should stick around for a little bit,
269 but display E_USER_NOTICE warnings that they are deprecated. This will
270 require documentation!
272 New stuff
273 ---------
275 VERSION: Version number directive was introduced
276 DEPRECATED-VERSION: If the directive was deprecated, when was it deprecated?
277 DEPRECATED-USE: If the directive was deprecated, what should the user use now?
278 REQUIRES: What classes does this configuration directive require, but are
279     not part of the HTML Purifier core?
281     vim: et sw=4 sts=4