RSS
 

CakePHP 3.7 and PHP 7.3

22 Nov

What a timing!
Soon both these minors will land as stable versions.

CakePHP 3.7

Let’s talk about this minor first. RC-2 has just landed.
The changelog towards master (3.6) shows, that the changes are not too much, it is basically a last kind of bridge towards a 4.0.
This is a diff of the book versions, and the 3.7 migration guide mainly lists a lot of new deprecations, nothing critical
to updating as it will continue to work until 4.0.

Try it now

You should upgrade your apps and plugins now in a separate branch/PR and let CI and manual testing determine if there are any BC breaks that need to be reported.
Do so early, as this is the best chance to get all found issues covered in time for the stable release.

Note that if you composer require --update-with-dependencies "cakephp/cakephp:3.7.0-RC2", you might also need to switch some core plugins, like DebugKit, to the 3.next branch here. Or you just disable it for the test.

For the beginning best to also disable deprecations in your config (E_ALL & ~E_USER_DEPRECATED) so you can actually test the relevant topics for compatibility.
All method name upgrades can be done separately also weeks later, no need to dive into all at once.

For me, all worked flawlessly. My apps were immediately working again (with deprecations silenced), also almost all plugins were fine.

Apropos Plugins

Try to keep compatibility for 3.6 just for a few more months if you can.
Don’t directly hardcode ^3.7 if you don’t use any of its new functionality, as you otherwise force everyone using it to upgrade everything in order to get the latest bugfixes. It can also create deadlocks if multiple plugins have been locked to it, while others aren’t upgraded yet.
Best to remove all deprecations from 3.5 and below and otherwise silence the deprecations for it inside the config and keep it compatible for both minors for some time.

Tooling

As mentioned in my previous post for 3.6 release, you should definitely provide a CI check for your plugins in terms of minimum version compatibility. Especially if you do support multiple minors, it is easy to miss a newly added method being used.
Also check the other tips there, as they all still bear valuable tips.

PHP 7.3

This PHP minor will also introduce only slight changes, mostly to make PHP a bit more strict and issue warnings for obvious issues a bit immediate.
In many cases, your application will be compatible.

Look out for your code around

  • Regular expressions (Using - in the middle of [] character groups)
  • compact() usage with undefined variables
  • String search functions with integer needle (I could actually fix a few lines of code this way even! See e.g. this missed check throwing a warning now)

Adjust your CI matrix

I already adjusted my Travis CI matrixes for including PHP 7.3 in the builds.

If you still support PHP5.6, I recommend not to have the full list of each minor, but use min/max for PHP7 here:

php:
  - 5.6
  - 7.0
  - 7.3

Especially if you also test multiple DB types per PHP version, this cuts down on the builds to be run.
And seriously, if 7.0 as well as the latest minor works, then most certainly all the minors in between are also just fine.

I sometimes also like to combine versions and DB types, so each handles one, this also cuts down on too many jobs here per commit.

Help the Community

You have a bit of time and are unsure where to help out?
As just mentioned, many ecosystem libraries – and especially plugins – will need some love. Check the awesome list for some of the more popular and frequently used ones, or just pick your favorites.
You can fork them, prepare the adjustments, see if Travis report is green and make a PR to the upstream repository.
For simple checks, you can even do all of that simply in the browser these days 🙂

The maintainers of plugins really love if people contribute back and take a bit of the maintenance overhead away.
I sure am.

You can also check the core issues and PRs and see if you might be able to help to get things done directly in the CakePHP core.
Specifically, take a look at the 3.7 milestone here.

Thank you! And happy baking.

5.00 avg. rating (93% score) - 1 vote
 
No Comments

Posted by Mark in CakePHP, PHP

 

Tutorial: CakePHP & Tagging

12 Jul

This article mainly shows the power of a Rapid Development Framework like CakePHP.
The full plugin documentation can be found directly in the repository.

Let’s imagine we have some blog posts in our database and we need to quickly add tagging functionality.
So let’s make our backend support adding and editing tags for a post, then display them in the paginated index and also in the public view.

Setup

We first composer require the plugin:

composer require dereuromark/cakephp-tags

We then need to load the plugin. One can just use CLI here too:

bin/cake plugin load Tags

Then we need to add the default Tags plugin tables using Migrations plugin:

bin/cake migrations migrate -p Tags

I usually add this into the composer script section:

"scripts": {
	...
	"migrate": [
		"bin/cake migrations migrate -p Queue",
		"bin/cake migrations migrate -p Tags", // Added
		"bin/cake migrations migrate"
	]
},

So I only have to execute one command locally and for deployment:

composer migrate

Our PostsTable class needs to know about the tagging functionality now:

	public function initialize(array $config) {
		parent::initialize($config);
		...
		$this->addBehavior('Tags.Tag', ['taggedCounter' => false]);
	}

We don’t add an app migration for a counter cache field for now, even though this can easily be done if needed.

We load the helper in the AppView class:

	public function initialize() {
		...
		$this->loadHelper('Tags.Tag');
	}

Let’s adjust the forms now:

// Adding the input field into the add and edit form
	echo $this->Tag->control();

Finally, our edit action needs to know that it should also fetch already saved tags to pre-populate the input field:

// Inside edit action
$post = $this->Posts->get($id, [
	'contain' => ['Tags'] // Add this line
];

That should be it. If you open up the add action, we can add comma separated tags like Foo, Bar and they would be displayed when you open up the edit action. You can modify and see that this also works already.

Only very few lines and a few configuration flags and you got a whole bunch of functionality out of the box for your application.
Time so far: 10 minutes.

Displaying

Now let’s add a nice list in both index and view:

// In the index action
$this->paginate['contain'] = ['Tags'];

// In the view action
'contain' => ['Tags'] // Add this line

// In the templates
echo h($post->tag_list) // Outputs a comma separated list

If you want more control, you can use the tags array:

// Either as helper
echo $this->SomeHelper->displayTags($post->tags);

// Or as element
echo $this->element('tags', ['tags' => $post->tags]);

Available fields per tag: id, namespace, slug, label, counter, created, modified, …

You could also create links to the index that allows filtering by tag:

echo $this->Html->link($tagName, ['action' => 'index', '?' => ['tag' => $tagSlug]]);

This would work together nicely with e.g. friendsofcake/search plugin (see tips secion).

Time so far: 20 minutes.

Tag Cloud

Wouldn’t it be only a half-solution if we couldn’t display the tags in a nice way, showing
the most used ones on some tags dashboard URL? 🙂

In our controller we can add a tags action:

$tags = $this->Posts->Tagged->find('cloud')->toArray();
$this->set(compact('tags'));

In the tags.ctp template:

<?php
$this->loadHelper('Tags.TagCloud');
echo $this->TagCloud->display($tags, ['shuffle' => false], ['class' => 'tag-cloud']);
?>

With a bit of custom CSS you can make each tag a floating element.
By default the shuffle is enabled, you can disable using 'shuffle' => false config as shown above.

Time total: 30 minutes max.

You can see that adding functionality like this can be hooked in rather easily in such a convention driven framework.
Check out the awesome list for a few more of those.

Tips

Security

This tutorial was mainly for a CRUD kind of backend application.
If you expose the functionality – maybe even with some AJAX – to the frontend, make sure that Csrf/Security components, as well as field whitelisting, are definitely in place (incl. $accessible fields of the entity).

If users can modify their posts’ tags, make sure that the id of the user matches the user_id of the post before allowing any modification.
Add, Edit, Delete actions should always be non-GET. $this->request->allowMethod('post') etc can help here.
The default bake templates can guide you here. They come with good defaults out of the box.

Note that all output of tags is always secured using h(), especially when other users can enter/add tags. This way you prevent XSS vulnerabilities.

Combining it with filtering

You can easily combine the tagged custom finder with e.g. Search plugin.
This way you can add a filter to your paginated index action.

Just pass a list of tags ([slug => name] pairs) down to the view layer where you populate the search form field as dropdown, for example:

echo $this->Form->control('tag', ['options' => $tags, 'empty' => true]);

In your table’s searchManager() configuration you will need a small callback config:

$searchManager
	...
	->callback('tag', [
		'callback' => function (Query $query, array $args) {
			// Here you would have to remap $args if key isn't the expected "tag"
			$query->find('tagged', $args);
		}
	]);

The CakePHP ORM here is really powerful and will automatically use inner joins here for the query.
An example of the generated SQL can be seen here.

Adding an untagged filter

In some cases you want to allow filtering for all records without tags. In this case the find('untagged') custom finder can come in handy:

		'callback' => function (Query $query, array $args, $manager) {
			if ($args['tag'] === '-1') {
				$query->find('untagged');
			} else {
				$query->find('tagged', $args);
			}
		}

Your form then just needs an extra row here for the dropdown:

$tags['-1'] = '- All without any tags -';
echo $this->Form->control('tag', ['options' => $tags, 'empty' => true]);

Auto-complete and IDE usability

You will notice that in the templates, for example, you can’t click on the Tags::control() method just yet.
The new properties of the entity or the behavior’s methods in our table are also yet unknown to the IDE.

Run the IdeHelper to auto-add the missing annotations into your classes:

bin/cake annotations models -v
bin/cake annotations view -v

It will add

// For the table
@property \Tags\Model\Table\TaggedTable|\Cake\ORM\Association\HasMany $Tagged
@property \Tags\Model\Table\TagsTable|\Cake\ORM\Association\BelongsToMany $Tags
@mixin \Tags\Model\Behavior\TagBehavior

// For the entity
@property \Tags\Model\Entity\Tagged[] $tagged
@property \Tags\Model\Entity\Tag[] $tags

// For the AppView and thus the templates
@property \Tags\View\Helper\TagHelper $Tag

The only one that you have to add manually:

// For the entity
@property string $tag_list !

since this cannot be known by the annotations shell.

Tags backend

The plugin itself does not ship with controllers or routing. You can easily add this yourself in your application, though.
Just bake the Tags controller with some CRUD actions for your backend (e.g. using --prefix admin).

More advanced usage and configuration

Auto-complete, validation, colors, scopes (namespaces), AJAX, counters and counter caches, …: See the plugin directly.

Whatever is missing: Feel free to PR (pull request) enhancements here.

Demo

This article and its demo were written using CakePHP 3.6.
A live demo and some showcases can be found in the sandbox.

4.00 avg. rating (81% score) - 4 votes
 
No Comments

Posted by Mark in CakePHP

 

Middleware and CakePHP

22 Apr

I started to migrate some of my CakePHP 3.x apps to CakePHP 3.3 middleware and even tested 3.4 already back in 2016.
Until then I didn’t even try the new PSR7 way. But it was about time I caught up here.
I wanted to actually start using some of the Middleware classes available.

I also always wanted to dig deeper into this topic that hasn’t yet too much blog post coverage it seems.
Now, let’s get started here 1.5 years later – but with some very useful gotchas (later in the article more).

Middleware what?

Yeah, this new PSR7 thing – you can also read more about it here.
In fact, it is not that new, but more and more frameworks have been adopting, and so did CakePHP.
When you are upgrading from 3.3 to a newer minor version, you will stumble upon it.

There are also tons of middleware already available out there that – by design – is supposed to work with any PHP framework.
Check out this impressive list.

But now back to CakePHP 3.

First steps

So what to do first to get this new infrastructure working?
The docs explain it quite well:

  • You update your index.php with the new way of dispatching a request (see cakephp/app‘s index.php).
  • You copy and paste the Application.php skeleton into your src/ directory (App namespace). It should be the only class there.

Then you remove your current bootstrap lines around DispatcherFactory::add(...).
You now can add the same functionality as Middleware again in your Application class.

Hands on

Let’s show it with two of my new Middleware classes, "Maintenance" and the improved "ErrorHandler". The first will be an addition, the second one a replacement.

use Setup\Middleware\MaintenanceMiddleware;
use Tools\Error\Middleware\ErrorHandlerMiddleware;
...
class Application extends BaseApplication {
	/**
	 * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
	 * @return \Cake\Http\MiddlewareQueue The updated middleware.
	 */
	public function middleware($middlewareQueue) {
		$middlewareQueue
			// Add Setup plugin Maintenance middleware
			->add(MaintenanceMiddleware::class)
			// Improved Tools plugin ErrorHandler middleware
			->add(ErrorHandlerMiddleware::class)
			// Handle plugin/theme assets like CakePHP normally does.
			->add(AssetMiddleware::class)
			// Finally apply normal routing
			->add(RoutingMiddleware::class);
		return $middlewareQueue;
	}
}

Instead of the ::class way you could also instantiate the classes: new ...Middleware().
But I prefer to keep them strings only until used, especially since I don’t need to pass any arguments for them.

When to use, and when not to use.

The middleware opens a few new possibilities, but it can also introduce more bad code.
You should decide wisely, what can/should be transformed into a middleware class.

Useful cases are usually:

  • Basic low level caching.
  • Serving assets (e.g. a custom WYSIWYG editor serving).
  • Manipulating the request early on, or the response afterwards.
  • Authentication and header parsing, e.g. Token based ones
  • Needing a very early on decision point on certain dispatching options.

You should be careful about:

  • Templating/Rending involved, here usually it is better to use controller (+component?) and normal view rendering.
  • Needing more customization on a controller level, on what parts of the app invoke this logic and which don’t (usually can stay an easy to hook in component).

Now to the gotchas I promised:

Why the improved ErrorHandler?

The core one treats all errors the same by default. Even user-triggered "out of bounds" or not found records are logged into the "error" log.
I find that totally wrong – and even harmful – as it cloaks the actual "internal errors" of higher severity.
So the improved handler provides a way to whitelist certain exceptions as "404" ones that can be triggered from visitors or bots and do not represent internal application errors. It uses the referer to decide whether this is "404" (externally triggered), or an actual issue for error.log (internal reference).
This 404.log then can be occasionally checked and removed, while the error.log as such has to be more closely monitored and everything coming in should be fixed ASAP.

More about the implementation details in the Setup plugin docs.
It is important to also switch out the low level handler in your bootstrap here as well as setting up a specific 404 listener.

Tip: If you use DatabaseLog plugin, you can easily set up a monitoring cronjob to alert the admin (e.g. via email) within seconds of severe issues/errors. With the above "separation" you don’t have false positives and will get only "real" alerts. The plugin built in alert system also makes sure errors are batch-sent to not send out too much emails. Of course you can also limit the scope of alerts further to e.g. only very severe exceptions using the configuration.

Maintenance, what’s that about?

I use this to put the whole application into maintenance mode if there is a more time intensive deployment coming up to assert the users don’t upload images during that timeframe, or put something into the database while it migrates which then breaks.
Usually it is only a few seconds – but it helps to keep the integraty of the website intact.

See my blog post about it, also check the Setup plugin documentation.

The best use case is adding those two commands (enable, disable) into your deploy script, at the beginning and the end. But you can also invoke it through command line manually, when you plan on doing some larger migration or maintenance work.

Other useful middlewares

(Browser) Language detection

The I18n middleware sounds useful if you need to provide multi-locale output and want to detect the best default.
The old approach would have been a component, the more modern way is a middleware here then, for example.

Throttling

The Throttle middleware can be used for certain (API) requests to stay under a certain limit of requests per timeframe.

Your recommendations?

You can comment with your recommended middleware classes, CakePHP or otherwise.

4.50 avg. rating (88% score) - 2 votes
 
No Comments

Posted by Mark in CakePHP

 

CakePHP 3.6 is coming

14 Mar

The first betas have landed already. Make your plugins and apps 3.6-ready now.
It is actually rather easy – better to be proactive now and have a small migration path than a big fallout and some surprises in a few weeks or months.

Prepare your Application

First read the 3.6 migration guide to get a grasp of the upcoming changes.
Deprecations ignored (easy ones, more later), there are not too many changes – especially no heavy ones. All in all this should be a very smooth upgrade from previous 3.x versions.

Now for the actual upgrade we have a few important issues to talk about:

Regressions

3.6 might introduce accidental regressions or issues that your app can not deal with. Those should be either fixed or outlined in the documentation and migration guide.
You can spend a few minutes here to find out early on how compatible your app is to a new minor version. Execute

composer require cakephp/cakephp:"dev-3.next as 3.5.14"

if you want to check the latest 3.6 version faked as 3.5 (due to the constraints). You can of course try 3.6 here as aliases, as well (as long as no constraints locks it down to ~3.5.x).

Note: For some plugins there might be already a 3.next branch (or even tags), as well.

Report or PR any issues you find and help to make the 3.6 release as smooth and BC as possible.

Migration path

Always keep up with minors in the framework. So at this point I expect everyone to be already running on CakePHP 3.5 for a while.

You can already have fully removed any deprecation of 3.4 or 3.5, which will further make the migration path smaller and easier to review.
Since 3.6 will trigger actual deprecation notices, this is really advised to do early on.
Once you actually switch to 3.6 you can actually silence the deprecations for the first run to verify functional compatibility by setting the error level in your app.php configs:

    'errorLevel' => E_ALL & ~E_USER_DEPRECATED,

Read the 3.6 migration guide for any functional upgrade that might be necessary, those should be applied first.

Once you established functional BC, you can start replacing all deprecations and afterwards again all tests should pass.

Tip: Try not use the above silence as a production default if possible. Instead try to remove those asap. But if not all plugins are updated yet, or do you don’t have the time to fix all of them right now, it is acceptable to just keep the deprecations silenced for the time being.

Migration tooling

Until now there was the CakePHP Upgrade tool which really helped a lot for the migration.
And it still does for a small subset, e.g. upgrade skeleton syncs the skeleton files from the latest cakephp/app repo into your app.
So it might not be completely dead just yet. If you are even below 3.5, definitely check it out.

But regex can only do so much. For more complex issues and especially all the deprecations there are now more modern and less error prone solutions available.
There is a very useful PHP tool that can help with deprecation replacement called rector.
It introspects the code and can very reliably identify and replace code pieces.

I tried it and using

composer require --dev bamarni/composer-bin-plugin
composer bin rector require --dev rector/rector
// lets dry-run for src/ directory
vendor/bin/rector process src/ --level cakephp36 --dry-run

it will show you what it would change as diff. Without --dry-run it will then actually run the upgrade. Make sure you committed all files or made a backup.

Tip: If it takes too long, just run it on each subfolder, e.g. src/Model, …

You can take a look at its CakePHP config. If there is anything missing, you can PR (pull request) your additions.

Plugins/Ecosystem

If you encounter issues with plugins or alike, open a ticket or better yet a pull request to get these things fixed ASAP.
This should ideally also be done pro-actively before you actually start upgrading your code-base, so that there is no time-bottleneck.

In case there really is something stuck, you can make a fork and use a tmp branch of master on your side for the upgrade.
Your composer.json would then point to dev-master of your repository fork and contain a repositories link so that composer knows to look there:

  "repositories": [
    {
      "type": "git",
      "url": "git@github.com:your-name/plugin-name.git"
    }
  ],

Once there is a tag with the fixed code in that original repository, you can remove and use the tagged release again.

Prepare your Plugins

This actually brings us the next point – if you are maintainer of a plugin yourself.

Having so many deprecations throughout 3.x it becomes more and more difficult to keep track of when methods were added and since what version you can use them, or not use others anymore.
With 3.6 this is more important than ever since this now adds deprecation warnings emitted for deprecated usage.
So make sure that if you start supporting 3.6 for your plugins, that your composer.json constraint towards cakephp/cakephp reads ^3.6. Users need to know if they can pull your version without the application breaking.

First of all: Make sure you support 7.2 in your Travis test matrix. There are quite some changes hidden in count() usage that can easily break your code or at least emit warnings.

Secondly, make sure you got also older PHP and CakePHP versions properly regression tested.
I started to actually add the following trick into my plugins:

matrix:
  include:
    ...
    - php: 5.6
      env: PREFER_LOWEST=1

Then exchange the first before_script line composer install with

before_script:
  - if [[ $PREFER_LOWEST != 1 ]]; then composer install --prefer-source --no-interaction ; fi
  - if [[ $PREFER_LOWEST == 1 ]]; then composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable ; fi

This will make sure, composer uses the lowest possible versions everywhere.
You will now spot any wrong usage of methods right in your tests in Travis – if those are covered by tests, that it.
That’s why I think it is more valuable that the tests execute the code than having good asserts. Sure, the latter is also needed in some cases. But the mere fact that the code executes fine means already a lot, from syntax issues (based on the PHP changes even in minor versions) and interface issues to other more runtime issues.

A full working version can be seen e.g. in TinyAuth .travis.yml.

Try to support 3.5 and 3.6

It is usually best to not always jump right to the latest minor as minimum requirement. This makes it harder for folks on the previous minor to get patches and bugfixes.
Thus, with the new deprecation warnings, you might have to do the following if you use any deprecated functionality because of this:

Add <ini name="error_reporting" value="16383"/> into your phpunit.xml.dist file (see here for example).

Similar things can be required for tooling like PHPStan. The linked IdeHelper plugins also shows a solution for this using a shim.php file that contains

error_reporting(E_ALL & ~E_USER_DEPRECATED);

Icing on the cake

You want to go even a step further and get alerted early on if your plugin or app breaks with new minors or patches?

Most of the time I encountered CI fails and BC issues months after when I applied a new change to a plugin and suddenly tests that should have passed didn’t pass anymore.
In this case you can see that the core way of building a query has been changed and now has less () brackets in 3.5+. Notified about this I can just make the tests conditional on the CakePHP version and all is good again.

It is rather easy to enable a cronjob in CI (e.g. Travis) that runs nightly or weekly and alerts you of any failures of new releases.
This is an example of how to enable for Open Source plugins in Travis:

5.00 avg. rating (97% score) - 4 votes
 
1 Comment

Posted by Mark in CakePHP

 

CakePHP Tips 2017 Part 1

14 Apr

Whoops as ErrorHandler

I recently switched to Whoops in my apps.
It provides a better exception rendering for development mode for most cases.

Just load the plugin (dereuromark/cakephp-whoops) and modify the bootstrap line:

//(new ErrorHandler(Configure::consume('Error')))->register();
(new \CakephpWhoops\Error\WhoopsHandler(Configure::consume('Error')))->register();

If you already have a src/Application.php and run a middleware setup, you also want to make sure you either remove the ErrorHandlerMiddleware there, or provide a custom one for Whoops.

You can test it for example with my sandbox app.
Check it out locally, set it up in minutes and give it a test run.

Jump from browser to IDE

With the Whoops error handler the probably nicest feature is the "click to IDE" one.
It allows to click on the class inside the displayed code piece and jump right into the IDE into the exact line of that file displayed in the browser stack trace.

Be more productive leveraging your IDE

Using the new IdeHelper plugin you can now not only bake annotations into new code, but also adjust
your existing code all the time.
The plugin provides a shell to quickly sync the existing annotations with your code.

This way you can maximize IDE compatibility and with that prevent obvious issues and mistakes.
The IDE will usually mark problematic code yellow (missing, wrong method etc).

It can also enable you to click through the classes and object chains which otherwise would just be plain text and have no typehinting.
Finally autocomplete will be available for all the "magically" provided classes and methods this way.

screenshot

This plugin is supposed to work with ANY IDE that supports annotations.
The plugin wiki contains even details and tips/settings for each of those IDEs – collected by the community.

Enable PHPStan in your app and plugins

PHPStan is a super useful static code analysis tool.
It will help you to find issues with your code way beyond what the test suite or IDE syntax checks can do.

For example, if you have two many different return types and only respect one of them, it will mark this as issue.
It will also tell you about interface mismatch or other similar code smells.

Adjust Travis

Add a new matrix row and a script call for that matrix entry:

...
matrix:
  include:
    ...
    - php: 7.0
      env: PHPSTAN=1 DEFAULT=0
...
script:
  ...
  - if [[ $PHPSTAN == 1 ]]; then composer require --dev phpstan/phpstan && vendor/bin/phpstan analyse -l 1 src; fi
...

Level 1 (-l 1) is the lowest level, once everything passes you can go up to level 5 currently. I recommend trying level 3, if possible.

If you need to ignore certain errors, you can use a custom phpstan.neon file in your /tests directory and then append -c tests/phpstan.neon into the command for travis.

vendor/bin/phpstan analyse -c tests/phpstan.neon -l 1 src

For me such a file looked like this, for example:

parameters:
	autoload_files:
        	- %rootDir%/../../../tests/bootstrap.php
	excludes_analyse:
		- %rootDir%/../../../src/TestSuite/*
	ignoreErrors:
		- '#Call to an undefined method Cake\\Datasource\\EntityInterface\:\:source\(\)#'

Don’t try to ignore too much, though – only "false positives" are recommended to be added here.

You can also write custom PHPStan extensions, see the current CakePHP core /tests/PHPStan folder for details.

Using it locally

I added this to my plugins’s composer.json files:

"scripts": {
    ...,
    "phpstan": "phpstan analyse -l 1 src",
	"phpstan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan && mv composer.backup composer.json"
}

This will allow local testing without messing up your composer file 🙂
For app instead of plugin development you also want to backup-restore the lock file.

Now just run

composer phpstan-setup
composer phpstan

I bet you will find a few useful issues to resolve right on the first run of this tool.

5.00 avg. rating (93% score) - 1 vote
 
3 Comments

Posted by Mark in CakePHP

 

CakePHP Tips Winter 2016

31 Dec

This is literally my last blog post for the year 2016 🙂

Use the right DateTime typehint

If you are passing DateTime objects around in CakePHP, they are usually Chronos objects,
and either \Cake\I18n\Time or \Cake\I18n\FrozenTime then.
Both those are not suited as typehint usually, as both are fine for most plugin solutions and also
limit the user in what kind of class he uses. At least FrozenTime would be a bit better then Time.

But what would be the correct typehint? Use DateTimeInterface from PHP.

public function addRow(DateTimeInterface $date, $content, $options = []) {
    $day = $date->format('d');
    ...
}

It is fairly simple to typehint that we need a DateTime object without unnecessarily restricting the plugin to a specific subset.

Note: This is only valid if you are not modifying the object, otherwise use the concrete one. But you really should try to code "immutable" and rather clone and then modify without altering the original value.

Nullable database fields

Nullable fields in the DB should usually either be null or have a concrete value. For foreign keys as well as most integer, float and even string fields that means, that an empty string as input coming in should be transformed to this null value then for data integrity.
Unfortunately, the CakePHP ORM doesn’t do that out of the box for some fields.

Why is it an issue? Well, imagine you want to query for all with an empty value for a field.
Instead of a simple 'field IS' => null you then need to do a more complex ['OR' => ['field IS' => null, 'field' => '']] all the time.
And if you forget it in a single query they results will be inconsistent. That’s kind of bad.

The Shim.Nullable behavior can help here.
Once attached, it will take care of those values when saving and make sure you don’t end up with different null values in your DB.

Leverage Composer scripts

The new cakephp/app skeleton contains some new interesting gotchas regarding composer scripts:

    "scripts": {
        ...
        "check": [
            "@test",
            "@cs-check"
        ],
        "test": "phpunit --colors=always",
        "cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests",
        "cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests"

That basically standardizes and simplifies the way the test and sniff calls are executed.
Before it was php phpunit and a long vendor/bin/phpcs ... call, now it is always just composer test or composer cs-fix etc.
Even doing together now is easy with composer check.

My apps and plugins use the PSR2R sniffer version, but from the outside the commands are completely identical, which is nice.

Note: The vendor/bin/ prefix part (as you might have expected) is not necessary as Composer will still find the scripts just fine.

Testing different databases

Locally, testing the CakePHP core for example defaults to SQLite.
The other databases, as they would need to have a valid connection, are skipped.

But if you are working on a specific feature that would need those to test if it is working, you would want to use that one instead.
Thanks to getenv() usage we can easily exchange the connection via db_dsn.

In your console:

export db_dsn="mysql://root:secret@127.0.0.1/cake_test"

This expects a "cake_test" database set up, and an access with the username root and the password secret (vagrant default for CakeBox here).

For Postgres it would probably be something like postgres://postgres@127.0.0.1/cakephp_test.
CakeBox also ships with that out of the box. You might need to also install the extension before via sudo apt-get install php-pgsql.

When you now run php phpunit.phar from now on, it will use that connection until you switch it again (for that session).

0.00 avg. rating (0% score) - 0 votes
 
2 Comments

Posted by Mark in CakePHP

 

Emoji and CakePHP

11 Nov

You might think: Why do I care.
Well, you would be surprised – but pretty much any website with at least one form on the page will be affected here.
So anyone with even just a basic contact form should think about applying the following changes.

A wile back I got reports that one of my older websites has problems with emoji smileys and alike.
When browsing it with a mobile device and entering a native mobile smiley the emoji and all the following text would be lost when saving.
This was especially annoying with a longer conversation that you then have to retype – many people will not know why the text was truncated and will even run into the same issue again and again.

What’s the issue?

It turns out that native emoji are 4 byte characters, and the normal MySQL database, its tables and fields were all "just" utf8_unicode_ci and only supported less than that.

How to fix it?

First of all: BACK UP YOUR DATA!

With MySQL 5.5+ it is now possible to convert everything to utf8mb4_unicode_ci and then it will safely accept all those characters now, as well.

In your database.php in 2.x or app.php in 3.x you need to change the encoding from utf8 to:

'encoding' => 'utf8mb4',

Then you must convert your database, the tables and all their fields to to the new collation.
You can use my Setup plugin to automate this:

bin/cake Setup.DbMaintenance encoding

But with dry-run param -d you can also just output the SQL commands and manually run them via Migrations plugin, for example.

If you now paste emoji characters and store them, they should be visible just fine on your website now.
Also make sure sending emails, e.g. through a contact form, is working properly.

Troubleshooting

You can run into the following issue:

SQLSTATE[42000]: Syntax error or access violation: 
1071 Specified key was too long; max key length is 1000 bytes

In that case make sure you modify your fields here. With the 4byte length some fields cannot be varchar(255) anymore, but must be a bit less (250) in oder for the convertion to work out. You could also look into "innodb_large_prefix" and options to increase the limit here that way instead.

Can I use the 3.x shell for 2.x?

Sure, with a trick. See use-3-x-migrations-for-your-2-x-cakephp-app on how to provide 3.x shell access and access to my Tools and Setup shells even for 2.x apps.

Tips

If you have a larger database and tables > 1GB you definitely want to put the site into maintenance mode and do this during the night or a very inactive time-frame, as it will need quite a few minutes to complete.

It will also increase your DB size, in my case from 2.1 to 2.5 GB. But that is worth it if it does not silently truncate data anymore.

Or just use Postgres

It turns out that Postgres natively supports emoji and alike, without any need for mb4 modification.
So maybe, if you still use MySQL it could also be time to switch

Further links

5.00 avg. rating (93% score) - 1 vote
 
No Comments

Posted by Mark in CakePHP, MySQL

 

CakePHP and connecting to GitHub API

22 Oct

I had to write a small tool to automate releasing certain GitHub repositories, and for that to authenticate I had to connect to GitHub API.
The integration wasn’t super-easy as there was no documentation yet on how this could be done. But I finally figured it out and want to share it.

HybridAuth plugin

I first introduce the plugin pretty quick I chose to use as authentication piece.
HybridAuth is maintained by a CakePHP core developer and bridges the original HybridAuth implementation into CakePHP. That library aims to "act as an abstract API between your application and various social APIs and identities providers". Out of the box it provides quite a few very popular services to connect to.

Getting started

I did install the plugin as documented, I also made sure the Migration file for it has been included because
we do need a "social_profiles" table here.

Then I connected the Users and SocialProfiles table:

    /**
     * @param array $config The configuration for the Table.
     *
     * @return void
     */
    public function initialize(array $config) {
        parent::initialize($config);
        ...
        $this->hasMany('ADmad/HybridAuth.SocialProfiles');
        EventManager::instance()->on('HybridAuth.newUser', [$this, 'createUser']);
    }
    /**
     * @param \Cake\Event\Event $event
     *
     * @throws \RuntimeException
     *
     * @return \App\Model\Entity\User
     */
    public function createUser(Event $event) {
        // Entity representing record in social_profiles table
        $profile = $event->data()['profile'];
        $username = substr($profile->profile_url, strrpos($profile->profile_url, '/') + 1);
        $user = $this->newEntity(
            [
                'username' => $username,
                'email' => $profile->email
            ]
        );
        $result = $this->save($user);
        if (!$result) {
            throw new \RuntimeException('Unable to save new user:' . print_r($user->errors(), true));
        }
        return $result;
    }

I used the "profile_url" data to automatically generate the same user on my website.
Since the login was only allowed via GitHub login, there was no change of collision.

Then I made sure the HybridAuth authentication adapter is added to the list of components in the AppController:

    /*
     * @return \Cake\Network\Response|null|void
     */
    public function initialize() {
        parent::initialize();
        ...
        $this->loadComponent('TinyAuth.Auth', [
            'authenticate' => [
                'Form',
                'ADmad/HybridAuth.HybridAuth',
            ],
        ]);
    }

I also modified the login according to the documentation.

And finally I just needed a link in the navigation menu in the case the user is not logged in yet:

echo $this->Html->link(
    'Login with GitHub',
    ['plugin' => false, 'prefix' => false, 'controller' => 'Account', 'action' => 'login', 
        '?' => ['provider' => 'Github', 'redirect' => $this->request->query('redirect')]
    ]
);

Note that the "redirect" query string is only necessary for CakePHP 3.4+ when the session is not used anymore for remembering the location to redirect to after login. And also note that at this point only a "dev" branch of the plugin supports the 3.4+ version yet.

Figuring out the configuration

Now that was the most difficult part. With a lot of debugging I found out that since the GitHub provider is not one of the core ones I need to provider wrapper path and class here:

    'HybridAuth' => [
        'providers' => [
            'Github' => [
                'enabled' => true,
                'keys' => [
                    'id' => env('AUTH_ID_GITHUB', ''),
                    'secret' => env('AUTH_SECRET_GITHUB', '')
                ],
                'wrapper' => [
                    'path' => ROOT . '/vendor/hybridauth/hybridauth/additional-providers/hybridauth-github/Providers/GitHub.php',
                    'class' => 'Hybrid_Providers_GitHub'
                ],
                'scope' => 'user:email,repo'
            ]
        ],
        'debug_mode' => false,
        'debug_file' => LOGS . 'hybridauth.log',
    ],

Note that I also set custom "scope" permissions here, you can however here leave that out or add more.

Small tweaks

I didn’t want to use the the plugin controller action to authenticate, but my own in AccountController (in order to execute a few custom things upon login).
So I just overwrote the hauth_return_toURL:

'hauth_return_to' => [
	'controller' => 'Account', 'action' => 'authenticated', 'plugin' => false, 'prefix' => false
]

Testing

Yeah, ok, here I did cheat.
The hybridauth library has a little flaw that makes it difficult to connect to CakePHP as plugin: It always forces the session to be started right away. Especially when testing the controllers now this can be super annoying as it throws ugly warnings:

..Warning Error: session_start(): Cannot send session cookie - headers already sent by 
(output started at phar:///home/vagrant/Apps/.../phpunit.phar/phpunit/Util/Printer.php:134) 
in [/home/vagrant/Apps/.../vendor/hybridauth/hybridauth/hybridauth/Hybrid/Storage.php, line 20]

So I just added the adapter when not in CLI mode:

if (PHP_SAPI !== 'cli') {
	$authenticate['ADmad/HybridAuth.HybridAuth'] = [
		...
	];
}
$this->Auth->config('authenticate', $authenticate);

Tests are green again 🙂

In short

All in all HybridAuth is a great CakePHP plugin to connect this HybridAuth library and any OpenID and OAuth authenticated service to your application.
Give it a spin!

Besides the here mentioned GitHub provider I also managed to use Facebook/Google sign-in this way in another app.
Basically all apps, if for technical users or more a social network, can benefit from such a one-click login as it really takes away the pain of double-opt-in registration forms and alike.

1.00 avg. rating (53% score) - 1 vote
 
No Comments

Posted by Mark in CakePHP

 

CakePHP Tips Fall 2016

18 Oct

Always use proper version check method

Be aware of version checks that are using the inferior and even wrong </> comparison.
This can lead to wrong conditions with higher minor versions.

I had this in my code from the early 2.x days:

if ((float)Configure::version() < 2.4) {
    ...
}

With the upcoming 2.10 release this will now suddenly start to return true. Luckily my 2.next tests already revealed that and I was able to fix it up properly:

if (version_compare(Configure::version(), '2.4') < 0) {
    ...
}

This will now work in all minors of 2.x.

The same is true for 3.x, of course, even though we are "only" at 3.3.6 at this point in time yet.

Order your flash messages semantically

You can use the Flash plugin to enhance your flash messages a bit.
It works out of the box with your current setup, but adds a few useful things on top:

  • Prevent size and output issues when running into a redirect loop boiling up messages in the session (>> 50) by limiting the stack to 10 by default.
  • Use ordered output (error first, then warning, success and lastly info). Errors should be visible first in case multiple types are to be displayed.

Switch away from basic log files

I would switch away from basic log files to database – even if it is just a simple SQLite one like DebugKit uses.
This way you can more easily navigate and filter through them.
Check out the DatabaseLog plugin which can do exactly that in just a few minutes of configuration.
For not just personal fun apps it is advised to look into a bit more sophisticated approaches, like using Monolog and services like NewRelic to write to. But for smaller projects it can be enough to have a small admin backend to filter through the logs, especially error and warning types.

Keep your controller code lean

That specifically includes all those verbose allow statements to make certain pages public:

use Cake\Event\Event; 
...
 	/**
	 * @return void
	 */
	public function beforeFilter(Event $event) {
		parent::beforeFilter($event);
		$this->Auth->allow('index');
	}

See TinyAuth which since recently allows to more cleanly declare this with a single INI file in your src/config folder.
The complete code above goes away for every controller 🙂

As long as not needed (custom dynamically made decisions based on session or alike) this kind of noise should be kept out of the controllers.
This makes it also possible to modify the public access to actions without the need to be a developer. The next step could be to make the input for TinyAuth database-driven and alike.

0.00 avg. rating (0% score) - 0 votes
 
No Comments

Posted by Mark in CakePHP