Let PHP write code for you
I'm working on a project. A neat new server. Well, the server itself (you know, that PowerEdge 2400 I talked about yesterday) seems to be, well, screwed somehow, but I wanted to *do* somethin anyway.
The magic word is LDAP. We want everything into an LDAP directory where possible. System users, quota, email aliases, Horde preferences, mailing list members, Samba as a PDC using LDAP as userbase/password backend, everything. I was able to set all of this up on my workstation, so that won't be a big problem.
There is one major issue tough: frontends. We were not able to find any decent frontend suited to our needs. Of course there is PhpLdapAdmin, which is a great tool to browse your tree and edit base-level stuff, but it's only usefull for directory administrators. You cannot expect a normal user to use PLA to change his password.
So we have to write everything ourself. Because we'd like to give the user both webbased and CLI interfaces for all administration tasks, PHP was chosen as development platform, using PEAR's Net_LDAP modules, which is kinda neat. You can use it to retrieve LDAP Schema information, which is what I need here.
The objective is simple: create classes in PHP that represent an LDAP objectClass. So if a user in LDAP is an "inetOrgPerson", I need a class in PHP called "inetOrgPerson", like $ikke = new inetOrgPerson( "uid=ikke,dc=test,dc=com");
Because LDAP supports objectClass inheritance, this should be reflected in the PHP code too. LDAP supports multiple inheritance, a PHP class can only inherit from one class, but normally this isn't a big issue, because most objectClasses only SUP one parent objectClass.
Writing PHP files with classes representing all possible attibuteTypes and objectClasses by hand didn't look like a pleasant foresight to me, so I wanted to automate things. Of course I could write everything by hand once, but then if new schema's would be added to the LDAP server, I'd have to write more classes, change inheritance,... which is just plain stupid.
So I started to write PHP classes and functions that create PHP class files for me, based on data it retrieves from the LDAP server. At the moment, attributeTypes and objectClasses are only represented using member variables and (read) accessor functions, so something like the example I gave is not possible (yet), but it shouldn't be too hard to get that working too.
This is what it looks like now:
* schemaItem
* atributeType inherits schemaItem
* homeDirectory inherits attributeType
* uid inherits attributeType
* ...
(attributeType inheritance isn't supported)
* objectClass inherits schemaItem
* top inherits objectClass
* person inherits top
* organisationalPerson inherits person
* inetOrgPerson inherits organisationalPerson
* ...
* ...
Every attributeType is represented by (attributeTypeName).class.php in a certain directory, and every objectClass as (objectClassName).class.php in some other directory.
Some classes provide the factory methods to create these class files: there is the base classBuilder class, which provides the following functions:
- classBuilder( $name, $super = null )
Constructor. $name is the name of the class to "build", $super is an optional base class $name should extend.
- addRequire( $req )
Add a "require_once( $req )" to the output file.
- addVariable( $type, $name, $private = false )
Add a member variable of type $type (mentioned in a comment), with name $name, to the class.
$private is not used yet, but could be used in the future to force prefixing private variables using an
_underscore_
- addVariableValue( $type, $name, $value, $vartype = VARIABLE_TYPE_STRING, $private = false )
Add a member variable which has a predefined value. $type, $name and $private are the same things as in
addVariable(3). $value is the standard value this variable should have, and $vartype defines the
type of the variable: STRING, INT or BOOL, so strings can be written like 'var $test = "test"',
integers like 'var $test = 1', and booleans like 'var $test = true' (watch the quotes).
- writeFile( $filename )
Finally writes the class structure to file $filename.
Next to these there are some more private functions, like one to generate a PHP-syntax-compatible representation of an Array (also multi-dimensional arrays because the function can call itself, I think it's pretty nifty :)) )
As you can see it is not possible to add member functions to classes, which should not be necessary, because the base classes ("schemaItem", "attributeType" and "objectClass") provide the necessay ones.
Next, I got Writer classes for every type I got:
* schemaItemWriter inheits classBuilder
* attributeTypeWriter inherits schemaItemWriter
* objectClassWriter inherits schemaItemWriter
These classes take data from the LDAP server to give classBuilder all the information it needs to generate the class files for all objects.
This could sound very obscure, but actually it isn't. I have to admit the code is not documented tough :oops: The PhpDoc tags are there (thank you Vim and the Vim PHPDoc plugin), but the correct data isn't in there ;)
Now the results:
cat attributeTypes/email.class.php
<?php
require_once( "attributeType.class.php" );
class email extends attributeType {
//Type: String
var $_oid = "1.2.840.113549.1.9.1";
//Type: String
var $_name = "email";
//Type: String
var $_equality = "caseIgnoreIA5Match";
//Type: String
var $_substring = "caseIgnoreIA5SubstringsMatch";
//Type: String
var $_syntax = "1.3.6.1.4.1.1466.115.121.1.26{128}";
//Type: Integer
var $_maxLength = 128;
//Type: Array
var $_aliases = array("0" => "emailAddress","1" => "pkcs9email");
}
?>
cat objectClasses/top.class.php objectClasses/person.class.php
<?php
require_once( "objectClass.class.php" );
class top extends objectClass {
//Type: String
var $_oid = "2.5.6.0";
//Type: String
var $_name = "top";
//Type: objectClass (Must)
var $objectClass;
}
?>
<?php
require_once( "top.class.php" );
class person extends top {
//Type: String
var $_oid = "2.5.6.6";
//Type: String
var $_name = "person";
//Type: Array
var $_supClasses = array("0" => "top");
//Type: sn (Must)
var $sn;
//Type: cn (Must)
var $cn;
//Type: userPassword (May)
var $userPassword;
//Type: telephoneNumber (May)
var $telephoneNumber;
//Type: seeAlso (May)
var $seeAlso;
//Type: description (May)
var $description;
//Type: Bool
var $_structural = true;
}
?>
ls attributeTypes objectClasses | wc -l
224
Generating these files manually would be a PITA ;-)
As you can see this code is not ready (yet): for the objectClasses, there should be validator functions (are all Must attributes set?), accessor functions for all attributes, and last but not least, pulling and pushing data from/to the LDAP directory...
I'm very happy with the current results. Once all schema information is pulled from the server by Net_LDAP, the parsing of this data and creation of class files is very fast. Adding the other necessary functions should be a breeze (well, not really ;-)) now.
Once this is done, we need to write some simple backend for everything (an abstraction layer between the frontends and the actual objectClasses etc, something the user nor the frontend scripter should be aware of), and of course the frontends. PEAR Console_Getopt looks usefull :-)
I'm at university at the moment: there is a LanWar night going on (I'm not gaming, but well...).