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: