Coding Style Guidelines
PHP
Coding Style
Beginning with version 7, Concrete CMS has followed the PHP Framework Interoperability Group's PSR guidelines for code style.
For version 7 and version 8 follow PSR-2 For version 9 and above follow PER-CS
These standards dictate a number of things, including spaces vs. tabs, brace placement, method naming, and more. Please read it and adhere to it.
Tough Decision
Like a lot in life, coding standards are deeply personal. I myself strongly disagree with several decisions in this standard (and, to spare myself the inevitable arguments, I'll leave which these are to your imagination.) That said, there is a lot of good in here, and I think it can only help the ecosystem. So I, too, will bite the bullet.
We're in the same boat, people. Let's set sail!
Keeping Up With Development
While Concrete is being developed, some of the stuff done in the past might become deprecated or old convention. Please refer to the following document in order to keep your codebase up to date:
Deprecated Code Reference (ongoing)
File Naming and Location
From version 5.7 and onward, Concrete is adopting a modified naming standard based on PHP Framework Interoperability Group's PSR-4. Our built-in autoloader will automatically find classes that adhere to this system.
All core libraries adhere to PSR-4 with no modifications. What does that mean? That means that our namespaced core libraries like the Page class
\Concrete\Core\Page\Page
Can be found at
concrete/src/Page/Page.php
PSR-4 basically states that projects can map different namespace prefixes to arbitrary starting directories. That's what we're doing.
If you are writing code that belongs in the core source directory, you must adhere to PSR-4 as is. Classes must be capitalized, etc...
Modifications
For classes that do not exist within the src/ directory (such as block controllers, attribute controllers, etc...) We ask that you name your controllers with the same capitalization that PSR-4 requires, but you name your files with Concrete's classic lowercase + underscore method (snake_case). These files will be converted by CamelCasing those filenames on the fly.
What does that mean? The blocks directory and its contents still looks the same as Concrete before 5.7. For example, the Page List block's controller is still located at
concrete/blocks/page_list/controller.php
If you were strictly following PSR-4, you would need to name your class
controller
inside the name space
\Concrete\Block\page_list
This is ugly and causes other problems. So instead, when we request the class
\Concrete\Block\PageList\Controller
We first check to see if
concrete/blocks/PageList/Controller.php
exists (since that is the default PSR-4 autoloading behavior.)
If it does not (and it won't, for blocks, attributes, single page controllers, etc...) – then we uncamelcase using our own methods, starting at the item after blocks. Backslashes become directory separatores, and camelcasing becomes underscores.
concrete/blocks/page_list/controller.php
That is how Concrete 5.7 and later handles non-library classes.
Similarly, if a theme is using the namespace Application\Theme\MyNewTheme
, it will live in a directory named application/theme/my_new_theme
. Using the namespace Application\Theme\Theme2024
will live in application/theme/theme2024
.
Inversion of Control (IoC)
Concrete uses a concept called "inversion of control". Through this, we are able to access different classes centrally from one place which makes swapping or overriding specific functionality much easier for developers.
This makes the classes available throght the inversion of control container (IoC container) which is referenced as $app
in many places of the core. Through this class, you are able to initiate new classes easily using the $app->make()
and $app->build()
methods.
For example, if we want to access the database connection, we could call $app->make('Concrete\Core\Database\Connection\Connection')
. Or if we want to initiate a new instance of the logger class, we could call $app->build('Concrete\Core\Logging\Logger')
.
Define Class Dependencies In Constructor
Because of the IoC concept Concrete uses, we are able to take advantage of this to make our code cleaner. This makes it possible for developers to define the class dependencies in the class constructor and the container will resolve them automatically through the IoC making it easy for developers to get access to these classes and also to other developers to override them.
In the past this was not as straight forward because of which in the core and 3rd party code, we still see a lot of code using the $app
container class directly or even code using the old style of accessing it through facades (Core::
).
Here's an example of what some old style code would look like (Incorrect) and what it should look like (Correct).
Incorrect:
<?php
namespace Application\SomeStuff;
use Concrete\Core\Support\Facade\Application;
class MyClass
{
public function doSomething()
{
$app = Application::getFacadeApplication();
$db = $app->make('database')->connection();
if ($db->tableExists('Areas')) {
$this->doSomethingElse();
}
}
// ...
}
Correct:
<?php
namespace Application\SomeStuff;
use Concrete\Core\Database\Connection\Connection;
class MyClass
{
protected $db;
public function __construct(Connection $db)
{
$this->db = $db;
}
public function doSomething()
{
if ($this->db->tableExists('Areas')) {
$this->doSomethingElse();
}
}
// ...
}
With this type code, you will need to initiate your class through the IoC container for it to resolve the dependencies automatically:
<?php
$my = $app->build('Application\SomeStuff\MyClass');
$my->doSomething();
Existing Code
We have already taken care of moving files to support these updated conventions. For coding style (including spaces vs. tabs, braces, etc...) we will be modifying existing core code to support this code base as needed. This is not a high priority, but we will accept pull requests quickly that modify this code for us. As for now, most of the codebase has been converted to follow these conventions.
Important Note
If you submit a pull request for this type of cleanup, please keep each request to one file – for the sake of vetting your pull request. Please do not run the entire Concrete source through a cleanup tool and send that to us. We could do that ourselves ;-)
Templates (HTML)
There are a couple of general rules to follow with your presentational layer (templates, files containing HTML):
- Keep your presentational layer as clean as possible.
- Indent your HTML with spaces and use 4 spaces per indent. If you need to choose between indenting for PHP scope or for HTML scope, choose PHP scope.
- Assign as much of your variables outside of the presentational layer as possible (usually in controller)
- Separate repeating elements to their own elements.
- Keep logic out of the templates
- Minimize your use of PHP opening and closing tags, if you have several PHP statements in a row, just use a PHP block rather than a bunch of
<?php ... ?>
statements
Some of these points are clarified below.
Assigning Variables
Incorrect:
<?php defined('C5_EXECUTE') or die("Access Denied.");
$name = $object->getName();
$price = $product->getBasePrice() + $vatPercentage * $product->getPrice();
?>
<table>
<tr>
<td><?php echo t("Name") ?></td>
<td><?php echo $name ?></td>
</tr>
<tr>
<td><?php echo t("Price") ?></td>
<td><?php echo $price ?></td>
</tr>
</table>
Correct:
<?php
namespace Foo;
/**
* Description of the class
*/
class Controller
{
/**
* Description of this function
*/
public function view()
{
$this->set('name', $this->object->getName());
$this->set('price', $this->product->getBasePrice() + $this->vatPercentage * $this->product->getPrice());
}
}
<?php
defined('C5_EXECUTE') or die("Access Denied.");
// Template
<table>
<tr>
<td><?= t('Name') ?></td>
<td><?= $name ?></td>
</tr>
<tr>
<td><?= t('Price') ?></td>
<td><?= $price ?></td>
</tr>
</table>
Keep logic out of templates
Make sure your template code is as clean and understandable as possible. Keep logic out of the template and focus on the presentational aspect. In case you have repeatable elements in your template, please use the elements concept.
Incorrect:
<?php defined('C5_EXECUTE') or die("Access Denied."); ?>
<?php
function print_list_item($item) {
echo '<li>' . $item->getName() . '</li>;
}
?>
<p><?php echo t("Check out our items:") ?></p>
<ul>
<?php foreach ($items as $item) : ?>
<?php print_list_item($item); ?>
<?php endforeach; ?>
</ul>
<p><?php echo t("Check out our products:") ?></p>
<ul>
<?php foreach ($products as $item) : ?>
<?php print_list_item($item); ?>
<?php endforeach; ?>
</ul>
Correct:
<?php defined('C5_EXECUTE') or die("Access Denied.") ?>
<p><?= t("Check out our items:") ?></p>
<ul>
<?php
foreach ($items as $item) {
View::element('element_scope/list_item', ['item' => $item]);
}
?>
</ul>
<p><?= t("Check out our products:") ?></p>
<ul>
<?php
foreach ($products as $item) {
View::element('element_scope/list_item', ['item' => $item]);
}
?>
</ul>
<?php
// elements/element_scope/list_item.php
defined('C5_EXECUTE') or die("Access Denied.");
?>
<li><?= $item->getName() ?></li>
Generally the given example would be "too simple" case to extract the list item to its specific element file. It is just used as an example that if you have repeable elements, you can use Concrete elements.
Note
While this is the convention to aim for, the core itself is not perfect either. Concrete has existed almost 20 years and some of the codebase has lived along for a very long time. The system has many users and sometimes it might not be that simple to update a piece of code that is widely used.
JavaScript
Coding Style
Whether you're using typescript or javascript, in general follow standard JS with the following changes:
space-before-function-paren
disabled: Instead offunction () {}
, we usefunction() {}
to be more consistent with PHPindent 4
: Use 4 spaces instead of 2 to be consistent with existing code.
File Naming and Location
JavaScript file names should be all lowercase, with dashes the only non alphanumeric character allowed in the filenames before the extension.
Yes
js/color-picker.js
js/core/jquery-ui.js
No
js/ccm.whatever.js
js/MyFile.js
js/who_knows.js
JavaScript should go in the standard js/ directories only. Unless it is explicitly allowed by the core (e.g. view.js files in blocks.)
Existing Code
While the style of our JavaScript is improving massively in 5.7, many of our file still need cleanup, and our JS naming definitely doesn't currently support these standards. We will be handling the renaming of files as needed. But we would love to have help with the style of the code itself.
Important Note
Again, if you submit a pull request for this type of cleanup, please keep each request to one or just a few files – for the sake of vetting your pull request. Please do not run the entire Concrete source through a cleanup tool and send that to us. We could do that ourselves ;-)
CSS/SCSS/LESS
Coding Style
We follow sass guidelines when writing SCSS and we apply those same principles wherever possible to CSS and LESS. Prefer writing SCSS over straight CSS or LESS wherever possible.
File Naming and Location
CSS file names should be all lowercase, with dashes the only non alphanumeric character allowed in the filenames before the extension.
Yes
css/jquery-ui.css
css/redactor.css
css/conversations.css
css/jquery-rating.css
No
css/MyLib.css
css/_whozits.css
css/ccm.spellchecker.css
CSS should go in the standard css/ directories only. Unless it is explicitly allowed by the core (e.g. view.css files in blocks.)
Existing Code
Most of our CSS has been moved into LESS files as of 5.7. We will accept pull requests for minor syntax changes here and there, but we're generally pretty pleased with this cleanup so far, and it doesn't require too much.
The names of our CSS files definitely don't currently support these standards. We will be handling the renaming of files as needed. Please do not submit pull requests renaming core files.
Important Note
Again, if you submit a pull request for this type of cleanup, please keep each request to one or just a few files – for the sake of vetting your pull request.
Further Discussion
These decisions have not been made lightly, and they're probably going to upset some (including some core developers – gulp). But hopefully this can lead to a cleaner, more uniform Concrete source.
For more discussion on this, including any addendums, additions or fixes, please post in the Documentation Forum.
Deprecated Code Reference (ongoing)
This is a list of commonly used code that is deprecated, and their replacements. These functions may still work and exist now, but can be removed from future versions of Concrete CMS and thus cause problems in the future.
This is a work in progress. This list will change as new functions are found and new versions are released.
Note:
In the replacements you will sometimes find references to $app
. You can access the application in some controllers with $this->app
, or if it’s not available, retrieve it with:
$app = \Concrete\Core\Support\Facade\Application::getFacadeApplication();
Using $app
may result in faster code.
Class Aliases
Version 5.7 introduced class aliases to make some of the commonly used core code easily available for developers. This idea was mostly due to the old 5.6 architecture where this was very common. This has, however, caused more confusion than benefit since people are finding it hard to follow where the code originates from.
Therefore, class aliases have been deprecated and full namespaces should be used in the future. A full list of aliases can be found from the concrete/config/app.php file.
Incorrect:
<?php
namespace Application\SomeStuff;
use Area;
use Block;
use Page;
// etc.
class MyClass
{
// ...
}
Correct:
<?php
namespace Application\SomeStuff;
use Concrete\Core\Area\Area;
use Concrete\Core\Block\Block;
use Concrete\Core\Page\Page;
// etc.
class MyClass
{
// ...
}
Facades
Generally it is suggested to define your object's dependencies in the constructor rather than using the facades.
Incorrect:
<?php
namespace Application\SomeStuff;
use Concrete\Core\Support\Facade\Application;
class MyClass
{
public function doSomething()
{
$app = Application::getFacadeApplication();
$db = $app->make('database')->connection();
if ($db->tableExists('Areas')) {
$this->doSomethingElse();
}
}
// ...
}
Correct:
<?php
namespace Application\SomeStuff;
use Concrete\Core\Database\Connection\Connection;
class MyClass
{
protected $db;
public function __construct(Connection $db)
{
$this->db = $db;
}
public function doSomething()
{
if ($this->db->tableExists('Areas')) {
$this->doSomethingElse();
}
}
// ...
}
With this type code, you will need to initiate your class through the IoC container for it to resolve the dependencies automatically:
<?php
$my = $app->build('Application\SomeStuff\MyClass');
$my->doSomething();
Sometimes this might not be possible e.g. when modifying a class that already has existing constructor functionality. In those cases it is still OK to use facades.
Table of deprecated functions
This table represents a sample of commonly used but deprecated functions; it is not definitive.
class | method | replacement | example | note |
---|---|---|---|---|
Loader | Loader::db() |
$db = Database::connection(); or $db = $app->make('database')->connection(); /* @var $db \Concrete\Core\Database\Connection\Connection */ |
deprecated since 5.7 | |
Loader | Loader::helper() |
$pkgh = $app->make('/packages/' . $pkgHandle . '/helper/' . $service); or $h = $app->make('helper/' . $service); |
$th = $app->make('helper/text'); or $nh = $app->make('helper/navigation'); or $ps = $app->make('helper/form/page_selector'); or $al = $app->make('helper/concrete/asset_library'); or $color = $app->make('helper/form/color'); |
deprecated since 5.7 |
Loader | $form = Loader::helper(‘form’) |
In block/view.php : $form is already defined |
deprecated since 5.7 | |
Loader | Loader::packageElement() |
$ve = View::element($file, $args, $_pkgHandle); |
deprecated since 5.7 | |
Loader | Loader::element() |
$ve = View::element($file, $args, $_pkgHandle); |
deprecated since 5.7 | |
Loader | Loader::model() |
none | deprecated since 5.7 | |
Loader | Loader::library() |
none | deprecated since 5.7 | |
Loader | Loader::controller() |
in a page: Page::getPageController(); |
deprecated since 5.7 | |
BlockType | BlockType::installBlockTypeFromPackage() |
$bt = BlockType::installBlockType($handle, $pkg); |
deprecated since 5.7 | |
Database | Database::get() |
$db = Database::connection(); or $db = $app->make('database')->connection(); /* @var $db \Concrete\Core\Database\Connection\Connection */ |
deprecated since 5.7 | |
Database | $db->execute() |
$db->executeQuery($q, array($arguments)); or ($q instanceOf \Doctrine\DBAL\Statement): $q->execute($arguments); |
deprecated since 5.7 | |
Database | $db->GetOne() |
$res = $db->fetchColumn($q, array($arguments)) |
deprecated since 5.7 | |
Database | $db->GetAll() |
$res = $db->fetchAll($q, array($arguments)) |
deprecated since 5.7 | |
File | $f->isError() |
none, check for valid ID: (File::getByID() != null) |
returns false ; deprecated since 5.7 |
|
Controller | $this->redirect() |
Redirect::to($url)->send() |
||
PageController | PageController::isPost() |
$this->getRequest()->isPost() |
deprecated since 5.7 | |
PageController | PageController::post() |
$this->request->request->get() |
deprecated since 5.7 | |
PageController | PageController::get() |
$this->request->query->get() |
PageController::get() can still be used to fetch the variables that have been made available through PageController::set() . In case you are fetching HTTP request parameters, you should be using the replacement method insteead. deprecated since 5.7 (for HTTP GET parameters) |
|
Permissions | FilePermissions::getGlobal() |
$fp = new Permissions(FileSet::getGlobal()); |
deprecated since 5.7 | |
Permissions | TaskPermission() |
$tp = new Permissions(); |
deprecated since 5.7 | |
Request | Request::getInstance() |
$r = $app->make(\Concrete\Core\Http\Request::class); |
||
UserInfo | UserInfo::getByID() |
$ui = $app->make(\Concrete\Core\User\UserInfoRepository::class)->getByID(); |
Full list from source
Unfortunately this page can lag behind the status of the current Concrete core. From the linux command line, you can get a list of lines by navigating to the /concrete/ directory and running:
grep --include=*.php -rnw ./ -e "@deprecated"