A friend of mine asked for a custom website, so here I am, writing a custom cms. I know, there are plenty of systems out there that would handle his needs, but it’s also a good excuse to play around with CakePHP 3, so here we are.

For the lazy, the codebase we’ll be working on will be available on GitHub. I will be pushing each set of changes on the date when each blog post in this series is published. No cheating!

Errata from previous post

Looks like I should run the code before committing it. Here are a few issues with the last post:

  • The OrderNotificationBehavior was attached improperly. It should be as follows:

    $this->addBehavior('PhotoPostType.OrderNotification');
    
  • The namespace for OrderNotificationBehavior should be namespace PhotoPostType\Model\Behavior;.
  • The use statement for QueueTrait should be use Josegonzalez\CakeQueuesadilla\Traits\QueueTrait;.
  • Missing a comma on line 20 of OrderNotificationBehavior.
  • Extra semicolon around line 34 of OrderNotificationBehavior
  • Missing data from the shipped MailerJob enqueue in OrderNotificationBehavior. It should be:

    'data' => [
        'order_id' => $entity->id
    ],
    

Thanks to those who’ve pointed out my derps. These fixes are available as the first commit in the current release.

Creating a heroku application

First, you’ll want to install the heroku cli. This will be used to orchestrate our application on heroku.

In the app repository, I ran the following to create a new heroku app:

heroku create

There is a bit of configuration we need to set in order to get our app fully working in heroku. First, lets ensure our config/.env.default does not override our environment variables by setting an application name:

heroku config:set APP_NAME=calico

Next, we’ll disable debug, as otherwise deploying will have errors regarding DebugKit not being installed. On heroku, packages in our composer.json require-dev section are not installed, so skipping this will mean our config/bootstrap.php will attempt to load a non-existent plugin.

heroku config:set DEBUG=false

In order to send email, you’ll probably want to configure your EMAIL_TRANSPORT_DEFAULT_URL env var as well. I’ve set mine to smtp settings from a Gmail account, though if you want to use a custom email transport for an email service, you are welcome to do that as well. Don’t forget to set a primary email!

heroku config:set EMAIL_TRANSPORT_DEFAULT_URL="mail://user:secret@localhost:25/?client=null&timeout=30&tls=null"
heroku config:set PRIMARY_EMAIL="example@example.com"

We’ll also want to configure stripe properly. I’ll add the following to my config/app.php and config/app.default.php:

/**
 * Configures Stripe
 */
'Stripe' => [
    'publishablekey' => env('STRIPE_PUBLISHABLEKEY', 'pk_test_1234'),
    'secretkey' => env('STRIPE_SECRETKEY', 'sk_test_abcd'),
    'mode' => env('STRIPE_MODE', 'test')
],
/**
 * Sets primary config for our app (email, etc.)
 */
'Primary' => [
    'email' => env('PRIMARY_EMAIL', 'example@example.com'),
],

And we can set the env vars like normal:

heroku config:set STRIPE_PUBLISHABLEKEY=pk_test_1234
heroku config:set STRIPE_SECRETKEY=sk_test_abcd
heroku config:set STRIPE_MODE=test
heroku config:set PRIMARY_EMAIL="example@example.com"

Commit!

git add config/app.default.php
git commit -m "Ensure we read env vars for stripe and primary email configuration"

I’ll configure a database, queuing, and our cache layer using some heroku addons for postgres and redis:

heroku addons:create heroku-postgresql:hobby-dev
heroku addons:create heroku-redis:hobby-dev
APP_NAME="$(heroku config:get APP_NAME)"
DATABASE_URL="$(heroku config:get DATABASE_URL)"
REDIS_URL="$(heroku config:get REDIS_URL)"
heroku config:set QUEUESADILLA_DEFAULT_URL="${DATABASE_URL}"
heroku config:set CACHE_DEFAULT_URL="${REDIS_URL}?prefix=${APP_NAME}_"
heroku config:set CACHE_CAKECORE_URL="${REDIS_URL}?prefix=${APP_NAME}_cake_core_"
heroku config:set CACHE_CAKEMODEL_URL="${REDIS_URL}?prefix=${APP_NAME}_cake_model_"

One thing that needs to be done is we need to ensure we build assets in heroku, or our admin won’t be able to render assets. I ran the following command locally:

mkdir webroot/cache_css webroot/cache_js

Then I added those directories to my .gitignore:

/webroot/cache_css
/webroot/cache_js

And finally, I added the following to the application’s composer.json in scripts.compile:

"mkdir webroot/cache_css webroot/cache_js",
"bin/cake asset_compress build"

And I’ll commit these changes:

git add .gitignore composer.json
git commit -m "Build assets on deploy"

Finally, we’ll need to square away our logging setup.

heroku config:set LOG_DEBUG_URL="syslog://logs?levels[]=notice&levels[]=info&levels[]=debug&file=debug"
heroku config:set LOG_ERROR_URL="syslog://logs?levels[]=warning&levels[]=error&levels[]=critical&levels[]=alert&levels[]=emergency&file=error"

Now push your code:

git push heroku master

You’ll see a lot of build output, but once it is done, you can type heroku open to open your site in the browser.

Background workers

You can add background queue workers by adding the following to your Procfile if it does not already exist:

worker: bin/cake queuesadilla

Then commit and push the change:

git add Procfile
git commit -m "Allow running a background worker"
git push heroku master

To start a worker, you’ll need to scale it up:

heroku ps:scale worker=1

Logging in

You’ll need to create a user to login as. To do so, you can start a new heroku dyno:

heroku run bash

And then run our helper UserShell to create the first user:

bin/cake user --username-field email

Homework Time: Uploading images

This will require a bit of reworking. Firstly, data is not persisted, so we need to store it on an external filesystem. I prefer AWS S3 for storing static files. Fortunately, flysystem supports quite adapters for different storage engines, so you can use whatever you’d like.

There are two places in the codebase you’ll need to edit:

  • UsersTable: The Josegonzalez/Upload behavior can be configured to use any adapter. Documentation here on that.
  • PhotoPostType: The adapter configured for upload is the Local adapter. Use whichever one you feel most comfortable.

I won’t be making these changes in my version, but in a future release of my client’s CMS, these two should be configurable :)


For those that may just want to ensure their codebase matches what has been done so far, the codebase is available on GitHub and tagged as 0.0.24.

And that’s a rap! We’ve created a fully-functioning CMS with:

  • Image uploading
  • Custom theme support
  • CrudView-generated admin dashboard
  • User authentication
  • Password reset flows
  • Email sending and previews
  • Background queues
  • Simple ecommerce functionality

Lots of stuff here for really not much code, and it was all thanks to the power of CakePHP.

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 2016 CakeAdvent Calendar.

Hope you all had as much fun as I did with this year’s CakeAdvent Calendar. Until next post, take care and happy holidays!