« Back to zenphoto.org

This is a discussion of expanding the Zenphoto object model outside simple Albums and Images. It began at this forum thread and will continue here with design and specification of a new model. Here are the relevant parts of the discussion copied from the forums: ZenphotoDevModelDiscussion

Object Model

Classes

  • Object
    • Group
      • Gallery
      • Album
      • Set
        • SearchSet
        • TagSet
        • DateSet
        • ...
      • RemoteGroup
      • Weblog
    • Image
      • LocalImage
      • RemoteImage
      • Video
      • Sound
    • Article
      • WeblogPost
      • ImagePage
      • SitePage
    • Comment

Current Model Plan

First, a note: Italic items are proposed extensions after the base model is complete and in working order.

As shown above, we've got one top level of Object (formerly PersistentObject). The next level contains the high-level objects: Group, which is an interface for groups of other objects; Image, which is an image, obviously; and Article which is just an idea currently, but could be an easy way to make Zenphoto into an integrated Blog and Gallery all in one. There are lots of decisions to be made and thinking to do, but if we have extensible classes, it's a trivial jump.

Class Descriptions

  • Object (PersistentObject)
    • Object is a parent class of everything. It allows objects to be persistent optionally, and handles saves and updates if so desired. Every item might not be persistent, but most will. Objects should have additional methods to run queries eg to find what groups the item is a member of, to tell what kind of item this is (to be set by the inheriting classes), how to find this Item in the database if it's there (unique key/value set), etc.
  • Group
    • This is an interface to the extent that we can say that in PHP. It defines a group of items, possibly including other groups. Sub-albums fall out of this representation, as do all groups of groups. Tagsets could have sub-groups, for example, which are additional tags that the current set is also tagged with (Eg: {dogs} might have a subgroup of {dogs labradors} and {dogs pets mutts} and {dogs cats}, for all tagsets that intersect the current set.). Also, subclasses should declare what types of objects they are allowed to contain in some way, like a Group::allowedTypes array. Thus, Object::type should be set for all objects (unless there's already a PHP equivalent that's better).
  • Gallery
    • Gallery is now a subclass of Group which makes perfect sense. It simply happens to be the top level, but has the same capabilities as all other groups. Eg: containing and iterating over all top-level albums.
  • Album
    • Album is a group representing a folder on disk. It contains other folders, any Image or other non-group item, and possibly other kinds of groups as well. Special care must be taken to keep the database up-to-date, see below for more info.
  • Set
    • A set is a group of items not represented on the filesystem. I'm not sure if this extra layer is needed; ah, nope, it'd be good: it defines an group as based on the database, and provides common methods for getting the whole set, next/prev, etc. Then subclasses simply provide query criteria and any special requirements.
  • SearchSet
    • A group defined by a custom search or database query. This could be persistent to the database, or just a one-time search.
  • TagSet
    • A group of images having a given tag or set of tags. As noted above, this group could have subgroups of other tagsets, which would allow the user to "drill down" into more and more specific sets of tagged images.
  • DateSet
    • A group defined by a date or date range. The query would be a simple object search over date ranges.
  • RemoteGroup
    • A group within another installation of zenphoto, which would (ideally) allow sharing for the requested group, and communicate the image and group data via XML interface. This allows for secure, permissive access to friends' or your own galleries to include in your own in cool ways yet to be determined. This is also going to have some serious caching going on to maintain performance, perhaps with a refresh once every 4-6 hours or so.
  • Image
    • Parent class defining everything that can be displayed as an image. This includes all the items below, and is no longer specifically an image on the filesystem. This is another "pseudo-interface" class like PersistentObject or Group.
  • LocalImage
    • An image residing on the local filesystem. Works pretty much like it does now, hopefully with database sync improvements (see below).
  • InternetImage
    • An image residing at a public URL anywhere on the internet. It will be downloaded, cached, and processed locally, but not put in a local Album. It could be searched for by type, or added to any group when created, like an Album or Tagset.
  • RemoteImage
    • An image from a remote Zenphoto gallery that's shared with you. Just stick the URL into the "Add image" form and add it to any group you want! It's cached locally like an InternetImage?, but checked for updates every once in a while.
  • Video
    • A video file (probably limited to FLV included in the page as a flash player). Thumbnailed by ImageMagick?
  • Sound ?
    • Not sure if we want to go here, but just reiterating that we could have all kinds of objects. Sounds could be played like videos, in a flash player perhaps.
  • Comment
    • Comments can go on any object, and Object has the simple necessary methods for getting comments associated with itself.

Persistence and Synchronization

This is going to be a little complicated, but nothing we can't handle.


More Ideas

Still, I believe it's very valuable to be able to stick an image in a folder and not do anything else and have it already be part of the gallery. That, in my opinion, shouldn't go away with any new database model.

I don't think that has to go away -- the album object can still just point to a folder, and any images in there get automatically created as Image objects if they don't exist. And the Gallery object can create new Albums if it finds new folders.

I agree with you there, that would be a better way to do it. The database then becomes the definitive source of data. You know how that would work, it just took me some time to agree ;-) But I do.

What about this structure?

Classes

  • Item / PersistentObject ?
    • Group
      • Gallery
      • Album
      • Set
        • SearchSet
        • TagSet
        • DateSet
        • ...
      • RemoteGroup
    • Image
      • LocalImage
      • RemoteImage
      • Video
      • Sound ?

A gallery would have a lot of the same attributes and methods as Groups, such as containing items, showing pages, etc. I think that makes sense if you think of the object model as not the logical structure of how the items exist within each other, but instead by how it functions.

This structure is good, you're completely correct in not leaving Gallery out of it. It is essentially another grouping, the fact that it happens to be the top level doesn't mean much. The methods to get its children could be implemented exactly the same way as in Group, or even in Group as with all other groups, with some criteria defined in Gallery about what those children will be. It's just a group! Excellent.

As for the Item/PersistentObject object question, I think all items might *optionally* be persistent items. It makes sense for the functionality to be in the base class, but not all objects can or will be persistent.

Definitely agree here, that was the direction I was headed. Only persist the objects you need to store, eg: SearchResults can be one-time-only or stored in the session instead of the database.

To that end, I think you could create a common set of optional interface components and include their stubs/interfaces/etc. in where it makes sense -- persistent the base Item class, and pageable/sortable, etc. in Group.

Exactly, that's the idea.

As for URLs, each class can also specific whether it is accessible directly by URL (abstract classes like Group or Set wouldn't be, but Album and TagSet would be). There could either be a central mapping function to handle the request (and thus new classes would need to add their mapping info there), or classes could "register" themselves with a mapping function so the whole thing could be dynamic.

That's also easily done with some kind of URL mapping as you say. It would match, ideally, with the 'unique set' used to get the record from the database, and the URL would start with some (perhaps user-definable) string pointing to the object type. This should be easy.

Each class file would add the mapping when the class is defined (outside the class definition in the global scope, but in the same file for organization). Alternatively they could all be defined in one place. I think I just repeated what you said. Ah well, the point is, it'll be easy. We'd do a one-line .htaccess offloading all the parsing to zenphoto, which would use the mappings to decide what to load. Then the mappings would of course also be used to build new URLs for links to these things. Not hard.


Even More Ideas

Wouldn't this be trivial to extend to a Blog class? (essentially being a Group of Article objects) or a Site class, being a Group of Page objects? ;-) There's something to think about for later... how'd a fully-integrated blog, web site, and Gallery be with the elegance of Zenphoto? It would trump Wordpress, let's just say that... Perhaps just for future-proofing this implementation we assume a Group can contain any kind of object, but we set the type of object it may contain in its subclasses... we'll stick to Images for now though ;-) trisweb

Definately agree with the latter. I thought this was a photo gallery, not a weblog tool. Focus on one thing - managing a online photo gallery - and not suffer on featuritis. Martin Sturm

I will always stay faithful to the single driving purpose of Zenphoto to be a simple, elegant photo gallery. You never need to worry about that. If and only if I ever decide to extend the software into other purposes, it will only be because I believe that I can not only do both parts of the software better (blog and photo gallery in this case), but also do the integration better, and with more ultimate value to the user. I would never force that upon users-- it would be modular and optional, with Zenphoto as its essence still available, and Zenblog (or whatever) as a tightly integrated but separate package. So please, don't get mad at me for featuritis. If anything I thought I'd proven myself on that subject by now. The problem with "features" is that they aren't well thought out or integrated -- I believe that Zenphoto can be integrated with a new blog solution based on the same model while keeping its philosophy, and I'll make sure that's true if it ever happens. trisweb

Ok, glad to hear that... for a moment I was afraid that Zenphoto would make the same fault made by other similar project. Currently, the code is very clean and I hope this will remain in the feature as well. Martin Sturm

Nope, I'll always try to uphold the 'zen' philosophy. :-) That's the whole point. Thanks for supporting that, it's good to keep me in check ;-) trisweb


On the Admin side, having an extensible and much more open-ended system means the admin interface could get overly complex. I'd suggest taking the the current model for editing image title/description/etc. (doing it on the album view) and extending that across all objects, so that the regular page view would give an admin user the ability to edit the current object, as well as create/delete/edit contained objects. Ideally, I'd to see Groups have admin functionality that could be applied en mass to all (or a selection) of objects in the group (setting tags, moving/deleting images, etc.)


Searching

If Item (would BaseObject or Base be a better name?) had a column in the database to store "searchable text", it may greatly speed searches. It could contain an automatically generated amalgamation of whatever text fields the subclass cares to stick into it -- titles, descriptions, tags, etc. The tradeoff is a larger DB, redundancy, and more expensive inserts and updates, but a single SQL statement can quickly search the entire DB since all the searchable text is in one column.

I think another option is that each object type would have to provide some sort of SearchAllInstancesOfThisClass() method, so when searches are run it would iterate over all the class types and check for results (possibly involving multiple SQL statements, depending on the sort of data contained in the object) for each one.

The performance hit for the latter may not be all that bad.

As a variation on the SearchAllInstancesOfThisClass() method idea, the base class would know how to search its database fields, and subclasses would just search additional fields that they add to to their parent class. Not sure if PHP's object model would make this a hard or easy. AlexWilson

Decided on "Object" for the top level class. We can borrow from Java ;-). We could use extra columns in the db to speed searches, but I think I'd only do that if we had some combination of data that needed the optimization. The main problem with the cascading object queries are the multiple database calls, which aren't really necessary. It'd be better to form one query and let the database handle the joins and selections and conditions and sorting. It's made for that.

I do like your idea for building the query from the object hierarchy though, that's very good, and you're right, the object model might make it hard. Each object will have to have an array of its database fields as "search fields" (perhaps even with text labels for display to the user) and it will have to include all fields of the type and subtype. Perhaps all subtypes of a class (eg: Group) should be stored in one table, and each class would just add additional fields as long as no more complex relationships are required....

So in any case of a single query, an in-memory "query cache" could be made with the results, and objects could be populated out of the cache instead of with database calls (an easy extension to PersistentObject that I'll actually be doing for 1.1). They could then be used for display.

Let's start thinking of the database schema for this, I think it's starting to look good. trisweb


Schema

OK... I'll toss some ideas out...

Everything will have an entry in the Objects table -- the id here will be used across all the tables. Any attributes that may appear in subclasses can go here as well. These additional columns can be nullable -- not all subclasses will necessarily have or require values for all fields. AlexWilson

This idea could work, but I think it would be better to have separate tables for the top-level types; albums, groups, images, comments. That would simplify both the schema and the queries. Otherwise, the idea is right -- subtypes of those top-level types would all be in the same table. This method limits the extensibility somewhat (no real downside for us there, we don't actually need it to store "any" kind of object all at once) at great benefit of comprehension and speed. trisweb

There is a downside to tossing the Objects table, referential integrity: If everything that is common can use the Objects table, then there can be an FK relationship. For example, take tags: If there is an Objects tables, then tags (assuming images, albums, and perhaps other objects are all taggable) can drive its foreign key relationship for the ObjectTags table off those ids, and you can have cascading deletes and all that good stuff. With no Objects table, you either have to have separate tables (or at least columns) for the many-to-many mappings and such if you want FKs, or you have to live without FKs. It's not just tags though, any other tables you want to tie to those classes will have the same issue. I think the Objects table has to be there if you want to get the most out of this object model, and I don't think there's really going to be much overhead because of it, and for some things (like searches) I can see the central Object table being a performance enhancer. AlexWilson

Damn, you're right again. I'm seeing the benefits. One of the main reasons I cringe at it is because of this giant J2EE app I hack at work that uses the same kind of "types" within a single table and it's caused us all sorts of problems, but then again their entire program is horrible, so I'm sure that wasn't done right either ;-) Within zenphoto I can see the advantages, and unique subclass information can still be stored in separate tables. Good call. trisweb

Objects

  • id - PK unique identifier
  • typeid - FK to ObjectTypes table
  • title
  • description

ObjectTypes

  • typeid - PK unique identifier
  • name
  • ... additional info can go in this table, like if a class is disabled, type-specific settings, etc.

Galleries

  • id - PK and FK to Objects
  • ... gallery-specific info.

Albums

  • id - PK and FK to Objects
  • folder - path on disk
  • ... other album-specific info

If you want Albums to belong to just one gallery, the Albums table can have a galleryid (FK to Galleries). If albums can belong to multiple galleries (which I think would be cool), then there would be a many-to-many mapping table for albums and galleries.

Multiple galleries, for sure. Gallery is just a group, remember, so the links would be made in the many-to-many Group<->Object table. trisweb

Tags

  • tagid - PK
  • tag - text of tag

ObjectTags

  • id - PK and FK to Objects
  • tagid - PK and FK to ObjectTags

Images

  • id - PK and FK to Objects
  • filename
  • directory album's id? or can this also be many-to-many?
    • A local image would have a single directory, other types of images may not even have a directory.
  • rating
  • etc...

A thought about many-to-many and parent-child mappings: The Objects table could have a parentid column (which would be a FK to Objects.id), maybe? There could also be a table that handled *all* the many to many relationships: It would have columns for mappingtypeid, id, and id2 (last two FK to Objects.id). For example, imagine this dataset, two pictures that both live in two albums:

ObjectTypes ( typeid, name ) values ( 1, 'Album' )
ObjectTypes ( typeid, name ) values ( 2, 'Image' )

Objects ( id, typeid, description ) values ( 1, 1, 'Album 1, additional info in Album table' )
Objects ( id, typeid, description ) values ( 2, 1, 'Album 2, additional info in Album table' )
Objects ( id, typeid, description ) values ( 3, 2, 'Picture 1, additional info in Image table' )
Objects ( id, typeid, description ) values ( 4, 2, 'Picture 2, additional info in Image table' )

ObjectMappings ( mappingtypeid, id, id2 ) values ( 1, 1, 3 )
ObjectMappings ( mappingtypeid, id, id2 ) values ( 1, 1, 4 )
ObjectMappings ( mappingtypeid, id, id2 ) values ( 1, 2, 3 )
ObjectMappings ( mappingtypeid, id, id2 ) values ( 1, 2, 4 )

The mappingtypeid of 1 would signify the album-to-image relationship (and be an FK to a table storing those, or at least map to a constant in the code).

To add tags to one of the images:

ObjectTypes ( typeid, name ) values ( 3, 'Tag' )
Objects ( id, typeid, description ) values ( 5, 3, 'Tag1' )
ObjectMappings ( mappingtypeid, id, id2 ) values ( 2, 3, 5 )

Ta-da! We don't need any extra tables.

Pros: The PersistentObject code does the work for everything, it can handle parent-child or many-to-many. I think it could extend the elegance and simplicity of the existing PersistentObject code to many-to-many (something it currently can't do).

Cons: Overengineering? Yet another layer of complexity (though it would hopefully be quite transparent). Cascading deletes would have to be one-way (so the association of id and id2 would matter).

Not sure about this one. It looks ugly at first, but I think it makes the PersistentObject code super-easy for people to extend. AlexWilson


I like the above, it seems like a good model. Plus it actually reduces the schema significantly, since all objects are going into the Object table, and all associations into the ObjectMappings... you're right, it is confusing at first. It seems like we're bypassing SQL completely and leaving the types up to the classes. But it is the best way to get complete relational mapping on any kinds of objects, including member and is-a relationships all in one... it could work... trisweb

Please continue working this out as much as you like, I may be a little dormant for a bit while I work on the admin and EXIF for 1.1. This stuff is all for 2.0, and it's great to be having this discussion finally. Thanks for your help Alex! trisweb

Ah, also I've started signing blocks of text on this page with a subscript of your name, looks like this in the syntax: ,,trisweb,, trisweb


Will do... I'm gonna let these ideas churn for a bit to see if anything else comes to me -- I may also be dormant for a bit, as I'm still hacking up 1.0.8 to get my own tag-only browsing working. Assuming I can get it working, I expect it'll only end up useful for myself (or anyone who wants only the exact same functionality), but it's a good way to wrap my head around the existing code and see what's there. AlexWilson

Cool, thanks very much, you've been an incredible help so far. Good luck with your tags :-) trisweb