Contact

FUxCon 2013


Models

CakePHP

In the Model-View-Controller pattern, Models define the types of objects in the problem domain. They also provide functionality to persist such objects in a relational database.

In CakePHP, models are implemented by PHP classes in the folder app/Model. Plugins can have there own models which then live in app/Plugin/plugin/Model. Model classes represent individual domain objects. They also allow to mainipulate collections of such objects. This can be through a set of powerful find methods or through own custom methods.

Model files generally don’t declare scalar database fields. CakePHP reads these from the database and caches them in a serialized structure in app/tmp/cache/models. However, models declare rules for field validation and one of the supported relationship types (1:1, 1:n, n:m). Models also declare special behavior like taggable or tree. a first approximation of models can be generated automatically by the command line tool cake, e.g.

app/Console/cake bake model Project

Our implementation uses two models in directory app/Model:

  • Project.php - projects listed on the start page
  • User.php - user accounts as owners of projects

Here is part of the project model from our CakePHP implementation:

<?php
class Project extends AppModel {
     // Taggable behavior comes from the Tags plugin, Containable is built-in 
     public $actsAs = array('Tags.Taggable', 'Containable');
     public $validate = array(
          'title' => array(
               'notempty' => array(
                    'rule' => array('notempty'),
                    'message' => 'Title must not be empty',
               ),
          ),
     );

     public $belongsTo = array(
          'User' => array(
               'className' => 'User',
               'foreignKey' => 'user_id',
          )
     );
}

In CakePHP, models can be enhanced by behaviors. There exist some standard behaviors like Containable, or Tree. Our implementation uses a contributed behavior Taggable to implement the tagging feature.

Django

Django follows a content-first approach. Once you have defined your models, you automatically get an API and a fully working admin interface that you can use to enter content into the database. Each app can have its own models. All model classes for an app are defined in the single file app/models.py

All fields are declared. You then create the database tables with the command line tool:

manage.py syncdb 

Here is part of the model file app/projects/models.py from our Django model:

class Project(models.Model):
  class Meta:
    verbose_name = 'Projekt'
    verbose_name_plural = 'Projekte'
    db_table = 'projects'
    get_latest_by = 'modified'
    ordering = ['title']

  user = models.ForeignKey(User)
  slug = models.SlugField()
  title = models.CharField(max_length=255)
  about = models.TextField(blank=True, null=True)
  photo = models.ImageField(upload_to='project', blank=True, null=True)
 
  # derived images implemented with extension django-imagekit:
  photo_thumbnail = ImageSpecField(source='photo', processors=[ResizeToFill(100, 100)],
    format='JPEG', options={'quality': 60}
  )
  photo_medium = ImageSpecField(source='photo', processors=[ResizeToFill(200, 200)],
    format='JPEG', options={'quality': 60}
  )
  photo_big = ImageSpecField(source='photo', processors=[ResizeToFill(380, 380)],
    format='JPEG', options={'quality': 60}
  )

  start_date = models.DateField(blank=True, null=True)
  end_date = models.DateField(blank=True, null=True)

  # tags implemented by extension django-taggit
  tags = TaggableManager(blank=True)

  created = models.DateTimeField(auto_now_add=True)
  modified = models.DateTimeField(auto_now=True)
 
  def __unicode__(self):
    return 'Project #' + str(self.id) + ': ' + self.title
   
  def admin_thumbnail(self):
    if self.photo:
      return '<img src="%s">' % self.photo_thumbnail.url
  admin_thumbnail.allow_tags = True

From this, Django generates the admin interface. If you want to further specify aspects of your admin interface, you can do so in a separate file. Here is part of our projects/admin.py:

class ProjectAdmin(admin.ModelAdmin):
  #date_hierarchy = 'start_date'
  list_display = ('title', 'start_date', 'end_date', 'admin_thumbnail')
  list_editable = ('start_date', 'end_date')
  prepopulated_fields = {'slug': ('title',)}
 
  fieldsets = (
    (None, {
      'fields': (
        'slug',
        'title',
        'photo',
        'about',
        'start_date',
        'end_date',
        'tags',
      ),
    }),
  )

admin.site.register(Project, ProjectAdmin)

Here are screenshots from the generated admin interface. This includes user accounts:

Django Admin

Django Admin Project

Drupal

In Drupal you define your content through its web interface. This definition can be exported into code. We haven’t explored this possibility, though. Here is a screen shot of the project definition in Drupal’s web interface:

Drupal Admin Fields

Symfony

Symfony manages models through one of its supported database backends. The default is to use the doctrine backend. As with Django, you declare your model fields in either XML, YML or as annotations and use a command line tool to generate the tables in your MySQL database, e.g.

php app/console doctrine:generate:entities FUxCon2013/ProjectsBundle/Entity/Project

Doctrine requires the definition of a getter and a setter for each field. I have opted to use magic methods in a super class to avoid this. Here is the project model from the Symfony implementation in src/FUxCon2013/ProjectsBundle/Entity/Project.php:

<?php
class Project extends GenericAccessors
{
    // The form generator chokes on "protected" attributes  
    public $id;
    public $title;
    public $startDate;
    public $endDate;
    public $about;
    public $created;
    public $modified;

    public $user;
}

The super class makes sure that with this arrangement the code to be written keeps small while there still are getters and setters for each field, e.g.

<?php
$p = new Project();
echo $p->getTitle();

Unfortunately, the form generator we want to use for the edit form does some checking on the access level of attributes directly and does not seem to see the getters inherited from the super class. We cannot declare attributes protected because of this.

In addition, doctrine needs some more information for each field which I provide in a YAMl file, ie.

FUxCon2013\ProjectsBundle\Entity\Project:
    type: entity
    table: projects
    id:
        id:
            type: integer
            generator: { strategy: AUTO }
    fields:
        title:
            type: string
            length: 255
        startDate:
            type: date
            column: start_date
        endDate:
            type: date
            column: end_date
        about:

            type: text
        created:
            type: datetime
        modified:
            type: datetime


     manyToOne:
        user:
            targetEntity: User

Symfony requires a repository class for each model to be able to access collections of model objects. In our implementation, this class contains methods needed for paginated display of projects:

<?php
class ProjectRepository extends EntityRepository
{
    public function count() {
        return $this->getEntityManager()
            ->createQuery('
                SELECT p.title, p.about
                FROM FUxCon2013SitesBundle:Project p'
            )
            ->getResult();
    }

    public function findPaginated($offset, $size) {
        return $this->getEntityManager()
            ->createQuery('
                SELECT p.title, p.about
                FROM FUxCon2013SitesBundle:Project p
                ORDER BY p.title ASC'
            )
            ->setFirstResult($offset)
            ->setMaxResults($size)
            ->getResult();
    }
}


comments powered by Disqus