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:
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 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:
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:
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();
}
}