1. NewClarity
  2. Repos
  3. sunrise-1

Overview

HTTPS SSH

Sunrise v1

Admin Post Forms & Fields

UPDATE: We have released Sunrise' successor on Github: WPLib

Quality Disclaimer

This library which was used only on client projects and not previously publicly released (because in our opinion it is not distribution quality).

We consider this a 2nd generation library in terms of evolution of our coding techniques and in our projects we are currently working on a 5th generation library we hope to finally publicly release. It has a lot more complexity than it needs and as such we are not happy with many of the architecture choices we made in this library and consider them legacy.

Reason for Publishing Repository

In hindsight some of the architectural choices work extremely well so the purpose of this readme is to allow us to comment on them for the WordPress Features-as-Plugins Post Meta Team to review in hopes they might want to adopt what we think are some of our better architecture choices.

Architecture Choices Worth Highlighting

We've worked on Sunrise for a total of 3 years for client projects, and we have learned a ton about what works and what doesn't. I can't stress enough how important we believe the choices have been in allowing us to never reach a "brick wall" that we can't get passed.

WordPress-ish

As much as possible we wanted Sunrise to "feel" like WordPress and that dictated our design. We would have made significant different choices if we were building a library for Symfony, for example.

OOP-based

Our 1st generation of Sunrise created fields using arrays so that we could "keep it simple." However, as the client asked for more and more features we soon realized our choice for "simple" resulted in an explosion of complexity that we could no longer manage. So we rewrote as a set of interacting objects and that has worked extremely well.

Fully Extensible

Our OOP architecture does not create a "special" set of field type; instead there is an API for creating a field type and all the field types included use it. This allows us to satisfy any future need for the addition of a new field type simply by adding a class that implements the new field.

Extensibility at Multiple Skill Levels

When designing our API we aimed for three (3) different "interfaces" for 3 different skill sets:

  • PHP Developer - Can create new field types as plugins.
  • Coding SiteBuilder - Can register forms and fields in plugins and themes.
  • Designer/Themer - Can use registered forms and fields in theme template files.

Sitebuilder Interface

We followed WordPress' lead with register_post_type() and register_taxonomy() and added sr_register_form() (see code) and sr_register_field() (see code) where we passed in the names and then a (mostly) non-nested $args array

By not nesting the $args array it makes it much easier to merge with defaults as you traverse down into

Formalized "Object Types" and Form Types

WordPress has a loose concept of "object type" but does not formalize it. We formalized it in Sunrise allowing us to plan for meta to be cognizant of the type it is being used for, i.e. 'post', 'user', 'comment', etc.

Field "Features"

We added a concept of field "features", each with their own class, and here are the features we support:

  • Entry - The field itself
  • Label - A text label above or to the right of a field
  • Help - A line of help text below or to the right
  • InfoBox - A pop-up help text displayed when hovering over a (?) icon.
  • Message - Warning or error messages, when applicable

You'll note all of these are implemented with HTML in some form and what we discovered when implementing is that each feature had most of the same needs so implementing as features really cut down on the duplication and complexity of code required.

get_field() and the_field() methods, with $args

Explanation to come...

Virtual Fields

Explanation to come...

Handlers

Handlers are like hooks except that they only support calling one solution, and they choose what to do based on a set of criteria.

More explanation to come...

Storage Types

We added Storage Types which enabled us to store data in different places and yet treat the fields programmatically the same. We implemented the following storage:

  • Meta Storage - Field storage in Meta fields.
  • Core Storage - Field storage in core fields like post_title and post_content.
  • Table Storage - Field storage in custom tables.
  • Taxonomy Storage - Field storage of related taxonomy terms.

We have envisioned adding new Storage Types, such as:

  • Pods
  • MongoDB
  • Redis
  • Memcached
  • WP Cache

Post Meta Storage Format

Probably the best choice we made was in our post meta storage format. It's simple, easy to query, easy to understand, unambiguous (with one caveat), and can handle storing any arbitrarily complex value with ease.

It's easiest to explain by example. Assume the following:

$person = new stdClass();
$person->name = 'Mike Schinkel';
$person->gender = 'male';
$person->links = array(
  'twitter' => 'http://twitter.com/mikeschinkel', 
  'facebook' => 'http://facebook.com/mikeschinkel', 
  'linkedin' => 'http://linkedin.com/in/mikeschinkel',
);

If we had a "person" field this would save as like so:

meta_key meta_value
_person[name] Mike Schinkel
_person[gender] male
_person[links][twitter] http://twitter.com/mikeschinkel
_person[links][2][facebook] http://facebook.com/mikeschinkel
_person[links][3][linkedin] http://linkedin.com/in/mikeschinkel

The above should be reasonably obvious but there's one thing we should explain. Array values are 1-based and the index of the first value ($person->links[0]) is omitted in the meta-key. While this was a little complex to implement it has huge benefit when it comes to changing from a single value to supporting multiple values.

Had we included the [1] we would have to do a data migration to move to multiple values but by omitting the [1] on the first element we can switch to multiple-value fields with no data migration required.

Architecture Changes we Envision

We have been planning to redo Sunrise for a while, although doing so may now be moot if WordPress will add the functionality into core. Still, these are the mistakes and/or things we have been planning to do for the rewrite.

Clean up __get() methods and virtual properties

We created quite a tangled mess with our __get() methods and using virtual properties all over the place. That was a mistake and we have planned to greatly simplify it when we rebuild.

Repeating Fields/Fieldsets in Core

We implemented Repeating Fields/Fieldsets as a field type, and that quite frankly was a big mistake. We've been planning to reimplement as part of the core functionality of Sunrise Fields.

Use an "Object Type" Class

We found the need to pass around object type ('post' vs. 'user' for example) and then sub-object type ('post_type' for example) and that made our API much more complex than it needed to be.

We wanted to try to use an ArrayObject class that would allow us to pass around a single value rather than two (2) values and would allow us to initialize with strings like 'post:person' or 'user' but still be able to access the individual values as object properties or array elements, i.e.

$type = new WP_ObjectType( 'post:person' );
echo $type->type;           // 'post'
echo $type['type'];         // 'post'
echo $type->subtype;        // 'person'
echo $type['subtype'];      // 'person'
echo $type                  // 'post:person'

Separate Declaration from "Fixup"

Explanation to come...

Create a Sunrise App Class with Helpers

Explanation to come...

Use More Explicit Base Classes

Explanation to come...

Less Complex Directory and File Structure

Explanation to come...

Overuse of Shorthands

Explanation to come...

Avoid Nested Arrays of Literals for Declarative Initialization

Most code-based form+fields solutions we see use nested arrays when initializing forms and field (see also FieldManager used by WordPress VIP). Based on our experience we believe this is a mistake because it creates significant more complexity for the person who has to get all the information correct, it makes it more difficult to implement and maintain the functionality, and it makes debugging a lot harder because when an error occurs the line reported by PHP it almost never the line where it actually occurred and with huge arrays it's hard to find the error.

Further, we can generically and easily merge in defaults for flat arrays but can't generically do the same for nested arrays passed in via a register_*() function.

So rather than have one uber register_*() function we should have one for each 'object' we are creating in the system, i.e. forms, fields and any every other 'object' type we want to register.