The Good
sqlprintf – my favorite. A function (in the db abstraction layer) that works a lot like prepared statements, but with a little bonus syntax. “WHERE x=^s AND y=^n” would insert a string at ^s, quoting it. ^n inserts an unquoted number. The string quoting prevents SQL injection attacks, and by specifying a number, you can perform a little extra bit of validation for unquoted numbers. I think there may have been some date conversion support too.
$model->next() doesn’t return an object, it updates $model’s properties. This is non-standard, and the common pattern is for next() to return an object, but by not creating and then later destroying an object, you gain a little bit of performance. This isn’t as inflexible as it sounds, because the typical use case is to read forward through a SELECT query.
Code generation. We went for code generation rather than dynamically creating our ORM objects, mainly because PHP starts a new environment with each page load, wiping out all the ORM work each time. This improved performance, and also allowed for slightly easier subclassing. You could generate the ORM class, and edit the output to create a new read-only or write-only class.
Use the directory tree to be the app’s object structure. Most MVC framworks use a single front controller and a map of URLs to dispatch code. We just used the directories and put a “controller” in the index.php in each directory. I find this makes debugging easier. This non-pattern is good for websites, but I can see that larger web apps might do better with a front controller pattern, particularly when you’re dealing with this proliferation of different devices access websites.
Copying ColdFusion ideas for the index.php pages. The CF style is top-to-bottom scripts. I was a proponent of that model of coding for the index.php pages. It’s not that you can’t have logic (after all, it’s PHP), but the overall design avoided complex logic pyramids. I was also inspired by Cobol and shell scripts, which tend to have minimal logic and top-to-bottom execution. What this means, in practice, is that you push logic and loops into the classes.
Using a subset of PHP for templates. We didn’t use Smarty; we used PHP with some restrictions. Templates were objects that would hold all the variables and objects to be exported to the template. When render() was called, the $GLOBALS array was cleared, the template’s objects were loaded into $GLOBALS, and the template was included. This was a sort-of security feature that theoretically prevented someone from exposing globals via a template or via code injection into a template. (e.g. “print_r($GLOBALS)” would end up printing only the exported variables). While that sounds drastic, it worked, mainly because render() was called as the final step. Templates could call other templates.
The Bad
SF’s models were not ORM models, per-se. They were hybrid controller-model wrappers around queries or views. When they were instantiated, they could be set to either select rows, or to update or create a new row. This is incredibly convenient for most cases, but kind of inflexible. You end up writing a lot of code to get around that limitation, and the code looks more like non-framework PHP. We needed to generate more traditional ORM code, and implement our style atop that (making for more code bloat:) .
Previews never worked because I suggested using session variables to simulate a row of data. This created a special state in the model when it held data, but it wasn’t going to be persisted to the database. That sounded good to me, as a programmer who didn’t want to write incomplete data to the database… but the actual real-world use case is that people want to save their work before it’s published. Preview is just a case of viewing an unpublished row. It’s better to just insert the data, even auto-saving for the user (using ajax), and flag it as not-published.
Also, PHP is the wrong environment for that kind of preview, which basically needs to hold the data in memory between GETs. PHP clears the environment with each execution, so you’re going to load the data from disk at some point, whether it’s from a $_SESSION or from the database.
Templates were cool, but we didn’t have a micro-template language like Mustache or Handlebars. That would have been nice. What’s really nice about those languages is that you can use the template on the client side or the server side, because it’s been translated to many languages.
We DIY’d the database abstraction and ORM. It was excusable in 2004, but by the time PHP 5 and PDO was widespread, we should have switched over.
We DIY’d the validation, but I’ve since found that PHP built-in validation features are okay. They aren’t great, but they are documented and human-readable, which matters in data validation logic. When you use the PHP functions, the code is a little easier to debug. Data validation includes testing for existence, the text itself, numeric ranges if applicable, supplying default values, and maybe setting flags. It’s a lot of logic, and during debugging, it’s better to not impose learning new functions on the programmer doing the debugging. (Top-to-bottom programming wins again.) (I flip-flop between the verbose Cobol-ish style, and defining a little language for data validation. This search looks for http request validation languages, and there aren’t any. Given that request parameters are the main way websites are hacked, you’d think it’s a more critical issue.)
The HTML HEAD area was managed by an object. It was kind of OK, but kind of a pain. The problem is that the HEAD contents should be set based on what’s used in the BODY of the page. There are dependencies to manage between the two areas. I think it was a case of premature optimization on my part, because I thought the HEAD was just JS, CSS, and META tags, mostly. I didn’t consider that JS loading order matters, and CSS might be organized differently than what we did. The complexity of HEAD (and also the part just above /BODY) has grown with HTML5, because, now, one page may actually contain and load multiple other “pages”. It might be so complex that it’s actually more complex to wrap the HEAD with an object oriented abstraction. Just use hand-tooled code, I guess.