Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Psr16Cache;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class CacheProvider provides a caching mechanism using adapters, it provides both PSR-6 and PSR-16.
*/
abstract class AbstractCacheProvider implements CacheProviderInterface
{
private ?AdapterInterface $adapter = null;
private ?Psr16Cache $psr16 = null;
public function __construct(
private CoreParametersHelper $coreParametersHelper,
private ContainerInterface $container,
) {
}
abstract public function getCacheAdapter(): AdapterInterface;
public function getSimpleCache(): Psr16Cache
{
if (is_null($this->psr16)) {
$this->psr16 = new Psr16Cache($this->getCacheAdapter());
}
return $this->psr16;
}
public function getItem($key): CacheItem
{
return $this->getCacheAdapter()->getItem($key);
}
public function getItems(array $keys = []): iterable
{
return $this->getCacheAdapter()->getItems($keys);
}
public function hasItem($key): bool
{
return $this->getCacheAdapter()->hasItem($key);
}
public function clear(string $prefix = ''): bool
{
return $this->getCacheAdapter()->clear();
}
public function deleteItem($key): bool
{
return $this->getCacheAdapter()->deleteItem($key);
}
public function deleteItems(array $keys): bool
{
return $this->getCacheAdapter()->deleteItems($keys);
}
public function save(CacheItemInterface $item): bool
{
return $this->getCacheAdapter()->save($item);
}
public function saveDeferred(CacheItemInterface $item): bool
{
return $this->getCacheAdapter()->saveDeferred($item);
}
public function commit(): bool
{
return $this->getCacheAdapter()->commit();
}
protected function cacheAdapterFactory(string $parameter): AdapterInterface
{
if (null === $this->adapter) {
$service = $this->coreParametersHelper->get($parameter);
$this->adapter = $this->container->get($service);
}
return $this->adapter;
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache\Adapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
class FilesystemTagAwareAdapter extends TagAwareAdapter
{
public function __construct(?string $prefix, int $lifetime = 0, ?string $directory = null)
{
$prefix = 'app_cache_'.$prefix;
parent::__construct(
new \Symfony\Component\Cache\Adapter\FilesystemAdapter($prefix, $lifetime, $directory),
new \Symfony\Component\Cache\Adapter\FilesystemAdapter($prefix.'_tags')
);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache\Adapter;
use Mautic\CacheBundle\Exceptions\InvalidArgumentException;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
class MemcachedTagAwareAdapter extends TagAwareAdapter
{
public function __construct(array $servers, string $namespace, int $lifetime)
{
if (!isset($servers['servers'])) {
throw new InvalidArgumentException('Invalid memcached configuration. No servers specified.');
}
$options = array_key_exists('options', $servers) ? $servers['options'] : [];
$client = MemcachedAdapter::createConnection($servers['servers'], $options);
parent::__construct(
new MemcachedAdapter($client, $namespace, $lifetime),
new MemcachedAdapter($client, $namespace, $lifetime)
);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache\Adapter;
use Symfony\Component\Cache\Adapter\RedisAdapter as SymfonyRedisAdapter;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class RedisAdapter extends SymfonyRedisAdapter
{
use RedisAdapterTrait;
/**
* @param mixed[] $servers
*/
public function __construct(
#[Autowire(env: 'json:MAUTIC_CACHE_ADAPTER_REDIS')]
array $servers,
#[Autowire(env: 'string:MAUTIC_CACHE_PREFIX')]
string $namespace,
#[Autowire(env: 'int:MAUTIC_CACHE_LIFETIME')]
int $lifetime,
#[Autowire(env: 'bool:MAUTIC_REDIS_PRIMARY_ONLY')]
bool $primaryOnly)
{
parent::__construct($this->createClient($servers, $primaryOnly), $namespace, $lifetime);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache\Adapter;
use Mautic\CacheBundle\Exceptions\InvalidArgumentException;
use Mautic\CoreBundle\Helper\PRedisConnectionHelper;
use Predis\Client;
trait RedisAdapterTrait
{
/**
* @param mixed[] $servers
*/
private function createClient(array $servers, bool $primaryOnly): Client
{
if (!isset($servers['dsn'])) {
throw new InvalidArgumentException('Invalid redis configuration. No server specified.');
}
$options = array_key_exists('options', $servers) ? $servers['options'] : [];
$options['primaryOnly'] = $primaryOnly;
return PRedisConnectionHelper::createClient(PRedisConnectionHelper::getRedisEndpoints($servers['dsn']), $options);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache\Adapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class RedisTagAwareAdapter extends TagAwareAdapter
{
use RedisAdapterTrait;
/**
* @param mixed[] $servers
*/
public function __construct(
#[Autowire(env: 'json:MAUTIC_CACHE_ADAPTER_REDIS')]
array $servers,
#[Autowire(env: 'string:MAUTIC_CACHE_PREFIX')]
string $namespace,
#[Autowire(env: 'int:MAUTIC_CACHE_LIFETIME')]
int $lifetime,
#[Autowire(env: 'bool:MAUTIC_REDIS_PRIMARY_ONLY')]
bool $primaryOnly)
{
$client = $this->createClient($servers, $primaryOnly);
parent::__construct(
new RedisAdapter($client, $namespace, $lifetime),
new RedisAdapter($client, $namespace.'.tags.', $lifetime)
);
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache;
use Symfony\Component\Cache\Adapter\AdapterInterface;
final class CacheProvider extends AbstractCacheProvider
{
public function getCacheAdapter(): AdapterInterface
{
return $this->cacheAdapterFactory('cache_adapter');
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Psr16Cache;
interface CacheProviderInterface extends AdapterInterface
{
/**
* @return Psr16Cache
*/
public function getSimpleCache();
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
final class CacheProviderTagAware extends AbstractCacheProvider implements CacheProviderTagAwareInterface
{
public function getCacheAdapter(): TagAwareAdapterInterface
{
$adapter = $this->cacheAdapterFactory('cache_adapter_tag_aware');
\assert($adapter instanceof TagAwareAdapterInterface);
return $adapter;
}
public function invalidateTags(array $tags): bool
{
return $this->getCacheAdapter()->invalidateTags($tags);
}
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Cache;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
interface CacheProviderTagAwareInterface extends CacheProviderInterface, TagAwareAdapterInterface
{
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Command;
use Mautic\CacheBundle\Cache\CacheProviderInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* CLI Command to clear the application cache.
*/
#[AsCommand(
name: 'mautic:cache:clear',
description: "Clears Mautic's cache"
)]
class ClearCacheCommand extends Command
{
public function __construct(
private CacheProviderInterface $cacheProvider,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
return (int) !$this->cacheProvider->clear();
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
return [
'routes' => [
'main' => [],
'public' => [],
'api' => [],
],
'menu' => [],
'services' => [
'events' => [
'mautic.cache.clear_cache_subscriber' => [
'class' => Mautic\CacheBundle\EventListener\CacheClearSubscriber::class,
'tags' => ['kernel.cache_clearer'],
'arguments' => [
'mautic.cache.provider',
'monolog.logger.mautic',
],
],
],
'other' => [
'mautic.cache.adapter.filesystem' => [
'class' => Mautic\CacheBundle\Cache\Adapter\FilesystemTagAwareAdapter::class,
'arguments' => [
'%mautic.cache_prefix%',
'%mautic.cache_lifetime%',
'%mautic.tmp_path%',
],
'tag' => 'mautic.cache.adapter',
],
'mautic.cache.adapter.memcached' => [
'class' => Mautic\CacheBundle\Cache\Adapter\MemcachedTagAwareAdapter::class,
'arguments' => [
'%mautic.cache_adapter_memcached%',
'%mautic.cache_prefix%',
'%mautic.cache_lifetime%',
],
'tag' => 'mautic.cache.adapter',
],
],
'models' => [],
'validator' => [],
],
'parameters' => [
'cache_adapter' => 'mautic.cache.adapter.filesystem',
'cache_adapter_tag_aware' => 'mautic.cache.adapter.filesystem',
'cache_prefix' => '',
'cache_lifetime' => 86400,
'cache_adapter_memcached' => [
'servers' => ['memcached://localhost'],
'options' => [
'compression' => true,
'libketama_compatible' => true,
'serializer' => 'igbinary',
],
],
'cache_adapter_redis' => [
'dsn' => 'redis://localhost',
'options' => [
'lazy' => false,
'persistent' => 0,
'persistent_id' => null,
'timeout' => 30,
'read_timeout' => 0,
'retry_interval' => 0,
],
],
],
];

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
use Mautic\CoreBundle\DependencyInjection\MauticCoreExtension;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return function (ContainerConfigurator $configurator): void {
$services = $configurator->services()
->defaults()
->autowire()
->autoconfigure()
->public();
$services->load('Mautic\\CacheBundle\\', '../')
->exclude('../{'.implode(',', MauticCoreExtension::DEFAULT_EXCLUDES).'}');
$services->alias(Mautic\CacheBundle\Cache\CacheProviderInterface::class, Mautic\CacheBundle\Cache\CacheProvider::class);
$services->alias('mautic.cache.provider', Mautic\CacheBundle\Cache\CacheProvider::class);
$services->alias('mautic.cache.provider_tag_aware', Mautic\CacheBundle\Cache\CacheProviderTagAware::class);
$services->alias('mautic.cache.adapter.redis', Mautic\CacheBundle\Cache\Adapter\RedisAdapter::class);
$services->alias('mautic.cache.adapter.redis_tag_aware', Mautic\CacheBundle\Cache\Adapter\RedisTagAwareAdapter::class);
$services->get(Mautic\CacheBundle\Cache\Adapter\RedisAdapter::class)
->tag('mautic.cache.adapter');
$services->get(Mautic\CacheBundle\Cache\Adapter\RedisTagAwareAdapter::class)
->tag('mautic.cache.adapter');
};

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
class MauticCacheExtension extends Extension
{
/**
* @param mixed[] $configs
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Config'));
$loader->load('services.php');
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\EventListener;
use Mautic\CacheBundle\Cache\CacheProvider;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
class CacheClearSubscriber implements CacheClearerInterface
{
/**
* @var CacheProvider
*/
private AdapterInterface $cacheProvider;
private LoggerInterface $logger;
public function __construct(AdapterInterface $cacheProvider, LoggerInterface $logger)
{
$this->cacheProvider = $cacheProvider;
$this->logger = $logger;
}
/**
* @param string $cacheDir
*
* @throws \Exception
*/
public function clear($cacheDir): void
{
try {
$reflect = new \ReflectionClass($this->cacheProvider->getCacheAdapter());
$adapter = $reflect->getShortName();
} catch (\ReflectionException) {
$adapter = 'unknown';
}
try {
if (!$this->cacheProvider->clear()) {
$this->logger->emergency('Failed to clear Mautic cache.', ['adapter' => $adapter]);
throw new \Exception('Failed to clear '.$adapter);
}
} catch (\PDOException) {
}
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Exceptions;
class InvalidArgumentException extends \Symfony\Component\Cache\Exception\InvalidArgumentException
{
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MauticCacheBundle extends Bundle
{
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Tests\Cache;
use Mautic\CacheBundle\Cache\Adapter\FilesystemTagAwareAdapter;
use Mautic\CacheBundle\Cache\CacheProvider;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
class CacheProviderTest extends TestCase
{
private CacheProvider $cacheProvider;
/**
* @var MockObject|FilesystemTagAwareAdapter
*/
private MockObject $adapter;
/**
* @var MockObject|CoreParametersHelper
*/
private MockObject $coreParametersHelper;
/**
* @var MockObject|ContainerInterface
*/
private MockObject $container;
public function setUp(): void
{
parent::setUp();
$this->adapter = $this->createMock(FilesystemTagAwareAdapter::class);
$this->coreParametersHelper = $this->createMock(CoreParametersHelper::class);
$this->container = $this->createMock(ContainerInterface::class);
$this->cacheProvider = new CacheProvider($this->coreParametersHelper, $this->container);
}
public function testRequestedCacheAdaptorIsReturned(): void
{
$this->coreParametersHelper->expects($this->once())
->method('get')
->with('cache_adapter')
->willReturn('foo.bar');
$this->container->expects($this->once())
->method('get')
->with('foo.bar')
->willReturn($this->adapter);
$this->assertEquals($this->cacheProvider->getCacheAdapter(), $this->adapter);
}
public function testSimplePsrCacheIsReturned(): void
{
$this->coreParametersHelper->expects($this->once())
->method('get')
->with('cache_adapter')
->willReturn('foo.bar');
$this->container->expects($this->once())
->method('get')
->with('foo.bar')
->willReturn($this->adapter);
$this->cacheProvider->getSimpleCache();
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Mautic\CacheBundle\Tests\EventListener;
use Mautic\CacheBundle\Cache\AbstractCacheProvider;
use Mautic\CacheBundle\EventListener\CacheClearSubscriber;
use Monolog\Logger;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Cache\Adapter\AdapterInterface;
class CacheClearSubscriberTest extends \PHPUnit\Framework\TestCase
{
/**
* @var MockObject&AbstractCacheProvider
*/
private MockObject $adapter;
public function setUp(): void
{
parent::setUp();
$this->adapter = $this->getMockBuilder(AbstractCacheProvider::class)
->disableOriginalConstructor()
->onlyMethods(['clear', 'commit', 'getCacheAdapter'])
->getMock();
$this->adapter->method('clear')->willReturn(true);
$this->adapter->method('commit')->willReturn(true);
$this->adapter->method('getCacheAdapter')->willReturn($this->createMock(AdapterInterface::class));
}
public function testClear(): void
{
$this->adapter->expects($this->once())->method('clear')->willReturn(true);
$subscriber = new CacheClearSubscriber($this->adapter, new Logger('test'));
$subscriber->clear('aaa');
}
}