Adding Custom Code Libraries in a Package
Packages will often need to include custom PHP code for some of their functionality. Concrete CMS's implementation of autoloading provides a few ways of keeping custom classes names attractive and easy to work with.
Structuring Your Package
Consider a scenario where your company, PortlandLabs, develops a product named Statistics. A logical namespace for your add-on's custom code would be PortlandlabsStatistics
. Within this namespace, you might create a class PageListener
to handle the collection of page view statistics.
Directory Structure:
packages/
└── portlandlabs_statistics/
├── controller.php
└── src/
└── Page/
└── PageListener.php
All of the autoloaded classes will be located under the src/
directory. Note: everything under the src/
directory must match the class casing exactly for autoloading to work correctly.
Defining the Namespace and Class
In PageListener.php
, define the namespace and class as follows:
<?php
namespace PortlandlabsStatistics\Page;
class PageListener {
public function __construct() {
// ...
}
}
The class name here is succinct and easy to remember, but for Concrete to autoload it, we need to tell it where to find the class by adding it to an autoloader registry.
Autoloading Custom Classes
To enable Concrete to autoload your custom classes, specify the autoloader registry in your package's controller.php
:
<?php
namespace Concrete\Package\PortlandlabsStatistics;
use Concrete\Core\Package\Package;
class Controller extends Package {
protected $pkgHandle = 'portlandlabs_statistics';
protected $appVersionRequired = '9.0';
protected $pkgVersion = '1.0';
protected $pkgAutoloaderRegistries = [
'src' => 'PortlandlabsStatistics',
];
public function getPackageName() {
return t('PortlandLabs Statistics');
}
public function getPackageDescription() {
return t('Manages widgets for FooCorp.');
}
public function on_start() {
$listener = new \PortlandlabsStatistics\Page\PageListener();
$listener->addListeners();
}
}
Utilizing Custom Classes in Other Package Components
Suppose you have another class that reports the accumulated statistics for a page. When referencing your custom classes elsewhere in your package, ensure you include the appropriate use
statement
<?php
use PortlandlabsStatistics\Page\PageStats;
$stats = new PageStats();
echo $stats->getStats($page);
Common Pitfalls and Solutions
Namespace and Directory Alignment: Ensure that the namespace declaration in your PHP files matches the directory structure and case. For instance, a class located at
packages/portlandlabs_statistics/src/Page/PageListener.php
and mapped toPortlandlabsStatistics
as above should have the namespace ofPortlandlabsStatistics\Page\PageListener
.Autoloader Configuration: Verify that the protected
$pkgAutoloaderRegistries
class property incontroller.php
correctly maps the directory to the namespace.Class Naming Conventions: Adhere to PSR-4 standards, using CamelCase for both directory names and class names within the
src/
directory.
Example: Implementing a Custom Helper Class
Suppose you want to create a helper class Summary
within your package.
Directory Structure:
packages/
└── portlandlabs_statistics/
├── controller.php
└── src/
└── Summary.php
Summary.php:
<?php
namespace PortlandlabsStatistics;
class Summary {
public static function getSummary($path) {
// implement summary
}
}
Using Summary
in controller.php
:
<?php
namespace Concrete\Package\PortlandlabsStatistics;
use Concrete\Core\Package\Package;
use PortlandlabsStatistics\Summary;
class Controller extends Package {
// ...
public function report($page) {
return Summary::getSummary($page);
}
}
Avoiding Legacy Namespacing
Another complementary approach is to modify Concrete's traditional expectations about autoloading paths. Concrete historically automatically generates namespaces based on the src/
directory. For example, if you want to include a custom Captcha library in your package, you'll normally need to make sure a file is present at packages/your_package/src/Captcha/CustomCaptchaController.php
and its class is automatically generated as Concrete\Package\YourPackage\Src\Captcha\CustomCaptchaController
.
To prevent Concrete from expecting classes under the \Src
namespace, set the protected $pkgAutoloaderMapCoreExtensions
class property to true
in your package controller and move the newly automapped src/
files from packages/your_package/src
to packages/your_package/src/Concrete
. This disables legacy namespacing and nicer namespaces.
For example, in your controller, you set $pkgAutoloaderMapCoreExtensions
to true
:
<?php
namespace Concrete\Package\YourPackage;
use Concrete\Core\Package\Package;
class Controller extends Package {
// ...
protected $pkgAutoloaderMapCoreExtensions = true;
// ...
}
From there on, all files found in the src/
directory in your package no longer need to have \Src
in their namespace. Your custom captcha controller's class would be
namespace Concrete\Package\YourPackage\Captcha;
class CustomCaptchaController {
}
And the file system would look like: packages/your_package/src/Concrete/Captcha/CustomCaptchaController.php
.