Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Install and configure FormaLMS and DoceboCE
Post Reply
prashantt
Newbie
Posts: 5
Joined: Wed Jul 30, 2025 2:43 pm

Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by prashantt »

Hello,

I was in the process of manual installation of Forma LMS with AlmaLinux release 8.10 on PHP 7.4, Apache 2.4 , MYSQL 5.7 downloaded from :

https://sourceforge.net/projects/forma/ ... p/download

However while starting installation i.e visiting

http://example.com/manual/forma4019/install/

No output is being displayed . On turning display_errors = On via php.ini as well.

So to debug the issue i added debug code to get error in index.php i.e

Code: Select all

function get_error()
	{
		print_r(error_get_last());
	}

	register_shutdown_function('get_error');
So the following error is being displayed i.e

Code: Select all

Array ( [type] => 4 [message] => syntax error, unexpected 'static' (T_STATIC) [file] => /{{PATH}}/{{TO}}/{{FORMA_LMS}}/lib/Cache/Lang/LangCache.php [line] => 12 ) 
It seems that the code :

Code: Select all

public static function getInstance(?string $format = self::FORMAT_JSON, ?int $ttl = self::CACHE_TTL, ?string $namespace = null): static
return type declaration static is causing the issue. As per PHP official docs 'Support for the return only type static' was added in PHP 8.0

https://www.php.net/manual/en/language. ... ations.php

Are PHP requirements for FormaLMS changed ?.

I see PHP 7.4 listed in official docs for version 4.x i.e :

https://docs2.formalms.org/books/refere ... quirements

Could you please replicate this at your end on PHP 7.4 and resolve this?.

Awaiting your kind reply.
alfa24
Senior Boarder
Posts: 2292
Joined: Fri Nov 24, 2017 8:45 am

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by alfa24 »

Try with this /lib/Cache/BaseCache.php:

Code: Select all

<?php


namespace FormaLms\lib\Cache;

abstract class BaseCache
{
    public const FORMAT_PHP = 'php';
    public const FORMAT_JSON = 'json';
    public const FORMAT_MSGPACK = 'msgpack';
    public const FORMAT_IGBINARY = 'igbinary';

    protected string $cacheDir;
    protected string $namespace;
    protected array $memoryCache = [];
    protected string $cacheFormat;
    protected int $cacheTTL;
    protected static $instances = [];

    protected function __construct(
        string  $subDir,
        ?string $format = self::FORMAT_JSON,
        ?int    $ttl = null,
        ?string $namespace = null
    ) {
        $this->cacheDir = _files_ . '/cache/' . trim($subDir, '/') . '/';
        $this->namespace = $namespace ?? static::class;
        $this->cacheTTL = $ttl ?? 3600;
        $this->cacheFormat = $this->determineFormat($format);
        $this->ensureStructure();
    }

    public function __clone() {}
    public function __wakeup() {}

    public static function getInstance(
        ?string $format = 'php',
        ?int    $ttl = null,
        ?string $namespace = null
    ) {
        $class = static::class;
        $key = $class . '_' . ($format ?? 'default') . '_' . ($ttl ?? 'default');

        if (!isset(self::$instances[$key])) {
            self::$instances[$key] = new static(
                static::getSubDir(),
                $format,
                $ttl,
                $namespace
            );
        }

        return self::$instances[$key];
    }

    public static function resetInstances(): void
    {
        self::$instances = [];
    }

    abstract protected static function getSubDir(): string;

    protected function ensureStructure(): void
    {
        if (!is_dir($this->cacheDir) && !mkdir($this->cacheDir, 0755, true) && !is_dir($this->cacheDir)) {
            throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->cacheDir));
        }
    }

    protected function determineFormat(?string $format = null): string
    {
        $selectedFormat = strtolower(\FormaLms\lib\Get::cfg('cache_format', $format));

        switch ($selectedFormat) {
            case 'msgpack':
                return extension_loaded('msgpack') ? self::FORMAT_MSGPACK : self::FORMAT_JSON;
            case 'igbinary':
                return extension_loaded('igbinary') ? self::FORMAT_IGBINARY : self::FORMAT_JSON;
            case 'php':
                return self::FORMAT_PHP;
            case 'json':
            default:
                return self::FORMAT_JSON;
        }
    }

    public function getCacheFormat(): string
    {
        return $this->cacheFormat;
    }

    public function getTTL(): int
    {
        return $this->cacheTTL;
    }

    public function setTTL(int $ttl): void
    {
        $this->cacheTTL = $ttl;
    }

    public function get($key, $subKey, $type = 'default')
    {
        $cacheKey = $this->buildMemoryCacheKey($key, $subKey, $type);

        if (isset($this->memoryCache[$cacheKey])) {
            return $this->memoryCache[$cacheKey];
        }

        $file = $this->getCacheFile($key, $subKey, $type);
        if (!file_exists($file)) {
            return null;
        }

        try {
            $content = file_get_contents($file);
            if ($content === false) {
                return null;
            }

            $data = $this->unserialize($content);
            if (!is_array($data) || !isset($data['timestamp']) || !isset($data['data'])) {
                return null;
            }

            if ((time() - $data['timestamp']) > $this->cacheTTL) {
                @unlink($file);
                return null;
            }

            $this->memoryCache[$cacheKey] = $data['data'];
            return $data['data'];

        } catch (\Throwable $e) {
            return null;
        }
    }

    public function set($key, $subKey, $data, $type = 'default'): bool
    {
        try {
            $file = $this->getCacheFile($key, $subKey, $type);
            $cacheKey = $this->buildMemoryCacheKey($key, $subKey, $type);

            $this->memoryCache[$cacheKey] = $data;

            $dir = dirname($file);
            if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
            }

            $cacheData = [
                'timestamp' => time(),
                'data' => $data
            ];

            $content = $this->serialize($cacheData);
            if (file_put_contents($file, $content) === false) {
                return false;
            }

            if (function_exists('opcache_invalidate')) {
                opcache_invalidate($file, true);
                if ($this->cacheFormat === self::FORMAT_PHP) {
                    opcache_compile_file($file);
                }
            }

            return true;

        } catch (\Throwable $e) {
            return false;
        }
    }

    protected function serialize($data): string
    {
        switch ($this->cacheFormat) {
            case self::FORMAT_PHP:
                return sprintf("<?php\nreturn %s;", var_export($data, true));
            case self::FORMAT_JSON:
                return json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
            case self::FORMAT_MSGPACK:
                return msgpack_pack($data);
            case self::FORMAT_IGBINARY:
                return igbinary_serialize($data);
            default:
                throw new \RuntimeException("Unsupported cache format: {$this->cacheFormat}");
        }
    }

    protected function unserialize($content)
    {
        switch ($this->cacheFormat) {
            case self::FORMAT_PHP:
                return include $content;
            case self::FORMAT_JSON:
                return json_decode($content, true, 512, JSON_THROW_ON_ERROR);
            case self::FORMAT_MSGPACK:
                return msgpack_unpack($content);
            case self::FORMAT_IGBINARY:
                return igbinary_unserialize($content);
            default:
                throw new \RuntimeException("Unsupported cache format: {$this->cacheFormat}");
        }
    }

    protected function getCacheExtension(): string
    {
        switch ($this->cacheFormat) {
            case self::FORMAT_PHP:
                return '.php';
            case self::FORMAT_JSON:
                return '.json';
            case self::FORMAT_MSGPACK:
                return '.msg';
            case self::FORMAT_IGBINARY:
                return '.igb';
            default:
                return '.cache';
        }
    }

    protected function buildMemoryCacheKey($key, $subKey, $type): string
    {
        return sprintf('%s_%s_%s', $type, $key, $subKey);
    }

    protected function getCacheFile($key, $subKey, $type): string
    {
        return $this->cacheDir . $type . '/' . $key . '/' . $subKey . $this->getCacheExtension();
    }

    public function clear($key = null, $subKey = null, $type = null): void
    {
        if ($key === null && $subKey === null && $type === null) {
            $this->memoryCache = [];
        } else {
            $pattern = sprintf(
                '%s%s%s',
                $type === null ? '' : $type . '_',
                $key === null ? '' : $key . '_',
                $subKey ?? ''
            );

            foreach ($this->memoryCache as $cacheKey => $value) {
                if (strpos($cacheKey, $pattern) === 0) {
                    unset($this->memoryCache[$cacheKey]);
                }
            }
        }

        if ($key === null && $subKey === null && $type === null) {
            $this->clearDirectory($this->cacheDir);
            $this->ensureStructure();
            return;
        }

        if ($type !== null) {
            $typeDir = $this->cacheDir . $type;
            if ($key === null) {
                $this->clearDirectory($typeDir);
                return;
            }

            $keyDir = $typeDir . '/' . $key;
            if ($subKey === null) {
                $this->clearDirectory($keyDir);
                return;
            }

            $file = $this->getCacheFile($key, $subKey, $type);
            if (file_exists($file)) {
                unlink($file);
                if (function_exists('opcache_invalidate')) {
                    opcache_invalidate($file, true);
                }
            }
        }
    }

    protected function clearDirectory(string $dir): void
    {
        if (!is_dir($dir)) return;

        $files = glob($dir . '/*');
        foreach ($files as $file) {
            if (is_dir($file)) {
                $this->clearDirectory($file);
                @rmdir($file);
            } else {
                @unlink($file);
                if (function_exists('opcache_invalidate')) {
                    opcache_invalidate($file, true);
                }
            }
        }
    }

    public function getStats(): array
    {
        return [
            'format' => $this->cacheFormat,
            'ttl' => $this->cacheTTL,
            'memory_cache_size' => count($this->memoryCache),
            'directory' => $this->cacheDir,
            'extensions' => [
                'msgpack' => extension_loaded('msgpack'),
                'igbinary' => extension_loaded('igbinary'),
                'opcache' => function_exists('opcache_invalidate')
            ]
        ];
    }
}
And with this /lib/Cache/Lang/LangCache.php:

Code: Select all

<?php

namespace FormaLms\lib\Cache\Lang;

use FormaLms\lib\Cache\BaseCache;

class LangCache extends BaseCache
{
    private const CACHE_DIR = 'languages';
    private const CACHE_TTL = 86400;

    // Compatibile con PHP 7.4: rimosso `: static`
    public static function getInstance(?string $format = self::FORMAT_JSON, ?int $ttl = self::CACHE_TTL, ?string $namespace = null)
    {
        return parent::getInstance($format, $ttl, $namespace);
    }

    protected static function getSubDir(): string
    {
        return self::CACHE_DIR;
    }

    /**
     * Override ensureStructure to add specific directories for language cache
     */
    protected function ensureStructure(): void
    {
        $dirs = [
            $this->cacheDir . '/lists',
        ];

        foreach ($dirs as $dir) {
            if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
            }
        }
    }

    /**
     * Override getCacheFile for language-specific path structure
     */
    protected function getCacheFile($lang_code, $key, $type): string
    {
        switch ($type) {
            case 'translation':
                return $this->cacheDir . '/' . $lang_code . '/translations/' . $key . $this->getCacheExtension();

            case 'language':
                return $this->cacheDir . '/' . $lang_code . '/info' . $this->getCacheExtension();

            case 'language_list':
                return $this->cacheDir . '/lists/available' . $this->getCacheExtension();

            case 'module_list':
                return $this->cacheDir . '/lists/modules' . $this->getCacheExtension();

            default:
                return $this->cacheDir . '/' . $type . '/' . $lang_code . '/' . $key . $this->getCacheExtension();
        }
    }
}
Per supporto GRATUITO contattatemi in privato qui
prashantt
Newbie
Posts: 5
Joined: Wed Jul 30, 2025 2:43 pm

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by prashantt »

Hello @alfa24,

Thanks for your reply.

After changing code as mentioned in those files the installation starts fine but i face another issue i.e

After entering the correct database information, I get the following error i.e :

Can't connect to DB, please check inserted data

I tested on two of servers where i have MYSQL 5.7.44 and same issue occurs.

What could be the issue?. Could you please replicate ?.
Last edited by prashantt on Thu Jul 31, 2025 10:05 am, edited 1 time in total.
alfa24
Senior Boarder
Posts: 2292
Joined: Fri Nov 24, 2017 8:45 am

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by alfa24 »

What is the full name of installed mysql package? You can find it in phpinfo
Per supporto GRATUITO contattatemi in privato qui
prashantt
Newbie
Posts: 5
Joined: Wed Jul 30, 2025 2:43 pm

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by prashantt »

Hello @alfa24,

Following is the info of installed mysql package :
phpinfo.png
phpinfo.png (10.72 KiB) Viewed 155 times
mysqlnd.png
Please check.
alfa24
Senior Boarder
Posts: 2292
Joined: Fri Nov 24, 2017 8:45 am

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by alfa24 »

What about the mysql SERVER version? I mean the package installed on your server. Depending on your o.s. you have different commands to obtain the info.
Per supporto GRATUITO contattatemi in privato qui
prashantt
Newbie
Posts: 5
Joined: Wed Jul 30, 2025 2:43 pm

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by prashantt »

It's MYSQL community server 5.7.44. I have checked with mysql -v and here is the output :
mysql.jpg
Let me know if you need any further information.
alfa24
Senior Boarder
Posts: 2292
Joined: Fri Nov 24, 2017 8:45 am

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by alfa24 »

That should be ok, the issue is to be found in authentication probably, but one should be on your instance to help further.
In any case, this is no more a Forma related issue.
Per supporto GRATUITO contattatemi in privato qui
prashantt
Newbie
Posts: 5
Joined: Wed Jul 30, 2025 2:43 pm

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by prashantt »

Hello @alfa24,

Thanks for your good support . But is there any way to debug the same?. Cause I checked this now also with Alma Linux 9.6 and Server version: 8.0.43 MySQL Community Server - GPL. There too I get the same issue, even with the correct credentials being entered.
alfa24
Senior Boarder
Posts: 2292
Joined: Fri Nov 24, 2017 8:45 am

Re: Forma LMS 4.0.19 : Unable to start installation due to syntax error on PHP 7.4

Post by alfa24 »

Debug is up to you... As I wrote, I can't help further without being on your instance, sorry.
Per supporto GRATUITO contattatemi in privato qui
Post Reply