CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

RSS Feeds, Fast and Easy

For my first entry, I am going to talk about how to create an RSS Feed on your website. RSS (Really Simple Syndication) is a format used to publish frequently updated works such as blogs or featured products. RSS defines a set of XML elements that are used to describe a channel or feed of information. An RSS feed is comprised of two parts, first is the metadata describing the channel and second is the records that make up the elements of the feed. RSS feeds allow your sites visitors to access the information on your site using software that reads these feeds. This will allow your site's visitors to stay up-to-date on the information on your site.

CakePHP allows for easy integration of RSS feeds into existing controller actions through the automatic router extension parsing. This allows us to specify what type of response we want from a URL through adding the proper extension to the URL such as http://www.yoursite.com/entries.rss. This alerts the router that your are asking for RSS formatted data in return. In addition, CakePHP has an RssHelper class that can be used to output parts of the metadata and elements in the feed through an easy to use helper.

Preparation

Before we begin making the feed we must alert the router that we want to allow for extensions to be parsed in the URL and that we want it to accept .rss as a valid extension. In your sites router file we add the following:

	Router::parseExtensions('rss');

Also for CakePHP to work it magic we must also have the RequestHandler in our controller's $components array. Now the router knows that we would like to parse urls that end in .rss as requesting RSS formatted responses. The next step of preparation is to add a default layout for rss feeds on your site. When you request a different format response the layout that is rendered will be selected from a sub-folder with the same name as the format. So in this case we would need a folder called /rss in the layouts folder in our CakePHP install. The view class will search for a file that has the same name as the layout that would be rendered if you were just rendering the html. In most cases this is the default.ctp layout file in the main layouts directory, but because we are requesting the response in RSS format we must add a default.ctp layout in the /layouts/rss/ sub-directory. This layout is our default RSS Feed layout.

	echo $rss->header();

	if (!isset($channel)) {
		$channel = array();
	}
	if (!isset($channel['title'])) {
		$channel['title'] = $title_for_layout;
	}

	echo $rss->document($rss->channel(array(), $channel, $content_for_layout));

Here in the layout our RssHelper shines through. We use the method RssHelper::channel() which generates the element and associated metadata elements. The $content_for_layout variable contains the output from the view. These then get passed to the RssHelper::document() method, which wraps the RSS document in the respective elements.

Controller

The controller needs no modification in the case of a simple RSS feed. This is because we are only adding a second view that is xml/rss to the action. The same data is used in both views and because CakePHP automatically sets the correct response type we don't need to tell it to render the correct view and layout for RSS. Here is the action method in the EntriesController for a basic view sorted by a published_date field and showing only if it is published.

	public function index() {
		$this->paginate['Entry'] = array(
			'conditions' => array('Entry.published' => 1),
			'order' => 'Entry.published_date DESC');
		$this->set('entries', $this->paginate());
	}

If you do have code that is specific for only the RSS view you can use the RequestHandler::isRss() to see if the action was called with the request for xml/rss formatting on response. This method returns a boolean value based on if the .rss extension was parsed in the URL.

	if ($this->RequestHandler->isRss()) {
		// RSS feed specific code goes here
	}

Note About Channel Metadata

It may feel right to put your metadata information in the index method in the controller, using Controller::set() to send the information to the views. This is inappropriate and is one of the most common snags that we have seen in the CakePHP community with creating RSS feeds. That information which is passed in the layout file to the RssHelper::channel() method should be set in the view using View::set() which will set the $channel variable for the layout in the view.

Views

As we had to put the layout in a subdirectory of the layouts folder we also need to create a view for the index action for the blogs controller. This is done by creating a directory /views/entries/rss/ which will hold our view file that will generate the RSS to render. You will need to add your RssHelper to the list of helpers in your controller so that it is automatically loaded in the view and the layout.

Our view begins by setting the $channel variable for the layout, this contains all the metadata for our RSS feed.

	$homeUrl = $html->url('/', true);
	$this->set('channel', array(
		'title' => __("Daniel's Recent Articles", true),
		'link' => $homeUrl,
		'description' => __("Most recent articles from Daniel.", true),
		'language' => 'en-us',
		'image' => array(
			'title' => 'Recent Articles from Daniel',
			'url' => FULL_BASE_URL . $this->webroot('/img/rss_feed_image', true),
			'link' => $homeUrl));

First we get the URL link for the website home that we will use for the links. Also we set the title, description and image to use for the RSS feed icon. By setting the channel variable using View::set() we are providing the layout the information to render the RSS feed's metadata elements.

The second part of the view generates the elements for the actual records of the feed. This is accomplished by looping through the data that has been passed to the view and using the RssHelper::item() method. The other method you can use, RssHelper::items() which takes a callback and an array of items for the feed. (The method I have seen used for the callback has always been called transformRss(). There is one downfall to this method, which is that you cannot use any of the other helper classes to prepare your data inside the callback method because the scope inside the method does not include anything that is not passed inside, thus not giving access to the TimeHelper or any other helper that you may need. The RssHelper::item() transforms the associative array into an element for each key value pair.

	foreach ($entries as $entry) {
		$postTime = strtotime($entry['Entry']['created']);

		$entryLink = array(
			'controller' => 'entries',
			'action' => 'view',
			'year' => date('Y', $postTime),
			'month' => date('m', $postTime),
			'day' => date('d', $postTime),
			$entry['Entry']['slug']);

		// This is the part where we clean the body text for output as the description 
		// of the rss item, this needs to have only text to make sure the feed validates
		$bodyText = preg_replace('=\(.*?)\=is', '', $entry['Entry']['body']);
		$bodyText = $text->stripLinks($bodyText);
		$bodyText = Sanitize::stripAll($bodyText);
		$bodyText = $text->truncate($bodyText, 400, '...', true, true);

		echo  $rss->item(array(), array(
			'title' => $entry['Entry']['title'],
			'link' => $entryLink,
			'guid' => array('url' => $entryLink, 'isPermaLink' => 'true'),
			'description' =>  $bodyText,
			'dc:creator' => $entry['Entry']['author'],
			'pubDate' => $entry['Entry']['created']));
	}

You can see above that we can use the loop to prepare the data to be transformed into XML elements. It is important to filter out any non-plain text charictars out of the description, especially if you are using a rich text editor for the body of your blog. In the code above we use the TextHelper::stripLinks() method and a few methods from the Sanitize class, but we recommend writing a comprehensive text cleaning helper to really scrub the text clean. Once we have set up the data for the feed, we can then use the RssHelper::item() method to create the XML in RSS format. Once you have all this setup, you can test your RSS feed by going to your site /entries/index.rss and you will see your new feed. It is always important that you validate your RSS feed before making it live. This can be done by visiting sites that validate the XML such as Feed Validator or the w3c site at http://validator.w3.org/feed/.

Latest articles

Build a Single Page Application Using CakePHP and InertiaJS

Build a Single Page Application using CakePHP and InertiaJS

  The Inertia Plugin allows a CakePHP application to integrate Vue 3 components in the front end, without the need to write a specific API for data transfer. This is done  by adding a Middleware and view classes that facilitate the conversion of objects and data in JSON almost automatically, as well as the direct load in the components. The plugin is thought of as a base to extend and use your app’s specific controllers and views from. Just because  it works out of the box doesn't mean it is intended to be used exactly as is,  but this will  provide you a good kick start. See the repo here: https://github.com/CakeDC/cakephp-inertia

Requirements

  • CakePHP 4.5
  • PHP >= 8.1
  • NodeJS 18.9 (only for build Vue Components, not required on running site)

 

Step 1: Create a basic CakePHP install

  For this example I will use a basic installation using Docker and Composer.  First you must create project from cakephp/app  
$> composer create-project --prefer-dist cakephp/app:~4.5 inertia_app $> cd inertia_app $> cp config/app_local.example.php config/app_local.php
  Then write an docker-compose.yml file as:
version: '3' services:   psql13:     image: postgres:13     container_name: inertia-app-postgres13     volumes:       - ./tmp/data/inertia-postgres13__db:/var/lib/postgresql:delegated     environment:       - POSTGRES_USER=my_app       - POSTGRES_PASSWORD=secret       - POSTGRES_DB=my_app       - PGUSER=my_app       - PGDATABASE=my_app       - PGPASSWORD=secret     ports:       - '7432:5432'     cakephp:     image: webdevops/php-nginx:8.1     container_name: inertia-app-cakephp     working_dir: /application     volumes:       - ./:/application:cached       - ~/.ssh:/home/application/.ssh:ro     environment:       - WEB_DOCUMENT_ROOT=/application/webroot       - DATABASE_URL=postgres://my_app:secret@inertia-app-postgres13:5432/my_app     ports:       - "9099:80"
  Launch the container and go to http://localhost:9099/  
$> docker-compose up -d
 

Step 2: Add CakePHP Inertia plugin

  Install plugin via command line:
$> composer require cakedc/cakephp-inertia
  Once installed enable it in src/Application.php, adding at the bottom of bootstrap function:
$this->addPlugin('CakeDC/Inertia');
  or by command line:
$> bin/cake plugin load CakeDC/Inertia

 

Step 3: Create Vue App and install it

  To create Vue App type in command line:
$> bin/cake create_vue_app
  This command create in the resources directory the files that use our App, also create in root directory the files:
  • webpack.mix.js
  • package.json
  Then in root directory install with NPM:
$> npm install

 

Step 4: Create simple SPA (Single Page Application)

  Create a single page called dashboard that show values sets in a controller action We need to first add InertiaResponseTrait  
use CakeDC\Inertia\Traits\InertiaResponseTrait;   class PagesController extends AppController {    use InertiaResponseTrait;    ...  ...   }
  Create a new function that would look like this:
public function dashboard() {   //set default php layout of plugin that use vue   $this->viewBuilder()->setTheme('CakeDC/Inertia');     $page = [       'text' => 'hello world 1',       'other' => 'hello world 2',   ];   $this->set(compact('page')); }
  in config/routes.php uncomment lines to catch all routes:
$builder->connect('/{controller}', ['action' => 'index']); $builder->connect('/{controller}/{action}/*', []);
and comment line:
$builder->connect('/pages/*', 'Pages::display');
  Then create file resources/js/Components/Pages/Dashboard.vue that would look like this:
<script setup> import Layout from '../Layout' import { Head } from '@inertiajs/vue3' import {onMounted} from "vue";   defineProps({     csrfToken: String,     flash: Array,     page: Array, })     onMounted(() => {     console.log('Component Dashboard onMounted hook called') }) </script>   <template>     <Layout>         <Head title="Welcome" />         <h1>Welcome</h1>         <p>{{page.text}}</p>         <p>{{page.other}}</p>     </Layout> </template>
  On root directory execute:
$> npm run dev
  IMPORTANT: Whenever you modify the .vue templates, you must run this script. Go to http://localhost:9099/pages/dashboard to see that Dashboard Vue Component prints values assignments on Dashboard CakePHP function.
   

 

Step 5: Bake CRUD system

  For this example, we use sql file on config/sql/example/postgresql.pgsql   That creates a database with the relations     Once the database has been created, bake models and controllers as normal using:
$> bin/cake bake model Pages --theme CakeDC/Inertia $> bin/cake bake controller Pages --theme CakeDC/Inertia $> bin/cake bake model Tags --theme CakeDC/Inertia $> bin/cake bake controller Tags --theme CakeDC/Inertia $> bin/cake bake model Categories --theme CakeDC/Inertia $> bin/cake bake controller Categories --theme CakeDC/Inertia
  and bake templates using vue_template instead of template as:
$> bin/cake bake vue_template Pages --theme CakeDC/Inertia $> bin/cake bake vue_template Tags --theme CakeDC/Inertia $> bin/cake bake vue_template Categories --theme CakeDC/Inertia
  Again run:
$> npm run dev
  You can the results from this example by going to http://localhost:9099/pages/index   In the following recording you can see how to add, edit and delete a record without reloading the page at any time.

 

Step 6: Using prefix and adding a navigation menu

  Add route to prefix Admin on config/routes.php
$builder->prefix('admin', function (RouteBuilder $builder) {    $builder->fallbacks(DashedRoute::class); });
  To generate controllers and template with a prefix use --prefix option of bake command as:
$> bin/cake bake controller Pages --prefix Admin --theme CakeDC/Inertia $> bin/cake bake controller Tags --prefix Admin --theme CakeDC/Inertia $> bin/cake bake controller Categories --prefix Admin --theme CakeDC/Inertia $> bin/cake bake vue_template Pages --prefix Admin --theme CakeDC/Inertia $> bin/cake bake vue_template Tags --prefix Admin --theme CakeDC/Inertia $> bin/cake bake vue_template Categories --prefix Admin --theme CakeDC/Inertia
  You can add a horizontal menu to navigate through controllers   Edit resources/Components/Layout.vue and put inside header tag links as:
<header>    <Link as="button" href="/pages/index" class="button shadow radius right small">Pages</Link>    <Link as="button" href="/tags/index" class="button shadow radius right small">Tags</Link>    <Link as="button" href="/categories/index" class="button shadow radius right small">Categories</Link> </header>
  Again run:
$> npm run dev
  You can see the results from this  example by going to http://localhost:9099/admin/pages/index   In the following recording you can see how to add, edit and delete a record without reloading the page at any time and navigate through pages, tags and categories.

  Hopefully this example will make your experience easier! Let us know: [email protected].

When and why should you upgrade to CakePHP 5?

CakePHP 5.0.0 was released on September 10th. The current version as of today is 5.0.3 (released Nov 28th and compatible with PHP 8.3 https://github.com/cakephp/cakephp/releases/tag/5.0.3). You might be asking yourself some questions related to the upgrade… here's what we've been recommending to our clients to do since version 5 was released. Leaving aside the obvious reasons for an upgrade, today we're going to categorize the decision from 2 different points of view: Your current CakePHP version, and your role in the project.

When should you upgrade? 

  We are going to use current CakePHP version as the main criteria: * If you are in CakePHP <= 2   * We strongly recommend an upgrade as soon as possible. If you are unable to upgrade, try to keep your PHP version and all the underlying dependencies as fresh as you can and isolate the application as much as possible. If your application is internal, consider using a VPN blocking all outside traffic. If your site is open to the public, consider using an isolated environment, hardened. Adding a web application firewall and a strict set of rules could also help to mitigate potential security issues. Even if CakePHP is very secure, the older versions of CakePHP, like  1 and 2  have a very old code base , and other vendors/ libraries could be a serious security risk for your project at this point.   * If you are in CakePHP 3.x   * The effort to upgrade at least to CakePHP 4.x should not be a blocker. We would recommend upgrading at least to the latest CakePHP 4.5.x. You can actually "ignore" the deprecations for now, you don't need to plan for upgrading your authentication/authorization layers just yet, focus on getting your project stable and up to CakePHP 4.5.x in the first round.   * If you are in CakePHP 4.x   * Upgrading to CakePHP 5.x is not an immediate priority for you.   * I would say, 2024 is a good time to start planning for an upgrade. Feature and bugfix releases for 4.x will continue until September 2025. Security fixes will continue for 4.x until September 2026. You have plenty of time to consider an upgrade, and take advantage of newer (and faster!) PHP versions.  

Why should you upgrade? 

  We are going to use your role in the project to provide some good reasons: * If you are a developer   * More strict types, meaning better IDE support and more errors catched at development time.   * New features in CakePHP 5.x will make your code more readable, like Typed finder parameters https://book.cakephp.org/5/en/appendices/5-0-migration-guide.html#typed-finder-parameters      * Quality of life features, reducing development time like https://book.cakephp.org/5/en/appendices/5-0-migration-guide.html#plugin-installer   * Compatibility with PHP 8.3 for extra performance & support   * If you are a manager   * Ensure your development team is forced to drop old auth code and embrace the new authentication/authorization layer https://book.cakephp.org/5/en/appendices/5-0-migration-guide.html#auth   * The new authentication layer will allow you to easily integrate features like single sign on, two factor authentication or hardware keys (like Yubikeys), as there are plugins available handling all these features.   * Get an extended support window. CakePHP is one of the longest maintained frameworks out there, upgrading to CakePHP 5 will keep your core maintained past 2026.   * Upgrade to PHP 8.3 and force legacy vendors to be up to date with the new version, this will also push your team to get familiar with the new PHP core features.   * If you are an investor, not directly related with the project day-to-day operations   * Secure your inversion for a longer period.   * Reduce your exposure to security issues.   * Send a strong message to your partners, keeping your product updated with the latest technology trends.   * Send a strong message to your team, investing in the upgrade of your application will let them know the project is aiming for a long term future.   In conclusion, upgrading to CakePHP 5 is a good move for 2024 whether you're a developer, manager, or investor. The version 5 is stable and ready to go. Staying current becomes not just a best practice but a strategic advantage.   If you are in doubt, feel free to contact us. We'll review your case (for free) and provide an actionable recommendation based on your current situation in the next business day.  

A quick CakePHP Local environment with DDEV

In the realm of web development, a seamless local environment is the bedrock for efficient and stress-free coding. Enter DDEV, a powerful tool that simplifies the setup process and empowers developers to dive into their projects with ease. In this blog post, we'll embark on a journey to demystify the process of setting up a local development environment using DDEV. Whether you're a seasoned developer or just starting in the world of web development, optimizing your local environment can significantly enhance your workflow.

Pre Conditions :

Install Docker https://docs.docker.com/get-docker/ and install DDEV https://ddev.readthedocs.io/en/stable/

Step 1: Create a new CakePHP project skeleton 

composer create-project cakephp/app myproject A new folder "myproject" will be created with a CakePHP project skeleton inside. Go to this new directory and proceed with the following instructions.

Step 2: Initial ddev setup

Run ddev config
This will do the initial ddev setup, press enter for all questions.  Run ddev auth ssh
This will add ssh key authentication to the ddev-ssh-auth container

Step 3: Adjust the settings

Inside "myproject" a new .ddev folder will be created, open config.yaml  and adjust there: php version, database and the database url environment.  For PHP:
php_version: "8.1"

For the database: database: type: mysql version: "8.0" For the environment variable: web_environment: - DATABASE_URL=mysql://db:db@db/db

Step 4: Start ddev

ddev start  This will spin up the project.

Step 5: Open your application

ddev launch This will open your project in a browser.   Once you have the application up and running, some useful commands you could run are:
  • ddev composer to execure composer
  • ddev mysql to get into the database
  • ddev ssh takes you into the web container.
In this link https://ddev.readthedocs.io/en/latest/users/usage/cli/ you can see more useful commands.   Hope you enjoy playing with DDEV!

 

   

 

 

We Bake with CakePHP