Use Custom Classes in Package

I try to use a custom class in a package but I fail. I read the documentation here: https://documentation.concretecms.org/9-x/developers/security/adding-custom-code-to-packages
But it doesn’t work when I follow this article.

My package’s name is “fhs”, the custom class is “Shortcuts”, stored in the file /packages/fhs/src/Shortcuts.php with namespace “Concrete\Package\Fhs”. I try to use it with “Concrete\Package\Fhs\Shortcuts”.

My first problem is, that I don’t understand the phrase
“Just add this boolean to your package’s controller.php:
$pkgAutoloaderMapCoreExtensions = true;”
in the documentation.

What does “add this boolean to your … controller.php” mean? As a property (public)? Or in the on_start()-function? Or elsewhere? I made it a public property and a variable in the on_start()-function but none of them worked.

I also tried to use my class with “Src” in the namespace, but no.

I also read this post:

and tried what was recommended there, but it didn’t work either.

There must be something I didn’t understand from the documentation. I remember sucessfully using my own classes in earlier versions of ConcreteCms (or rather Concrete5).

My code:

/packages/fhs/src/Shortcuts.php

<?php
namespace Concrete\Package\Fhs;
class Shortcuts {
    public static function render($content) {
        return $content;
    }
}

/packages/fhs/controller.php

...
use Concrete\Package\Fhs\Shortcuts;
...
    public function on_start() {
        $this->pkg = $this;
        $this->registerAssets();
        echo Shortcuts::render('Hollebolle');
...

OUTPUT:
Error
Class “Concrete\Package\Fhs\Shortcuts” not found

So lets say this is your package’s controller

<?php
namespace Concrete\Package\Fhs;

defined('C5_EXECUTE') or die('Access denied.');

use Concrete\Core\Package\Package;

class Controller extends Package
{
    protected $pkgHandle = 'fhs';
    protected $appVersionRequired = '9.0';
    protected $pkgVersion = '0.9';
    
    /**
     * @var bool disable legacy namespacing. Remove \Src from package namespace.
     */
    protected $pkgAutoloaderMapCoreExtensions = true;
    
    /**
     * let's map your "src" folder to "Fhs" so your class inside it uses "Fhs" as namespace.
     */
    protected $pkgAutoloaderRegistries = [
        'src => '\Fhs',
    ];

    public function getPackageName()
    {
        return t('FHS');
    }

    public function getPackageDescription()
    {
        return t('Does what FHS does');
    }

    /**
     * the rest of your package controller goes below
     **/
    
}

To be honest I’m not even sure both $pkgAutoloaderMapCoreExtensions and $pkgAutoloaderRegistries are required, but it works for me.

So now your class /packages/fhs/src/Shortcuts.php should be:

<?php
namespace Fhs;
class Shortcuts {
    public static function render($content) {
        return $content;
    }
}

You see the namespace is Fhs because that’s what we defined in the controller with $pkgAutoloaderRegistries

So in your package’s controller you will do:

use Fhs\Shortcuts;

If instead your class had been in /packages/fhs/src/SomeFolder/Shortcuts.php then the namespace would have been

namespace Fhs\SomeFolder;

I hope this helps

$pkgAutoloaderMapCoreExtensions is only necessary when you want to avoid \Src as part of the automatically generated namespace and you are not otherwise mapping the namespace with $pkgAutoloaderRegistries.

If you use $pkgAutoloaderRegistries , then the autoload mappings are going to be whatever is specified in that array.

Using $pkgAutoloaderMapCoreExtensions simply changes the automatic mapping from \Concrete\Package\Src\Fhs\Shortcuts to \Concrete\Package\Fhs\Shortcuts. But for this to work, you also have to move /packages/fhs/src/Shortcuts.php to /packages/fhs/src/Concrete/Shortcuts.php.

I’ve tried reworking that page to make this issue clearer. Let me know if I’ve missed anything or if there is still some confusion.

You mean:
If you use $pkgAutoloaderRegistries, then the autoload mappings are going to be whatever is specified in that array.

I did indeed. Thanks for catching that. A little too quick with the copy/paste there. I’ve updated my post above.

sorry for my late response as I get back to this project only now. Thanks for your solution! It works ver well.

Based on your solution I also made my package a composer library with its own autoloader etc. and it seems to work as well. There are only issues with the psr-version used by concreteCms which differs from the one composer installs.

Can you clarify this a bit more? If there’s an issue with the Composer-based install, let’s fix it.

I didn’t fully understand the problem, but I’ll try to explain it:

A have an instance of CCMS, but NOT installed with composer, just copied files and installed via frontend. No problems so far.

As I have to make extended customizations for the customer, using several libraries, I decided to create my package using composer. So in my package folder /webroot/packages/fhs/ I put the composer-file and everything.

The site still worked, but when I installed or updated the package, I got the following error:

Whoops \ Exception \ ErrorException (E_COMPILE_ERROR)
Declaration of Psr\Log\AbstractLogger::emergency(Stringable|string $message, array $context = ): void must be compatible with Psr\Log\LoggerInterface::emergency($message, array $context = )

I have no idea what that says, but a little search told me that there is some conflict between different psr versions - the one in my package and the one in CCMS’s core. I didn’t understand the cause, but I managed to work around with pointing the PSR-Log-Namespace to the CCMS’s folder in the composer.json. That fixed it for me. Looks like this:

"autoload": {
    "#": "Psr-log conflicts with c5's (older?) psr-log. To resolve this problem I point the namespace to the c5 folder! [cg 30.3.2025]",
    "psr-4": {
        "Infosophie\\Fhs\\": "src/",
        "Psr\\Log\\": "../../concrete/vendor/psr/log/Psr/Log/"
    }
},

So, you have installed two versions of a same dependency (one in Concrete and one in your package)?
I don’t think it’s a good idea…

I didn’t, composer did…

Yep, that’s a common issue when working with packages that come with their own composer.json file.

I wrote some time ago an utility script that lets you install in your package vendor folder only the dependencies that aren’t installed by Concrete: it’s called composerpkg, you can find it at GitHub - concrete5-community/cli: Unofficial concrete5 CLI Tool (see here for a description of it).

2 Likes

Thanks for that! I’m going on a bike tour next week, but I’ll definitely check it out later. It works as it is now, although I’m a big fan of clean installations, but you rarely have time for that…

@mlocati, I love that tool but is it still working? I thought it didn’t work beyond Concrete v8.

Yep, composerpkg should work without problems .
For example, it’s being used to create the distribution zip archives of Community Store (example execution) and some other projects.

1 Like

I’ve been crying over the loss of this wonderful tool ever since v9 came out, and you’re telling me it was all in vain!!!
I’m an idiot…

LOL, yep, it never went away :wink:

I’m back here because of a new project where I need my own classes – why bother remembering things the internet remembers… :wink: And I tried your instructions because they sounded plausible, but I noticed different behavior. Has ConcreteCms changed since then?

You show three methods:

  1. Create a custom namespace in the root namespace:
  • The files are located at: packages/mypackage/src/Folder/Myclass.php

  • The following property is set in the package controller:
    protected $pkgAutoloaderRegistries = [‘src’=>‘Mypackage’];

  • Namespace/Class Name: Mypackage\Folder\Myclass

  1. The “Legacy Method” with Src in the Namespace
  • The files are located at: packages/mypackage/src/Folder/Myclass.php

  • Nothing is set in the package controller (default behavior):

  • Namespace/Class Name: Concrete\Package\Mypackage\Folder\Myclass

  1. The Avoiding Legacy Namespace Method:
  • The files are located at: packages/mypackage/src/Concrete/Folder/Myclass.php

  • The following property is set in the package controller:
    protected $pkgAutoloaderMapCoreExtensions = true;

  • Namespace/Class Name:
    Concrete\Package\Mypackage\Folder\Myclass

Is this correct so far?

My experience is as follows:

Method 1 works, but I don’t like it because it occupies the root namespace and therefore potentially conflicts with other code. The whole point of namespaces is to prevent exactly that.

Method 2 doesn’t work!! Or I’ve done something wrong…

Method 3 works, but even without setting the property in the package controller.

Can you confirm this? Has ConcreteCMS changed in this regard since May 2025? Or am I missing something?

Avoid method 2. Its designed for back compatibility of code dating back to v7 and may not always be available.

Method 3 is generally for situations where you need to place a class in a core namespace, so is best avoided unless a core namespace is actually necessary.

So best to stick with method 1.

You are not limited to a single namespace or to the top level in that namespace. To continue your example, this should place the namespace \MyPackage\ classes in the folder /src/MyPackageClasses/

`$pkgAutoloaderRegistries = [‘src/MyPackageClasses’=>‘MyPackage’];`

The namespace cascades into all folders below that. So you can then have class files in folder src/MyPackageClasses/Utilities/ and refer to them using the namespace \MyPackage\Utilities.

Also methods might work or not work depending on what minimum version of Concrete you are requesting in the package. Controller. Concrete will do some stuff in the background depending on that.

Bookmarking this post - i struggle with namespaces so much ConcreteCMS and getting my stuff going the right direction…i’m sure it’s simple for some but still working on it. Thanks all for writing this post