Skip to content

My Simple ORM

Presentation

msorm is a PHP class generator.

From an XML file that describes a relational model, msorm generates a set of PHP classes that represent this model and its relationships. This set of PHP objects can be copied into a project and is usable as is, without any other tools being necessary.

In this way, it differs from other ORMs that need to be integrated into the project to function.

The advantage is that the generated model is very lightweight; the trade-off is that if the model evolves, it needs to be regenerated, but there are options that make things very simple.

The generated model can be extended with additional methods that can be written.

Using the model is very easy and makes the application code very readable.

msorm is organized as follows:

msorm/
  ├── msorm.php
  ├── generators/
     ...
     ...
  └── projects/
        ├── myproject_1/
            └── description.xml
        └── myproject_2/
             └── description.xml

For a given project, you need to create a description.xml file (see here) and run the following command (the msorm.php file must have execution rights):

$ ./msorm.php -g model -p myproject

This command is the most basic. It will take the description.xml file located in the myproject/ subfolder of the projects/ folder and create a model/ folder at the same level, which contains all the generated PHP files.

There are several options, including:

  • -g (or --generate): must be followed by model or sql
    Indeed, msorm can generate an SQL file (in the same location as the description.xml file) that allows creating the database.
  • -o (or --output): allows specifying a different destination folder than the model/ subfolder.
  • -u (or --update): allows not overwriting any additional code that may have been written.
  • -h (or -? or --help): displays help summarizing all options.

An Example

The description.xml File

msorm comes with a sample project that serves as a case study for the tool's capabilities. The different types of relationships are addressed there.

<?xml version="1.0" encoding="UTF-8"?>
<database name="bookstore" phpNamespace="bookstore">

    <table name="books" phpObjectName="Book">
        <column name="id" type="integer" unsigned="true" autoIncrement="true" primaryKey="true" />
        <column name="title" type="varchar" size="256" required="true" />
    </table>

    <table name="publishers" phpObjectName="Publisher">
        <column name="id" type="integer" primaryKey="true" unsigned="true" autoIncrement="true" />
        <column name="name" type="varchar" size="128" required="true" />
    </table>

    <table name="publications" phpObjectName="Publication"> <!-- Cannot be a crossref table because the same publisher can publish the same book multiple times with different ISBNs -->
        <column name="isbn" type="varchar" size="20" primaryKey="true" />
        <column name="book_id" type="integer" unsigned="true" required="true">  <!-- Many to One: A publication concerns only one book. A book can have multiple publications (multiple publications can concern the same book) -->
            <reference table="books" column="id" deleteCascade="true" />
        </column>
        <column name="publisher_id" type="integer" unsigned="true" required="true"> <!-- Many to One: A publication concerns only one publisher. A publisher can have multiple publications -->
            <reference table="publishers" column="id" deleteCascade="true" />
        </column>
        <column name="publication_date" type="date" />
        <column name="publication_notes" type="varchar" size="200" />
    </table>

    <table name="contributors" phpObjectName="Contributor">
        <column name="id" type="integer" unsigned="true" autoIncrement="true" primaryKey="true" />
        <column name="gender" type="enum" values="man,woman" />
        <column name="first_name" type="varchar" size="128" required="true" />
        <column name="last_name" type="varchar" size="128" required="true" />
    </table>

    <table name="contribution_types" phpObjectName="ContributionType">
        <column name="id" type="integer" primaryKey="true" unsigned="true" autoIncrement="true" />
        <column name="name" type="varchar" size="128" required="true" />
        <column name="description" type="varchar" size="256" />
    </table>

    <table name="contributions" phpObjectName="Contribution"> <!-- Cannot be a crossref table because the same contributor can contribute multiple times to the same book with different types of contributions (see below) -->
        <column name="book_id" type="integer" unsigned="true" primaryKey="true"> <!-- Many to One: There can be multiple contributions to the same book. -->
            <reference table="books" column="id" />
        </column>
        <column name="contributor_id" type="integer" unsigned="true" primaryKey="true"> <!-- Many to One: A contributor can contribute to multiple books. -->
            <reference table="contributors" column="id" />
        </column>
        <column name="contribution_type_id" type="integer" unsigned="true" primaryKey="true"> <!-- Is primary key to allow recording multiple contributions from the same contributor to the same work. -->
            <reference table="contribution_types" column="id" />
        </column>
    </table>

    <table name="covers" phpObjectName="Cover">
        <column name="isbn" type="varchar" size="20" primaryKey="true"> <!-- One to One: A publication has only one cover. Here filename could have been added to publication, but this is for the example. -->
            <reference table="publications" column="isbn" deleteCascade="true" />
        </column>
        <column name="filename" type="varchar" size="100" required="true" />
    </table>

    <table name="distributors" phpObjectName="Distributor">
        <column name="id" type="integer" unsigned="true" autoIncrement="true" primaryKey="true" />
        <column name="name" type="varchar" size="128" required="true" unique="true"/>
        <column name="country" type="varchar" size="256" />
        <column name="address" type="varchar" size="256" />
        <column name="zipcode" type="varchar" size="256" />
        <column name="city" type="varchar" size="256" />
    </table>

    <table name="distributions" isCrossRef="true" phpExtraAttributesPrefix="distribution">  <!-- Many to Many -->
        <column name="isbn"  type="varchar" size="20"  primaryKey="true">
            <reference table="publications" column="isbn" deleteCascade="true" />
        </column>
        <column name="distributor_id"  type="integer" unsigned="true" primaryKey="true">
            <reference table="distributors" column="id" deleteCascade="true" />
        </column>
        <column name="price" type="float" />
    </table>

</database>

What is Generated

model/
└── bookstore/                   --> takes the indicated phpNamespace
    ├── Book.php                       |
    ├── BooksManager.php               |
    ├── Contribution.php               |
    ├── ContributionType.php           |
    ├── ContributionTypesManager.php   |
    ├── ContributionsManager.php       |
    ├── Contributor.php                |
    ├── ContributorsManager.php        |
    ├── Cover.php                      |--> here are the classes we will use in our code
    ├── CoversManager.php              |    These are empty classes that inherit from their
    ├── Distributor.php                |    "core" equivalent
    ├── DistributorsManager.php        |
    ├── Publication.php                |
    ├── PublicationsManager.php        |
    ├── Publisher.php                  |
    ├── PublishersManager.php          |
    ├── RawDatasManager.php            |
    ├── core/
    │   ├── BookCore.php                        | 
    │   ├── BooksManagerCore.php                |
    │   ├── ContributionCore.php                |--> here are the "core" classes that contain the code
    │   ├── ContributionTypeCore.php            |    useful for manipulating our objects
    │   ├── ContributionTypesManagerCore.php    |
    │   ├── ContributionsManagerCore.php        |
    │   ├── ContributorCore.php                 |
    │   ├── ContributorsManagerCore.php         |
    │   ├── CoverCore.php                       |
    │   ├── CoversManagerCore.php               |
    │   ├── DatabaseConnectionProvider.php      |
    │   ├── DistributorCore.php                 |
    │   ├── DistributorsManagerCore.php         |
    │   ├── PublicationCore.php                 |
    │   ├── PublicationsManagerCore.php         |
    │   ├── PublisherCore.php                   |
    │   ├── PublishersManagerCore.php           |
    │   ├── RawDatasManagerCore.php             |
    │   └── __db_params.conf.php                --> a file to configure the database connection
    └── exceptions/
        ├── BookException.php                           |
        ├── BooksManagerException.php                   |
        ├── ContributionException.php                   |
        ├── ContributionTypeException.php               |
        ├── ContributionTypesManagerException.php       |
        ├── ContributionsManagerException.php           |
        ├── ContributorException.php                    |
        ├── ContributorsManagerException.php            |
        ├── CoverException.php                          |--> the exception classes
        ├── CoversManagerException.php                  |
        ├── DatabaseConnectionProviderException.php     |
        ├── DistributorException.php                    |
        ├── DistributorsManagerException.php            |
        ├── PublicationException.php                    |
        ├── PublicationsManagerException.php            |
        ├── PublisherException.php                      |
        ├── PublishersManagerException.php              |
        └── RawDatasManagerException.php                |
  • Each table defined in the XML file generates two classes except for crossRef tables.
  • For each table that generates a class, the phpObjectName attribute is defined, which will be the name of the class.
    • the phpObjectPFName attribute can also be defined if the plural is not formed by simply adding an "s". 'PF' stands for 'plural form'. This form is used to generate the name of the manager and the methods that serve to retrieve attributes in "many" relationships.
  • There is an additional class RawDatasManager. This is a class that allows for direct manipulations without going through the object side.

How to Use This Model?

The principle remains the same, but the generated classes contain code and attributes that really depend on the definition XML file.

Example:

$books_manager = bookstore\BooksManager::getInstance();

$books = $books_manager->getFilteredList(array('title' => 'My Book'));

$myBook = $books[0]; // assuming there is one and only one

echo $myBook->getId()." : ".$myBook->getTitle().PHP_EOL;
foreach($myBook->getContributions() as $contribution){
    $contributor_id = $contribution->getContributorId();
    $contributor = $contribution->getContributor();
    $type = $contribution->getContributionType();

    echo "    ".$contributor->getLastName()." ".$contributor->getFirstName()." (".$contributor_id.") : ".$type->getName().PHP_EOL;
}

$myBook->setTitle('My new title');
$books_manager->update($myBook);      // writes to the database

In the example above, we see that for each "object" defined in XML, we have two PHP classes: one "Object" class and one "ObjectsManager" class.

  • The first represents objects corresponding to the rows of the tables, with attributes being the columns (with their getters and setters), but also the objects resulting from the defined relationships (reference).
  • The second is a class that allows managing these objects: obtaining one or more objects, checking existence, adding, deleting, or updating them.

As mentioned earlier, these two classes inherit from their "core" equivalents, which contain all the attributes and methods provided by default.

These two classes can therefore be enriched with other attributes or methods that you may need. In particular, the getFilteredList() method of the managers is quite limited in its filtering capabilities, and you may need to write a method to extract specific objects.

The relationships between objects are taken into account during additions, updates, or deletions, so that multiple objects can be created, updated, or deleted in the database in a single instruction.


In this example, $book and $publisher will be created on the fly in the database:

$publication_manager = bookstore\PublicationsManager::getInstance();

$book = new bookstore\Book();
$book->setTitle("My book");

$publisher = new \bookstore\Publisher();
$publisher->setName("Casterman");

$publication = new bookstore\Publication();
$publication->setIsbn("123456789V");

$publication->setBook($book);
$publication->setPublisher($publisher);

try {
    $publication_manager->add($publication);
} catch (\Exception $e) {
    echo $e->getMessage();
}

It would be the same for calling the update method.
Continuing from the previous example, the $cover object would be added on the fly:

$cover = new bookstore\Cover();
$cover->setFilename("123456789.jpg");

$publication = $publication_manager->getByIsbn("123456789");
$publication->setCover($cover);
try {
    $publication_manager->update($publication);
} catch (\Exception $e) {
    echo $e->getMessage();
}

In the XML file, the references to the books and publishers tables in the publications table are defined with the deleteCascade=true attribute. Thus, if a book (or a publisher) is deleted, all publications of that book (or publisher) would also be deleted.

$books_manager = bookstore\BooksManager::getInstance();

$books = $books_manager->getFilteredList(array('title' => 'My Book'));
$myBook = $books[0]; // assuming there is one and only one

$books_manager->delete($myBook);