Changelogs: Decoda v6.2.0

A new version of Decoda has been released, version 6.2.0. Please download the new tag or view the documentation. If you have any questions, be sure to send me an email or comment on this post. If you run into any problems, report an issue on the GitHub repository.

Version: 6.2.0
Tested On: PHP 5.4.3, Composer
Requires: PHP 5.3, Composer
Commit Hash: e90e79fddb88aff3928eb74018594b5717080e1b
Changes:

  • Added a newline to line break conversion setting lineBreaks [#48]
  • Added an onlyTags setting to filters that only allow tags and no text nodes as direct descendants
  • Added [*] list item tag to ListFilter (does not support nested lists)
  • Changed utility methods to public from protected
  • Improved newline normalization

Updating Composer in Travis

If you're using Composer in Travis, it's a good idea to update Composer before each build. If you do not update, you may receive warnings like the following.

Warning: This development build of composer is over 30 days old. It is recommended to update it by running "/home/travis/.phpenv/versions/5.4.13/bin/composer.phar self-update" to get the latest version.

It seems the Travis environment isn't completely reset between builds and jobs. Tests should always be ran using the latest version of Composer, so place a selfupdate in before_script.

before_script:
  - composer selfupdate
  - composer install

Changelogs: Uploader v4.3.0

A new version of Uploader has been released, version 4.3.0. Please download the new tag or view the documentation. If you have any questions, be sure to send me an email or comment on this post. If you run into any problems, report an issue on the GitHub repository.

Version: 4.3.0
Tested On: PHP 5.4.3, CakePHP 2.3.6, Composer
Requires: PHP 5.3, CakePHP 2.3, Composer
Commit Hash: 6bcd77330c5e7a4ce62f60d633b19dfba05e6a50
Changes:

  • Includes changes from previous minor versions
  • Updated Transit to v1.4.0
  • Added transportDir setting for uploads and transforms which allow custom transport directory paths [Issue #125]
  • Added fit transformer that allows for fitting an image to a certain size while applying a background fill
  • Added 4th argument to AttachmentBehavior.deleteFiles() to circumvent database hit
  • Reversed move and rename logic so transformed files do not conflict
  • Fixed bug where empty file paths trigger error on deletion [Issue #126]

Prism PHP Support

If you follow me on GitHub you may have noticed that I forked and committed quite a bit to the wonderful Prism JS project by Lea Verou. If you don't know what Prism is, it's a lightweight syntax highlighter written in JavaScript. It's what powers the syntax highlighting for my code blocks.

My major contribution to the project was full PHP highlighting support (including 5.3 and 5.4), as well as some improvements to the C-Like language and the inclusion of the CSS extras language. Take a look at the PHP snippet below which is taken from my Titon\Model project.

/**
 * @copyright	Copyright 2010-2013, The Titon Project
 * @license		http://opensource.org/licenses/bsd-license.php
 * @link		http://titon.io
 */
namespace Titon\Model;
use Titon\Common\Base;
use Titon\Common\Traits\Attachable;
use Titon\Model\Relation;
class Model extends Base {
	use Attachable;
	protected $_config = [
		'connection' => 'default',
		'table' => '',
		'prefix' => '',
		'primaryKey' => 'id',
		'displayField' => ['title', 'name', 'id'],
		'entity' => 'Titon\Model\Entity'
	];
	public function addRelation(Relation $relation) {
		$this->_relations[$relation->getAlias()] = $relation;
		$this->attachObject([
			'alias' => $relation->getAlias(),
			'class' => $relation->getModel(),
			'interface' => 'Titon\Model\Model'
		]);
		return $relation;
	}
	public function createTable(array $options = [], $temporary = false) {
		$schema = $this->getSchema();
		$schema->addOptions($options + [
			Dialect::ENGINE => 'InnoDB',
			Dialect::CHARACTER_SET => $this->getDriver()->getEncoding()
		]);
		return (bool) $this->query(Query::CREATE_TABLE)
			->attribute('temporary', $temporary)
			->schema($schema)
			->save();
	}
	public function getPrimaryKey() {
		return $this->cache(__METHOD__, function() {
			$pk = $this->config->primaryKey;
			$schema = $this->getSchema();
			if ($schema->hasColumn($pk)) {
				return $pk;
			}
			if ($pk = $schema->getPrimaryKey()) {
				return $pk['columns'][0];
			}
			return 'id';
		});
	}
}

The language also supports seamless integration with HTML markup. The snippet below is taken from my Titon\Debug project.

<div class="titon-debug">
	<div class="debug-head">
		<abbr title="<?php echo $file; ?>" class="debug-file">
			<?php echo self::parseFile($file) . ':'; ?><!--
			--><span class="debug-line"><?php echo $line; ?></span>
		</abbr>
	</div>
	<?php foreach ($vars as $var) { ?>
		<div class="debug-output">
			<?php if (isset($dump)) {
				echo self::_renderTemplate('table', array('value' => $var));
			} else { ?>
				<pre><code><?php echo \Titon\esc(print_r($var, true)); ?></code></pre>
			<?php } ?>
		</div>
	<?php } ?>
</div>

I hope you all find use in this contribution! Feel free to report any issues or suggestions to me.

Persisting data through legacy routes

Usually when a website is migrated to a new version or system, old URLs will break. To circumvent this problem, legacy URLs are redirected to their new destination through some kind of router. CakePHP offers this functionality through Router::redirect() and the RedirectRoute class. But how do you persist URL parameters (like IDs) to the new URL? It's pretty simple.

When I launched the new version of this site, I had to setup legacy routes for blog posts. The old URLs were structured as so blog/read/[id]/[inflected_title], where the new ones are structured like blog/read/[slug]. From the looks of it, no parameters are the same, so this will require a middleman controller. First, I need to map the redirect using the persist option (this is crucial).

Router::redirect('/blog/read/:id/*', array('controller' => 'jump', 'action' => 'blog'), array(
	'id' => '[0-9]+',
	'pass' => array('id'),
	'persist' => array('id')
));

This route will redirect the old URL to JumpController::blog($id) while passing the ID from the URL as the first argument. The JumpController is used to power my tiny URLs — so it now serves 2 purposes. Next up is defining the action logic.

public function blog($id = null) {
	if ($blog = $this->Entry->getById($id)) {
		$this->redirect(array('controller' => 'blog', 'action' => 'read', $blog['Entry']['slug']), 301);
	}
	throw new NotFoundException();
}

When the action is called, the code attempts to find a blog entry that matches the ID. If an entry is found it will redirect with a 301 (Moved Permanently), else it throws a 404 (Not Found). That's all there is too it!

Changelogs: Uploader v4.2.0

A new version of Uploader has been released, version 4.2.0. Please download the new tag or view the documentation. If you have any questions, be sure to send me an email or comment on this post. If you run into any problems, report an issue on the GitHub repository.

Version: 4.2.0
Tested On: PHP 5.4.3, CakePHP 2.3.1, Composer
Requires: PHP 5.3.3, CakePHP 2, Composer
Commit Hash: 7164f34197bcf8844f880af714929eb9efbdfaec
Changes:

  • Updated Transit requirement to 1.3
  • Added a rotate transformer that accepts a degrees setting
  • Added a exif transformer that fixes orientation and strips Exif data (should be applied first with self)
  • Added a defaultPath setting to transforms

Wordpress: a fractal of bad design

Everyone knows about WordPress, and everyone knows WordPress is just awful. If you happen to think WordPress is awesome, hopefully your mindset will change by the end of this post. I'll just state this now — I hate WordPress. I despise its popularity but I understand why it is so, I want to shoot myself when I look at the source code, and I try my hardest to avoid any WordPress related projects. But the worst problem of all is it breeds ignorance (e.g., "WordPress developers"), promotes backwards standards and implements inefficient design patterns.

Inspired by eevee's post on PHP: a fractal of bad design, this post will attempt to do the same about WordPress. Any point eevee made about PHP, double that for WordPress. I'll try my best not to bash everything, but it's really hard not to. I've literally revised this post 4 times to tone it down.

I've worked with WordPress through the years, even way back in 1.x days. I recently had to maintain a few 3.5 and 3.6 installs for my previous employer, as well as interact with their VIP service (but that's a whole topic in itself). I've written plugins, widgets, modified the admin, yadda yadda. I'd say I have a good grasp on WordPress as a whole, and that's why I'm writing this post. I always get asked by other developers why I hate WordPress, so here's a definite list of the largest problems with the WordPress codebase, but I'm also not the only one who thinks so.

Before I begin, I would like to state that popularity does not equate to quality. Stop using that as an excuse to use WordPress.

Please read the post before bashing me or trying to defend. If I can tell your comment was posted before reading it, I will delete it, else have at it!
Globals variables are good right?

No, they are bad and should rarely be used. WordPress uses them everywhere for everything; the loop for example. The problem with globals is that they are mutable, which permits them to be modified and overwritten, which in turn will cause problems in other unrelated areas of the application. Another issue is determining the original source location, as it's near impossible to deduce which part of the application created the variable nor what overwrote it during the interpretation cycle. I could easily disrupt the loop by writing the following code:

global $post;
$post = null;

And voilĂ , the whole application is useless. Honestly, I cannot wrap my head around the thought process on thinking this was a good idea. Globals (whether variable or state related) have a use case, WordPress obviously doesn't know which case that is.

Is database abstraction really necessary?

Yes, it definitely is. WordPress has no concept of models or entities (or very poor ones like WP_Post), nor does it use an ORM or ActiveRecord type of pattern. All database access is divided up into separate query objects, like WP_Query and WP_User_Query, all of which have insane amounts of inefficient logic to handle paging, filtering, sanitizing, relations, etc. To top it all off, each time a query is made it modifies a global object (refer to the point above), wtf? The codebase provides functions like query_posts() and get_posts(), all of which call WP_Query internally. Like honestly, why would a query need to be global?

function query_posts($query) {
	$GLOBALS['wp_query'] = new WP_Query();
	return $GLOBALS['wp_query']->query($query);
}

Database queries are used on a case by case basis, they shouldn't persist. Each query class and function may do something slightly different, but redundancy much? This problem would not exist if a proper DBAL was in place. WordPress does have a wpdb class that tries to mimic a DBAL, but falls short.

The biggest issue (in my opinion) with their system is that it doesn't allow for custom database tables. WordPress for some reason wants to store any and all data in their tables, most of which is handled through custom post types, meta tables, or options tables. One could create custom database tables through a series of actions and callbacks, but there would be no admin interface, how stupid is that? Furthermore, there is no direct database access since WordPress doesn't support an abstraction layer (this again?), so you're stuck with literal PDO and MySQL calls.

Throwing everything in a custom post type or a taxonomy is not the solution, it is the problem.
At least routing is done with mod_rewrite!

Using mod_rewrite isn't a bad thing, but modifying mod_rewrite rules through the PHP layer is. WordPress updates an .htaccess file anytime the core system or a plugin adds or modifies rewrite rules — only after you click a generate permalinks button within the admin (a source of many debugging headaches). Routing libraries like Horde, Symfony and even my own Titon have been a concept around for a very long time, so I'm not going to really delve into them. However, there's many issues with WordPress's routing system, too granular for me to get into, but most if not all of the issues would be solved by a decent PHP layered router.

All of the "necessary" functions like is_page(), is_single(), and is_category() would be obsolete as the router would handle all of the mapping, scoping, parameters, and arguments. For an idea on how bad rewriting is, just look at the docs.

What about the file and namespacing architecture?

I'm on the edge about this issue, seeing as how the WordPress codebase is quite old. Since WordPress was released 10 years ago (yeah, sadly), the concepts of MVC and proper data modeling weren't widely known or used, so the default PHP standard of laying out files was implemented. This primarily refers to themes in that each page is represented by a corresponding PHP file — index.php, archive.php, single.php, etc — instead of routing (point above) to a class instance that represents the state and behavior of a page. So for legacy reasons, I will leave this as a non-issue, but mention it anyways.

However, the rest of the codebase is poorly architect-ed. The first problem is that most functionality is handled through global functions (which is bad, point above) and not packaged into classes. Why things should be packaged into classes or namespaces is already a pretty common standard, so I won't get into that. To continue, WordPress has been slowly adding more classes with each version, like WP_Http and WP_Object_Cache, so kudos to them, but the organization of files and classes on the file system is still rather poor.

Every class and function can be found in wp-includes, which attempts to organize them into some random file based on their behavior, functionality, and scope, so at least they tried?

Well even with a poor file system, the templating works fine

Heh, templating in WordPress? What. This was the worst part of integrating any theme, by far. All I can do is face palm at the fact that WordPress has no templating system. But PHP itself is a templating language? Well yeah, but it's not utilized in a way where it can be considered one. There is no template hierarchy, no layouts, no wrappers, no re-usable elements (also called partials), no automatic escaping, no helper libraries, no compilation or caching (albeit a proper one), no proper data model, and the list goes on and on. I've seen too many projects where I would see the same few lines of code repeated over and over 30 times... when this problem could of been solved by a single re-usable template.

WordPress has been around for 10 years. Smarty has been around for 11 years. Twig has been around for 3 years. There's not a single excuse why a third-party library, or even a custom built library isn't part of WordPress core. The very fact that templates require get_header(), get_sidebar(), and get_footer() is pathetic.

The action and filter hooks seem pretty powerful

Yes, powerfully dangerous. Let's ignore the fact that actions and filters are nearly the same exact thing, just with different verbiage.

function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
	return add_filter($tag, $function_to_add, $priority, $accepted_args);
}

Let's also ignore the fact that these are events (and should be called as such), or that the propagation cycle cannot be stopped. Even Symfony, Zend and my own Titon have a proper event handling system.

The major problem is that these hooks are used anywhere and everywhere, which allows every little part of WordPress to be altered or modified. Any part of the application, whether it be a plugin, a library, or user implemented, can set a hook that modifies data, alters logic, or causes a problem later on in the execution cycle. Another major problem is that arguments are filtered (refer to $accepted_args above) unless strictly defined otherwise. In what case would I not want all arguments? None. Both of these issues lead to a debugging nightmare.

The hook system would be used scarcely if WordPress was architect-ed correctly. Hooks shouldn't exist in this magnitude.

What about error handling?

Get this, instead of using the built-in PHP error and exception handling, WordPress wrote their own! Ridiculous right? Instead of throwing exceptions and allowing the developer to catch and handle it (like any sane application), WordPress returns (not throws) an instance of WP_Error — a class that implements messages and codes, you know, like an exception.

What's even more ridiculous is that some functions can toggle a flag that either returns the WP_Error or returns a falsey value instead (I'm looking at you wp_insert_post() and wp_update_post()). This feature is so terrible, it speaks for itself.

But plugins and themes are where the magic happens

Take everything I said above, and everything I will continue to say, and double that. That's what those are. With their awful and inconsistent file architecture, no consistency between each other, their abuse of actions and filters, their inefficient database access, their backwards standards, and overall shoddy code, plugins and themes are an even greater problem then WordPress itself. When a problem arises from either core or another plugin, they believe throwing another plugin at it will fix the issue. The fact that so many plugins exist for such little things, and how different themes are from each other, just shows how poorly written WordPress is to begin with.

Anything else?

I haven't written about everything that irks me yet, so I will summarize a few more points below. I'm positive there are many more issues, but these should suffice for now. Feel free to send me more and I will append them to this post.

  • Class Abstraction and Inheritance - Very poorly done or simply not done at all.
  • Debugging Tools - There are no WordPress specific debugging tools to solve the problems listed above. And before you say anything, the Debug Bar is not a tool; displaying environment data isn't debugging.
  • Backwards Standards - Instead of following the rest of the PHP world with PSR or PEAR standards, WordPress decided to write their own standard which is quite opposite.
  • Asset Handling - JavaScript and CSS is handled through some kind of queuing and packaging system. Yet in the end, the source is just littered with script blocks and link tags. No compression, no concatenation, no optimization.
  • Deprecated Code - WordPress triggers deprecation errors for outdated code. Either it should be exceptions or they should require developers to do a hard migration.
  • Echo vs Return - Functions should never echo code, it should always return. This isn't as bad as the WordPress 1.x days, but it's still on my list of issues.
  • Pseudo Cron Jobs - Instead of using actual cron jobs, WordPress implemented a PHP layered cron job system. Which basically saves callback references in the database and triggers them from the PHP request at certain events. Lol what?
  • Over-zealous Escaping - Let's throw escaping and filtering functions around everything!
  • Security & Uptime - My friends over at MediaTemple would disagree. Most of their support issues or server problems are WordPress related.
  • Option Autoloading - The "autoload" of wp_options for the core and all plugins is extremely inefficient and slow.
  • Data Storing - Throwing all post related data in wp_postmeta or non-post related data in wp_options. You'll eventually end up with MBs of data when in actuality it should be far less. Can become terribly slow to sort through and read from.
  • Featured Images - One can define multiple image sizes for the featured image. However, all of those sizes are generated regardless of which ones you want, with no way to choose.
  • Menus - Creating hierarchical menus work well enough. But trying to render the menus that veer from the original implementation is complicated. What is this terrible Walker class? Give me templates.
So what's the solution?

WordPress has dug themselves into a pretty big hole, one that's not possible to get out of unless they do a complete rewrite of the system. It's been 10 years, and honestly, I don't ever see that happening. They continue to shoot themselves in the foot by expanding the system outside the scope of a blog, ala trying to make it a CMS. So what are the alternatives?

Personally, I'm pretty excited for Ghost, a blogging platform built in Node.js. Besides using Node.js as the backend, they utilize Handlebars for templating, SQLite for database storing, NPM for packaging, and localization with Polyglot.js. Already 5 reasons why it would be better than WordPress.

However, here are a few systems that are currently available. Craft is a popular up and coming CMS powered by the Yii framework. Serendipity also seems to be a popular PHP powered alternative. MovableType if you don't mind using Perl instead of PHP. Or SquareSpace if you prefer using a service.

Secondly, how about learning more of the language you are developing in? Throw together a few Laravel bundles, Symfony bundles, Titon modules, or one of the many other PHP frameworks out there. Honestly, will someone do this already and wipe WordPress off the map?

Don't let WordPress breed you into an incompetent developer.

Plugin loading quirks

On a daily basis I will receive emails pertaining to my Admin plugin, Utility plugin or my Forum plugin. Most of the time the problem they are having is related to plugin loading, and the failure to bootstrap or apply routes. But loading plugins is easy! One would think so, it does look pretty straight forward, but there are some weird cases where loading just does not work as you would assume.

In my documentation, I state that plugin loading should be done after CakePlugin::loadAll(), for the reasons listed below.

Using loadAll()

By default, CakePlugin::loadAll() does not include bootstrap or routes files. Passing an array with bootstrap and routes set to true will include them, but any plugin that does not have those files will throw missing errors. A setting ignoreMissing was added in CakePHP 2.3.0 to solve this. This approach works correctly, but not for versions below 2.3.0.

CakePlugin::loadAll(array(
	array('bootstrap' => true, 'routes' => true, 'ignoreMissing' => true)
));
Calling loadAll() last

One would think that calling CakePlugin::loadAll() after other plugins would work normally, but that's wrong. When called this way, previously loaded routes will be reset. This happens because loadAll() cycles through each plugin and re-loads them (even if a plugin was already loaded), and since both bootstrap and routes are false by default (point above) in loadAll(), the values get reset. Honestly, I believe this to be a bug in CakePHP, so when I have time I will track it down. Currently, this approach does not work correctly.

CakePlugin::load('Utility', array('bootstrap' => true, 'routes' => true));
CakePlugin::load('Admin', array('bootstrap' => true, 'routes' => true));
CakePlugin::load('Forum', array('bootstrap' => true, 'routes' => true));
CakePlugin::loadAll();
Calling loadAll() first

If calling it last doesn't work correctly, then calling it first will. By calling CakePlugin::loadAll() first, all the problems listed in the point above are invalid and plugin loading works correctly. Both routes and bootstrapping successfully triggers. This approach works correctly.

CakePlugin::loadAll();
CakePlugin::load('Utility', array('bootstrap' => true, 'routes' => true));
CakePlugin::load('Admin', array('bootstrap' => true, 'routes' => true));
CakePlugin::load('Forum', array('bootstrap' => true, 'routes' => true));
Passing an array to load()

Many don't know this, but an array of plugins can be passed to CakePlugin::load(). I tried many different combinations with no failures. This approach works correctly.

CakePlugin::load(array(
	'Uploader',
	'Utility' => array('bootstrap' => true, 'routes' => true),
	'Admin' => array('bootstrap' => true, 'routes' => true),
	'Forum' => array('bootstrap' => true, 'routes' => true)
));
CakePlugin::load(
	array('Utility', 'Admin', 'Forum'),
	array('bootstrap' => true, 'routes' => true)
);
How did you debug this?

It was rather easy. In Config/bootstrap.php I would attempt different variations of plugin loading to test the outcome. In my controller I had a few debug() statements that spit out Configure and Router information to verify that loading was working.

debug(App::objects('plugin'));
debug(Configure::read());
debug(Router::$routes);

Changelogs: Uploader v4.1.0

A new version of Uploader has been released, version 4.1.0. Please download the new tag or view the documentation. If you have any questions, be sure to send me an email or comment on this post. If you run into any problems, report an issue on the GitHub repository.

Version: 4.1.0
Tested On: PHP 5.4.3, CakePHP 2.3.1, Composer
Requires: PHP 5.3, CakePHP 2, Composer
Commit Hash: f2cad62f01c8aedc262692a54ea5d5ed272f4224
Changes:

  • Includes changes from 4.0.12 - 4.0.15
  • Updated Transit requirement to 1.2
  • Updated extension parsing to extract the value from the filename
  • Updated mimetype parsing to extract the value from multiple sources: file -b --mime command, then fileinfo, then $_FILES['type']
  • Fixed a bug where beforeUpload(), beforeTransform() and beforeTransport() callbacks were not being triggered for record deletions
  • Fixed a bug where $this->data is not available within callbacks

Changelogs: Forum v4.0.0

A new version of Forum has been released, version 4.0.0. Please download the new tag or view the documentation. If you have any questions, be sure to send me an email or comment on this post. If you run into any problems, report an issue on the GitHub repository.

Version: 4.0.0
Tested On: PHP 5.4.3, CakePHP 2.3.1, Composer
Requires: PHP 5.3, CakePHP 2.3, Composer
Commit Hash: bcea4ec25587acf89af064463088ba5eb981e8e3
Changes:

  • Replaced the custom ACL system with CakePHP's ACL
  • Replaced the admin system with the Admin plugin
  • Replaced the reporting system with the Admin plugin reporting system
  • Replaced the logging system with the Admin plugin logging system
  • Integrated the new Utility 1.4 features
  • Integrated the TreeBehavior where applicable
  • Rewrote the InstallShell and UpgradeShell
  • Removed the Profile model (the old fields should now be part of the users table)
  • Removed Spanish and Swedish locales (too many strings changed)
  • Removed user profiles (all user related code should be handled outside of the forum)
  • Added a ForumUser model that extends from the base User model
  • Added counter caching for topics and posts for the User model
  • Added counter caching for poll votes and options