Before you continue, you’ll want the following stuff on your computer:

  • PHP 5.4+. I’m using 5.6 locally, though 7.x should also work fine. Most, if not all, libraries and plugins I am using will work through any supported CakePHP version. I’ll let you know if that’s ever not the case.
  • The following PHP extensions:
    • ext-intl
    • ext-mbstring
    • ext-pdo_sqlite
    • ext-pdo_mysql
    • ext-pdo_pgsql
  • A database! I’m running Postgres locally, but anything supported by the CakePHP Core ORM should do fine. If you use MySQL, I won’t attack you.
  • A browser. I use Chrome, though any modern browser should work fine with the Javascript I’ll be writing.
  • Composer installed locally. How you get that is up to you.

We can start once you have the above.


Start out with a goal for the day before you start work. My goal is to scaffold out a small part of my app. I won’t have authentication or file uploading. I just want a solid database design and some crud-like templating.

Before I begin, I want to outline what my application will probably look like from a datastore perspective:

The above graph was generated with ditaa and some simple ascii art.

I’ve separated the notion of an Asset from a File. Assets are the things that my users will click on in the admin dashboard, and contain metadata that will be useful to search. Each asset can have one or more Files associated with it, has and belongs to many Tags, is associated with a single Category, and is owned by a User.

A File is the actual physical file, which will contain size, the directory on whatever storage device I choose, image metadata, etc. I’m choosing to do this as I think storing multiple versions of a file as different records will be much easier to reason about in the UI than trying to fiddle with it in code. No real reason other than that.

Note, I do have some legacy files I need to import into the new system, but I won’t worry about those for now, since it will presumably complicate my datastructure. I’ll almost certainly write a script to import the files into my database.

Initializing our new project

First, lets use composer to generate a project. I’m going to be basing it off of the 0.1.10 release of my [own app skeleton](/2015/12/26/creating-a-generic-cakephp-skeleton/:

composer create-project --prefer-dist josegonzalez/app:0.1.10 media-manager

From now on, assume you will be working inside of the media-manager root folder.

Before we begin, we need to at least modify our application to read from the correct database. One thing I’ve done in my new app skeleton is allow configuration via environment variables. Let’s start by copying over the defaults to a new file:

cp config/.env.default config/.env

The .env file is loaded if it exists, otherwise the config/env.php file will fallback to config/.env.default. We’ll be configuring our app’s private configuration here.

Two things I’m going to do first:

  • change the APP_NAME export to the value media-manager.
  • Make sure I have a local datastore setup with the credentials my_app:secret authed to the database media-manager. I mentioned I’m using Postgres, but anything should work.

Once that is set, I can verify that my application works by running the built-in CakePHP server shell. Run the following and browse to http://localhost:8765 to verify that you have everything done:

bin/cake server

I will be using the built-in CakePHP server shell for testing. You can use whatever you’d like, this is just easier for me since I haven’t setup Vagrant or Docker for my app skeleton yet.

Generating a database

Whenever I am building an application, I usually have some vague idea of what my database will look like once I am done. I say usually because sometimes I’m coming up with requirements as I go, which is to say that today is one of those days.

We can generate migration files for our database tables using the bake and migrations plugins. I’m allowed to get this wrong because the migrations plugin allows me to go back and generate new migrations to fixup any incorrect beliefs about how my application should work.

# create my assets table
bin/cake bake migration create_assets category_id:integer:index \
                                      user_id:integer:index name \
                                      file_count \
                                      created \
# create the files table. Note that I
# have included a few useful fields for the upload plugin
bin/cake bake migration create_files asset_id:integer:index \
                                     user_id:integer:index \
                                     name \
                                     dir \
                                     size:integer \
                                     type \
                                     metadata \
                                     created \
# categories is simple
bin/cake bake migration create_categories name:string:index \
                                          slug:string:index \
                                          created \
# tags is also simple, though I do need a join table
# there is probably a plugin out there for this but I'm too lazy
bin/cake bake migration create_assets_tags asset_id:integer:unique:ASSET_TAG \
bin/cake bake migration create_tags name:string:index \
                                    slug:string:unique \
                                    color \
                                    created \
# the users table will have an extra field you might not expect
# im going to eventually add github oauth to the app, so I want to track
# the user id if possible
bin/cake bake migration create_users name \
                                     email:string:index \
                                     password \
                                     github_id:integer:index \
                                     created \

We can now run our generated migrations:

bin/cake migrations migrate

This will create all of the above tables and two others:

  • jobs: A simple background jobs table that is provided by the josegonzalez/app application skeleton. We’ll get to this later.
  • phinxlog: Used to keep track of migrations that have been executed.

Baking my cake as fast as I can

The last step for today is generating some application defaults. We’ll be using the CrudView plugin - and therefore Bootstrap 3 - for the majority of our UI, and will be baking CrudView-compatible templates for things.

Lets start off by baking all of the tables and entities:

bin/cake bake model all

Next, lets bake some controllers and views for certain things (but not everything!):

bin/cake bake Controller Assets -t Crud
bin/cake bake Controller Categories -t Crud
bin/cake bake Controller Tags -t Crud

And now we’ll turn on CrudView for the entire application. Simply change the $isAdmin property of src/Controller/AppController.php to true. This can be done on a per-controller basis, but as we are using CrudView everywhere, it doesn’t hurt to turn it on universally.

And thats it for now. Why didn’t we bake templates or certain tables:

  • We need to add auth for users, and thats outside of what I wanted to accomplish today.
  • We don’t need a controller for join tables, nor for managing individual files (yet).
  • Templating is done via the CrudView plugin. If you browse to http://localhost:8765/categories, you should see a simple crud-interface for categories. Ditto for /assets and /tags.

What’s next?

Since tying uploads to specific users and authentication is a bit core to my app, that will be what I tackle in my next post. I’ll also go over that semi-mysterious github_id in a way that is sure to disappoint anyone who thinks I’m writing any OAuth code :)

Be sure to follow along via twitter on @savant. If you’d like to subscribe to this blog, you may follow the rss feed here. Also, all posts in the series will be conveniently linked on the sidebar of every post in the Media Manager series. Until next post, meow!