This is the largest page of our app. It allows us to edit a book title, link/unlink authors to the book, the book's instance list is alos here.

The edit-book form contains just a text input name=title, and a hidden input name=id. This form submission is handled by book_save.php:

$id = (int) $_POST["id"];
$book = Registry::persistenceDriver()->find($id, new Book());
if (!$book) {
    die("Book ID #" . (int) $_POST["id"] . " not found");
}

$book->title = trim($_POST["title"]);

if (!$book->title) {
    die("No book title provided");
}

Registry::persistenceDriver()->save($book);

Now we're going to link books to authors. Because an author can have many books, and a book can have many authors, the relationship is Many-to-Many. We'll need an intermediate table:

CREATE TABLE `book_has_author` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `book_id` int(11) NOT NULL,
  `author_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `book_id` (`book_id`,`author_id`),
  KEY `fk_author` (`author_id`),
  CONSTRAINT `fk_author` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `fk_book` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

To ease the CRUD operations on book_has_author table, we'll add BookHasAuthor class: a bare scaffold, generated by:

$ php ../../bin/generate.php --dbname=tinyorm_library --password=masha --table=book_has_author --class='library\scaffold\BookHasAuthor' --file=lib/scaffold/BookHasAuthor.php

Next, we create the "add-author-to-book" form with a dropdown to choose from existing authors. Options for the dropdown:

$allAuthors = (new Select("author", "id, name"))
    ->orderBy("name")
    ->execute()
    ->fetchAll(\PDO::FETCH_KEY_PAIR);

$allAuthors variable now contains an array that has author IDs as keys and author names as values. I explicitely specify column list for this Select for better code readability, despite the fact that author table has just these two columns.

Add author form action is book_author_add.php:

if (empty($_POST["book_id"])) {
    die("No book ID provided");
}

if (empty($_POST["author_id"])) {
    die("No author ID provided");
}

/** @var Book $book */
$book = Registry::persistenceDriver()->find((int) $_POST["book_id"], new Book());
if (!$book) {
    die("Book ID #" . (int) $_POST["book_id"] . " not found");
}

if ($book->hasAuthor($_POST["author_id"])) {
    die("This book already has this author");
}

$book->addAuthor($_POST["author_id"]);

Have you noticed the Book->hasAuthor() and Book->addAuthor() method calls? Tinyorm knows nothing about relationships between entities, and this is up to developer to decide how to work with these relationships. For this example, I decided to add methods that deal with Book-to-Author relationship - to the Book class:

class Book extends \library\scaffold\Book {
    ...
    /**
     * @param int $authorId
     */
    function addAuthor($authorId)
    {
        $link = new BookHasAuthor();
        $link->book_id = $this->id;
        $link->author_id = (int) $authorId;
        Registry::persistenceDriver()->save($link);
    }

    /**
     * @param int $authorId
     * @return bool
     */
    function hasAuthor($authorId)
    {
        return (bool) (new Select("book_has_author", "1"))
            ->where("book_id = ?", $this->id)
            ->where("author_id = ?", (int) $authorId)
            ->execute()
            ->fetchColumn();
    }
}

Now that we have the ability to add authors to books, we'll need to display authors list for a book. Seems logical to add another method to our Book class:

class Book extends \library\scaffold\Book {
    ...
    /**
     * @return Select
     */
    function getAuthors()
    {
        return (new Select("author", "author.*"))
            ->join("JOIN book_has_author AS bha ON (bha.author_id = author.id)")
            ->where("bha.book_id = ?", $this->id);
    }
    ...
}

This method returns Select instance, so that the book's author list can be further filtered, ordered etc. We will only need to fetch the full authors list:

$bookAuthors = $book->getAuthors()->execute()->fetchAll();

After all, we'll need to delete book-to-author relation. book_author_delete.php accepts to GET parameters: book_id and author_id:

if (empty($_GET["book_id"])) {
    die("No book ID provided");
}

if (empty($_GET["author_id"])) {
    die("No author ID provided");
}

$bookHasAuthor = (new Select("book_has_author"))
    ->where("book_id = ?", (int) $_GET["book_id"])
    ->where("author_id = ?", (int) $_GET["author_id"])
    ->setFetchClass(BookHasAuthor::class)
    ->execute()
    ->fetch();

if (!$bookHasAuthor) {
    die("This author is not registered for this book");
}

Registry::persistenceDriver()->delete($bookHasAuthor);

To find the needed row in book_has_author table, we create Select instance, specify WHERE conditions and set the fetch mode so that the results are fetched as BookHasAuthor class instances.

Every book is published and has one or more editions and instances. Our library can have a book in different editions. For example, for "War and peace" by Leo Tolstoy we have 2 instances published in 2005 and 1 instance published in 2010. For editions and instances we create a table:

CREATE TABLE `edition` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `book_id` int(11) NOT NULL,
  `year` smallint(6) DEFAULT NULL,
  `isbn` varchar(255) DEFAULT NULL,
  `instance_count` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `fk_edition_book` (`book_id`),
  CONSTRAINT `fk_edition_book` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Next, below the "Add author" section we have another section - "This book in library". Here, we' will list all the editions of the book that we have. Above this list, I placed a hyperlink to edition_edit.php and called it "Add new edition".

In order to get all the editions of the book, we add another method to the Book entity:

class Book extends \library\scaffold\Book {
    ...
    /**
     * @return Select
     */
    function getEditions()
    {
        return (new Select("edition"))
            ->where("edition.book_id = ?", $this->id);
    }
}

And now we're able to pass the edition list to the view:

$bookEditions = $book->getEditions()->execute()->fetchAll();