Who this is for. The content modeller deciding where a kind of content should live. Afterwards you can say what each built-in type gives you without building anything, and why choosing a book over a page tree costs nothing in capability.
Four types in one table
Pages, books, units and book pages are stored in one table, pages. The entity Pages\Entities\BasePage uses single-table inheritance: a column named entity records the class of each row, which is one of Pages\Entities\Page, Books\Entities\Book, Books\Entities\Unit and Books\Entities\Page. Everything declared on the base class behaves identically on all four:
- Slug and position. A slug is unique among the records under the same parent on the same site, and the position orders the siblings. (Slugs)
- Publishing. The published date is the gate a visitor has to pass, and an unpublish date takes the record down again. (The two gates, Scheduled publishing)
- Revisions, layouts and areas. The content is on the active revision, which names the layout and holds the layout areas. (Revisions)
- Custom URLs, tags, categories, language and SEO settings. The same fields, edited in the same panels.
- Owner and permissions. Every record has an owner and can carry permissions of its own.
- External id, the free identifier an import uses to find the record again.
So the choice between a page tree and a book is a choice of shape, not of features: a tree for content that readers arrive at one page at a time, a book for content that is read in order.
What still differs
| Pages | Books, units and book pages | |
|---|---|---|
| Hierarchy | A tree of any depth; any page can be a parent | Exactly three levels. A book cannot have a parent |
| Template | The template of the layout on the active revision | A book page uses its layout's template. Books and units always render books/books/show.html and books/units/show.html, whatever layout they have, except in a signed-in editor's ?revision= preview |
| Walking the structure | getParent(), getChildren(), getAllParents() | getUnits() on a book, getBook() and getPages() on a unit, getUnit() and getPositionInBook() on a book page |
| Translations | A source page and related pages | Book pages only; books and units have a language but no source or related records |
| Tags and categories in the admin | On by default | Off by default, a switch for each level |
| Sitemap | Included by default | Off by default, a switch for each level |
The pages that follow cover each type: Pages and the page tree and Books, units and book pages.
The five content types side by side
Blog posts, FAQ questions and custom entity records live in tables of their own, but they follow the same pattern: a record, a chain of revisions and an active revision that holds the layout areas. What differs is the shape around it.
| Pages | Books | Blog posts | FAQ questions | Custom entity records | |
|---|---|---|---|---|---|
| Shape | A tree | Book, unit, page, in reading order | Dated posts in a blog | Questions, listed on FAQ pages that form a tree | A flat list, with an optional parent and child definitions |
| Layout | Per revision | Per book page; fixed templates for books and units | Per revision, else the blog's post layout | Per revision | One for the whole definition |
| Listing route | None | None | Paged, and by tag, category and author, plus RSS | /faq/questions, and by tag and category | When the definition has a path |
| Sitemap | Yes, by default | Off by default | Yes, by default | Yes, by default | No |
| From a template | _query.findBy | _query.findBy | _query.findBy | _query.findBy | _query.findCustomEntityRecordsBy |
| Approval workflows | Yes | No | Yes | Yes | No |
| Unpublish date | Yes | Yes | No | No | No |
A blog itself, the container of the posts, is the one content record that is not versioned: its layout areas sit on the blog. Blogs and posts and FAQ pages and questions cover the other two built-in types, and Which type, or none works three real decisions through.