Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing PHP analysis tools and workflow #6

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/code-analyse.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: "Code Analysis"

on:
- "push"
- "pull_request"

jobs:
analysis:

runs-on: "ubuntu-latest"

steps:
- uses: "actions/checkout@v3"

- name: "Setup PHP Action"
uses: "shivammathur/setup-php@v2"
with:
php-version: "8.0"
extensions: "intl, xdebug, sodium, gd, pdo, curl, iconv, openssl"

- name: "Composer Install"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--prefer-dist --no-progress"

- name: "PHPStan"
run: "vendor/bin/phpstan"

- name: "PHPCS"
run: "vendor/bin/phpcs"
77 changes: 46 additions & 31 deletions Command/ConfigSynchronizeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,84 +7,92 @@
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\System\SalesChannel\SalesChannelEntity;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;

use function array_key_exists;
use function assert;
use function file_exists;
use function implode;
use function is_array;
use function sprintf;

class ConfigSynchronizeCommand extends Command
{
protected static $defaultName = 'config:sync';
// phpcs:ignore
protected static $defaultName = 'config:sync';
private static string $defaultScope = 'global';
private string $projectDir;
private SystemConfigService $systemConfigService;
private EntityRepositoryInterface $salesChannelRepository;
private OutputInterface $output;

public function __construct(
string $projectDir,
SystemConfigService $systemConfigService,
EntityRepositoryInterface $salesChannelRepository
private string $projectDir,
private SystemConfigService $systemConfigService,
private EntityRepositoryInterface $salesChannelRepository,
) {
$this->projectDir = $projectDir;
$this->systemConfigService = $systemConfigService;
$this->salesChannelRepository = $salesChannelRepository;
parent::__construct();
}

protected function configure(): void
{
parent::configure();

$this->setDescription('Update system config like defined in file config/config.yaml');
$this->addArgument(
'config_path',
InputArgument::OPTIONAL,
'Path to config yaml',
'config/config.yaml'
'config/config.yaml',
);
}

protected function execute(
InputInterface $input,
OutputInterface $output
OutputInterface $output,
): int {
$this->output = $output;
$configPath = $input->getArgument('config_path');
$filePath = $this->projectDir . '/' . $configPath;
if (!file_exists($filePath)) {
$configPath = $input->getArgument('config_path');
$filePath = $this->projectDir . '/' . $configPath;
if (! file_exists($filePath)) {
$this->output->writeln(sprintf('%s not found', $filePath));

return self::FAILURE;
}

$yamlReader = new Yaml();
$yaml = $yamlReader::parseFile($filePath);
$yaml = $yamlReader::parseFile($filePath);

$this->executeGlobalConfigSync($yaml);
$this->executeSalesChannelConfigSync($yaml);

return self::SUCCESS;
}

/** @param array<string, mixed> $yaml */
private function executeGlobalConfigSync(array $yaml): void
{
if (isset($yaml[self::$defaultScope])) {
$this->output->writeln('---------------------------------------');
$this->executeConfigSet($yaml[self::$defaultScope], self::$defaultScope);
$this->output->writeln('---------------------------------------');
if (! isset($yaml[self::$defaultScope])) {
return;
}

$this->output->writeln('---------------------------------------');
$this->executeConfigSet($yaml[self::$defaultScope], self::$defaultScope);
$this->output->writeln('---------------------------------------');
}

private function executeConfigSet(array $config, string $name, ?string $salesChannelId = null): void
/** @param array<string, mixed> $config */
private function executeConfigSet(array $config, string $name, string|null $salesChannelId = null): void
{
$this->output->writeln(sprintf('Current config scope: "%s"', $name));
$this->output->writeln('---------------------------------------');
foreach ($config as $key => $value) {
$currentValue = $this->systemConfigService->get($key, $salesChannelId);
$currentValue = $this->systemConfigService->get($key, $salesChannelId);
$currentValueAsString = $this->valueToString($currentValue);
$valueAsString = $this->valueToString($value);
$valueAsString = $this->valueToString($value);
$this->output->writeln(sprintf('Current value: "%s" for key: "%s"', $currentValueAsString, $key));
// using string comparison for all values (array|bool|float|int|string|null) simplified
if ($currentValueAsString !== $valueAsString) {
Expand All @@ -96,10 +104,11 @@ private function executeConfigSet(array $config, string $name, ?string $salesCha
}
}

/** @param array<string, mixed> $yaml */
private function executeSalesChannelConfigSync(array $yaml): void
{
$salesChannelUpdated = $salesChannelNotUpdated = [];
$salesChannels = $this->getSalesChannels();
$salesChannels = $this->getSalesChannels();
foreach ($salesChannels as $name => $salesChannelId) {
if (isset($yaml[$name])) {
$this->executeConfigSet($yaml[$name], $name, $salesChannelId);
Expand All @@ -109,34 +118,40 @@ private function executeSalesChannelConfigSync(array $yaml): void
$salesChannelNotUpdated[$salesChannelId] = $name;
}
}

// put info message only if the salesChannel was totally not updated,
// we get a salesChannel for every translation, to work in yaml file with the name of the translation
foreach ($salesChannelNotUpdated as $idNotUpdated => $name) {
if (array_key_exists($idNotUpdated, $salesChannelUpdated) === false) {
$this->output->writeln(
sprintf('>>> No config update for SalesChannel with id: "%s" <<<', $idNotUpdated)
);
if (array_key_exists($idNotUpdated, $salesChannelUpdated) !== false) {
continue;
}

$this->output->writeln(
sprintf('>>> No config update for SalesChannel with id: "%s" <<<', $idNotUpdated),
);
}
}

/** @return array<string, string> */
private function getSalesChannels(): array
{
$salesChannels = [];
$criteria = new Criteria();
$criteria = new Criteria();
$criteria->addAssociation('translations');

$salesChannelIds = $this->salesChannelRepository->search($criteria, Context::createDefaultContext());
foreach ($salesChannelIds->getEntities()->getElements() as $salesChannel) {
foreach ($salesChannel->getTranslations()->getElements() as $translation) {
assert($salesChannel instanceof SalesChannelEntity);
foreach ($salesChannel->getTranslations()?->getElements() ?? [] as $translation) {
$salesChannels[$translation->getName()] = $translation->getSalesChannelId();
}
}

return $salesChannels;
}

private function valueToString($value): string
/** @param scalar|list<scalar>|null $value */
private function valueToString(string|int|float|bool|array|null $value): string
{
if (is_array($value)) {
return implode(', ', $value);
Expand Down
39 changes: 22 additions & 17 deletions Command/PluginSynchronizeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,52 @@
namespace Flagbit\Shopware\ShopwareMaintenance\Command;

use Psr\Log\LoggerInterface;
use RuntimeException;
use Shopware\Core\Framework\Plugin\Exception\PluginBaseClassNotFoundException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function array_keys;
use function array_sum;
use function file_exists;
use function sprintf;

class PluginSynchronizeCommand extends Command
{
// phpcs:ignore
protected static $defaultName = 'plugin:sync';

private string $projectDir;
private LoggerInterface $logger;

public function __construct(
string $projectDir,
LoggerInterface $logger
private string $projectDir,
private LoggerInterface $logger,
) {
parent::__construct();
$this->projectDir = $projectDir;
$this->logger = $logger;
}

protected function configure(): void
{
parent::configure();

$this->setDescription('Install/uninstall plugins as defined in file config/plugins.php');
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!file_exists($this->projectDir . '/config/plugins.php')) {
if (! file_exists($this->projectDir . '/config/plugins.php')) {
$output->writeln(sprintf('%s not found', $this->projectDir . '/config/plugins.php'));

return 1;
}

$plugins = require $this->projectDir . '/config/plugins.php';
$disabledPlugins = array_keys(array_filter($plugins, function ($isEnabled) {
$plugins = require $this->projectDir . '/config/plugins.php';
$disabledPlugins = array_keys(array_filter($plugins, static function ($isEnabled) {
return $isEnabled === false;
}));
$enabledPlugins = array_keys(array_filter($plugins, function ($isEnabled) {
$enabledPlugins = array_keys(array_filter($plugins, static function ($isEnabled) {
return $isEnabled === true;
}));

Expand All @@ -60,6 +65,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
foreach ($enabledPlugins as $enabledPlugin) {
$installFailed[$enabledPlugin] = $this->executePluginInstall($enabledPlugin, $output);
}

$errorSum = (int) array_sum($installFailed);
if ($errorSum > self::SUCCESS) {
return self::FAILURE;
Expand All @@ -69,17 +75,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

/**
* @param array $parameters
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param array<string, mixed> $parameters
*
* @return int
* @throws \Symfony\Component\Console\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
private function runCommand(array $parameters, OutputInterface $output): int
{
$application = $this->getApplication();
if ($application === null) {
throw new \RuntimeException('No application initialised');
throw new RuntimeException('No application initialised');
}

$output->writeln('');
Expand All @@ -101,8 +105,9 @@ private function executePluginInstall(string $enabledPlugin, OutputInterface $ou
'plugins' => [$enabledPlugin],
'--activate' => true,
], $output);
} catch (PluginBaseClassNotFoundException $baseClassNotFoundException) {
} catch (PluginBaseClassNotFoundException) {
$this->logger->error('Execute plugin:refresh before plugin:sync !');

return self::FAILURE;
}

Expand Down
3 changes: 2 additions & 1 deletion DependencyInjection/ShopwareMaintenanceExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@

class ShopwareMaintenanceExtension extends Extension
{
/** @param array<string, mixed> $configs */
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.xml');
}
}
3 changes: 2 additions & 1 deletion ShopwareMaintenance.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<?php

declare(strict_types=1);

namespace Flagbit\Shopware\ShopwareMaintenance;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class ShopwareMaintenance extends Bundle
{

}
9 changes: 8 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
"symfony/framework-bundle": "^5.4"
},
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
"phpstan/phpstan": "^1.10"
}
}
21 changes: 21 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<ruleset>
<arg name="basepath" value="."/>
<arg name="extensions" value="php"/>
<arg name="colors"/>
<arg value="nps"/>

<file>Command</file>
<file>DependencyInjection</file>
<file>ShopwareMaintenance.php</file>

<rule ref="Doctrine"/>

<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming.SuperfluousSuffix"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousSuffix"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix"/>
<exclude name="SlevomatCodingStandard.Classes.SuperfluousTraitNaming.SuperfluousSuffix"/>
</rule>
</ruleset>
7 changes: 7 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
parameters:
level: 8
paths:
- "%currentWorkingDirectory%/"
excludePaths:
- "%currentWorkingDirectory%/vendor/"