Creating a microblogging platform
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!
To follow this series of tutorials, you’ll need at least the following setup:
- PHP 5.5.9+: I am running PHP 7.0.12 locally
- Composer: I’ve installed this via
homebrewon my mac.
- A supported CakePHP SQL Database: I am testing against both Postgres and MySQL. YMMV.
- git: I’ve installed this via
homebrewon my mac.
If you have none of these, you’ll want to use the friendsofcake/vagrant-chef setup. It has support for multiple PHP applications, as well as various datastores/utilities, and is probably the easiest setup to get going. Follow the instructions in the readme if you have any further questions.
Baking the App
Lets start by baking the app! Most CakePHP projects start out by using the official cakephp/app going to use my own josegonzalez/app composer project skeleton. It has a few goodies I’ll be using in the application.
composer create-project --no-interaction --prefer-dist josegonzalez/app calico
A few things!
- I’ve named my blog platform
calico. You can name yours lollipop if you’d like. Calico suits me pretty well.
- I’m using the
--no-interactionflag to create the new project. If you omit this, the
composercommand may ask you questions. For now, I am assuming that the defaults are fine.
- I’ve customized the project skeleton by specifying
josegonzalez/app. Changing this will change your skeleton base.
Lets save our new application in a git repository.
cd calico git init git add . git commit -m "Initial commit"
All further shell commands are assumed to be running within the application folder. In this case, that will be
Configuring our app
In my application skeleton, all configuration is done via environment variables. This allows us to change any config value at runtime without necessitating a full application deploy. It will also allow us to specify different environments both locally, as well as once the application has been deployed to a server. All config is stored in the
config/.env.default file, though it is highly recommended that you customize it in a
config/.env file. Lets do that:
cp config/.env.default config/.env
config/.envfile is git-ignored, meaning it will not be tracked by git and cannot be committed. To see what else is git-ignored, check out the
.gitignorefile in your repository base.
I’m going to change one thing in my
config/.env, and that is the
APP_NAME variable. I’ve changed mine to
calico, because that is what my application is called. The other environment variables can be changed at will, and - through a few small modifications to the base app project - are automatically picked up by CakePHP.
Creating our initial database tables
Before we continue, you’ll want to create a database for your application. By default, the app skeleton will name the database after your
APP_NAME variable. For my application, the following databases were created:
You can configure the
DATABASE_URL environment variable in the
config/.env file to change this default.
The default username and password are also set here to the normal CakePHP defaults. I created both of these on my server, though you can modify these - and the other sections of that environment variable - to suit your needs.
Now that you have a database setup, we’ll want a few tables. After speaking with my friend, I’ve decided upon the following initial schema.
- Users hasMany Posts
- Posts belongsTo Users
- Posts hasMany PostAttributes
- PostAttributes belongsTo Posts
Why am I going for an extra table to store post attributes?
- CakePHP does not support native JSON values in the ORM. It is totally possible to use JSON, but I don’t get any native database enhancements (yet)
- The CMS is a bit of a tumblog, and I don’t completely know what post types I’ll be creating. I can optimize this later if need be, but I doubt it will be a problem.
- It lets me play with the ORM a bit more.
We’ll be generating migration files using the
cake bake cli tool. Migrations can be used to version our database locally in PHP files, allowing us to apply them as necessary:
bin/cake bake migration create_posts user_id:integer:index type:string:index url:string:index status:index created modified sleep 1 bin/cake bake migration create_post_attributes post_id:integer:index name:string value:text sleep 1 bin/cake bake migration create_users email:string:index password:string avatar:string avatar_dir:string created modified
Why Sleep? We sleep because each migration uses the current timestamp as the unique identifier. This is a limitation of phinx, the library in use for migrations. By adding a sleep in between each command, we simulate running each command at a different timestamp, and therefore give them unique identifiers.
Seems legit. Once you run those commands, you’ll have three migration files in
config/Migrations. We can execute these against our database using the following
cake cli command:
bin/cake migrations migrate
If you open up your database, you’ll see our three tables were created by the above command, as well as a fourth
phinx table. This table is used to figure out which migrations were already run.
Lets commit these changes:
git add config/Migrations git commit -m "Added migrations for posts, post_attributes, and users"
Baking the app
This bit is pretty trivial. CakePHP has long offered a
bake tool that can be used to autogenerate files for your application. CakePHP includes it’s own bake templates that have quite a bit of functionality “baked” in, but we’ll be using the
Crud? Think of Crud as a programmatic bake. It allows us to provide the same defaults as
bake would, but also provides a programmable interface to editing those defaults. In the past, rebaking a file would have ended up destroying most of your customizations. Crud allows you to reuse the default actions in your controllers and views with all the power of an actual programming language. It’s a bit hard to explain, so just assume I know what I’m talking about and we’ll hopefully get through this.
To bake using crud templates, we’ll run the following commands:
bin/cake bake all posts --theme Crud bin/cake bake all post_attributes --theme Crud bin/cake bake all users --theme Crud
If you run
git status at this point, the following files and directories will show up:
src/Controller/PostAttributesController.php src/Controller/PostsController.php src/Controller/UsersController.php src/Model/Entity/Post.php src/Model/Entity/PostAttribute.php src/Model/Entity/User.php src/Model/Table/PostAttributesTable.php src/Model/Table/PostsTable.php src/Model/Table/UsersTable.php src/Template/PostAttributes/ src/Template/Posts/ src/Template/Users/ tests/Fixture/PostAttributesFixture.php tests/Fixture/PostsFixture.php tests/Fixture/UsersFixture.php tests/TestCase/Controller/PostAttributesControllerTest.php tests/TestCase/Controller/PostsControllerTest.php tests/TestCase/Controller/UsersControllerTest.php tests/TestCase/Model/Table/
Lots of gunk here. Lets explain them all before continuing:
src/Controller: These files are where you’ll be adding most entrypoints to the application. If you open any of these up, they’ll be largely empty, as they will be inheriting from the
src/Controller/AppController.php. Don’t worry too much about these, we’ll dig into them further in a bit.
src/Model/Entity: Handles some minimal logic concerning individual records in your database. I usually put “helper” code in here - date-time formatting, pretty-printed titles - in these classes.
src/Model/Table: Business logic can go here. I generally place complex find queries (select statements) here. You can also place validation rules here, as well as configure the Table to have the correct behaviors (decorator classes that change/enhance functionality).
src/Template: In CakePHP 2, we placed
.ctp(cake templates) files in the View directory. In CakePHP 3, the View folder is reserved for Helpers and View classes, while cake template files go in
src/Template. It’s a bit cleaner, and also lets you use
Helperas table names.
tests/Fixture: The classes here contain sample data that can be inserted into our test database during testing.
tests/TestCase: Test classes! They are mostly stubs, because we haven’t written any real code yet.
I’m going to add all these files and call it a night. My friend said they have a few weeks before they need their site, so taking it slow works for me :)
git add src/Controller src/Model/Entity src/Model/Table src/Template tests/Fixture tests/TestCase git commit -m "Add baked files for initial application"
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.1.
What is next? While you can certainly go in and create any records you’d like at this time, we’re going to want to lock down our database. Thus, our next target will be general user management, including a forgot password flow and modifying their personal details. We’ll be covering the above topic in the next installment of CakeAdvent 2016.
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. Come back tomorrow for more delicious content.