diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4c479e..d33969c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: CI for Maker42 +name: CI for Maker44 on: push: branches: @@ -14,22 +14,23 @@ on: paths: - '**' - '!*.md' + +# GITHUB_TOKEN は最小権限(読み取りのみ)にする +permissions: + contents: read + jobs: run-on-linux: name: Run on Linux - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - eccube_version: [ '4.2', '4.3' ] - php: [ '7.4', '8.0', '8.1', '8.2', '8.3'] - db: [ 'mysql', 'mysql8', 'pgsql' ] - plugin_code: [ 'Maker42' ] + eccube_version: [ '4.4' ] + php: [ '8.2', '8.3', '8.4', '8.5' ] + db: [ 'mysql8', 'pgsql' ] + plugin_code: [ 'Maker44' ] include: - - db: mysql - database_url: mysql://root:password@127.0.0.1:3306/eccube_db - database_server_version: 5.7 - database_charset: utf8mb4 - db: mysql8 database_url: mysql://root:password@127.0.0.1:3308/eccube_db database_server_version: 8 @@ -38,29 +39,11 @@ jobs: database_url: postgres://postgres:password@127.0.0.1:5432/eccube_db database_server_version: 14 database_charset: utf8 - exclude: - - eccube_version: 4.2 - php: 8.2 - - eccube_version: 4.2 - php: 8.3 - - eccube_version: 4.3 - php: 7.4 - - eccube_version: 4.3 - php: 8.0 services: - mysql: - image: mysql:5.7 - env: - MYSQL_ROOT_PASSWORD: password - MYSQL_DATABASE: ${{ matrix.dbname }} - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 mysql8: image: mysql:8 env: MYSQL_ROOT_PASSWORD: password - MYSQL_DATABASE: ${{ matrix.dbname }} ports: - 3308:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 @@ -69,7 +52,6 @@ jobs: env: POSTGRES_USER: postgres POSTGRES_PASSWORD: password - POSTGRES_DB: ${{ matrix.dbname }} ports: - 5432:5432 # needed because the postgres container does not provide a healthcheck @@ -81,30 +63,32 @@ jobs: - 1025:1025 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup PHP uses: nanasess/setup-php@master with: php-version: ${{ matrix.php }} - - name: Archive Plugin env: PLUGIN_CODE: ${{ matrix.plugin_code }} run: | tar cvzf ${GITHUB_WORKSPACE}/${PLUGIN_CODE}.tar.gz ./* - name: Checkout EC-CUBE - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: 'EC-CUBE/ec-cube' ref: ${{ matrix.eccube_version }} path: 'ec-cube' + persist-credentials: false - name: Get Composer Cache Directory id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v1 + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + - uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -112,7 +96,9 @@ jobs: ${{ runner.os }}-composer- - name: Install to composer working-directory: 'ec-cube' - run: composer install --no-interaction -o --apcu-autoloader + # Symfony 7.4 の symfony/cache が ext-redis <6.1 と衝突するが、ランナーには + # 古い php-redis が入っている。本プラグイン/本体テストは redis 未使用のため無視する。 + run: composer install --no-interaction -o --apcu-autoloader --ignore-platform-req=ext-redis - name: Setup EC-CUBE env: @@ -137,8 +123,8 @@ jobs: working-directory: 'ec-cube' run: | bin/console eccube:plugin:install --code=${PLUGIN_CODE} --path=${GITHUB_WORKSPACE}/${PLUGIN_CODE}.tar.gz - bin/console cache:clear --no-warmup bin/console eccube:plugin:enable --code=${PLUGIN_CODE} + bin/console cache:clear --no-warmup - name: Run PHPUnit env: APP_ENV: 'test' @@ -149,7 +135,11 @@ jobs: PLUGIN_CODE: ${{ matrix.plugin_code }} working-directory: 'ec-cube' run: | + # 有効化したプラグインのルーティングはコンテナのコンパイル時に確定する。 + # phpunit プロセスでの遅延コンパイルに任せると DB/タイミングで有効プラグイン一覧を + # 取りこぼし RouteNotFound になることがあるため、クリーンなプロセスで warmup して確定させる。 bin/console cache:clear --no-warmup + bin/console cache:warmup ./vendor/bin/phpunit -c app/Plugin/${PLUGIN_CODE}/phpunit.xml.dist app/Plugin/${PLUGIN_CODE}/Tests - name: Disable Plugin @@ -173,3 +163,80 @@ jobs: PLUGIN_CODE: ${{ matrix.plugin_code }} working-directory: 'ec-cube' run: bin/console eccube:plugin:uninstall --code=${PLUGIN_CODE} + + static-analysis: + name: Static Analysis + runs-on: ubuntu-24.04 + # 静的解析は DB 種別に依存しないため、SQLite 1 構成で一度だけ実行する。 + # (php-cs-fixer / rector は DB 不要だが、phpstan は objectManagerLoader が + # カーネルを起動し EccubeExtension が dtb_plugin を読むため本体+DBが必要) + env: + PLUGIN_CODE: Maker44 + APP_ENV: 'test' + APP_DEBUG: 0 + DATABASE_URL: 'sqlite:///var/eccube.db' + DATABASE_SERVER_VERSION: 3 + DATABASE_CHARSET: 'utf8' + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup PHP + uses: nanasess/setup-php@master + with: + php-version: '8.5' + + - name: Archive Plugin + run: | + tar cvzf ${GITHUB_WORKSPACE}/${PLUGIN_CODE}.tar.gz ./* + - name: Checkout EC-CUBE + uses: actions/checkout@v4 + with: + repository: 'EC-CUBE/ec-cube' + ref: '4.4' + path: 'ec-cube' + persist-credentials: false + + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + - uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + - name: Install to composer + working-directory: 'ec-cube' + run: composer install --no-interaction -o --apcu-autoloader --ignore-platform-req=ext-redis + + - name: Setup EC-CUBE + working-directory: 'ec-cube' + run: | + bin/console doctrine:database:create + bin/console doctrine:schema:create + # プラグイン有効化時に DeviceType 等のマスタデータを参照するため fixtures を投入する + bin/console eccube:fixtures:load + - name: Setup Plugin + working-directory: 'ec-cube' + run: | + bin/console eccube:plugin:install --code=${PLUGIN_CODE} --path=${GITHUB_WORKSPACE}/${PLUGIN_CODE}.tar.gz + bin/console eccube:plugin:enable --code=${PLUGIN_CODE} + bin/console cache:clear --no-warmup + + - name: Run php-cs-fixer + working-directory: 'ec-cube' + run: ./vendor/bin/php-cs-fixer fix --config=app/Plugin/${PLUGIN_CODE}/Resource/.php-cs-fixer.dist.php --dry-run --diff + + - name: Run Rector + working-directory: 'ec-cube' + run: ./vendor/bin/rector process --config=app/Plugin/${PLUGIN_CODE}/Resource/rector.php --dry-run + + - name: Run PHPStan + working-directory: 'ec-cube' + run: | + bin/console cache:clear --no-warmup + ./vendor/bin/phpstan analyse -c app/Plugin/${PLUGIN_CODE}/phpstan.neon.dist --no-progress diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d05e7a7..f78ecdb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,10 @@ jobs: run: | rm -rf $GITHUB_WORKSPACE/doc rm -rf $GITHUB_WORKSPACE/.github + rm -rf $GITHUB_WORKSPACE/Tests + # 開発・テスト用ファイルは配布パッケージに含めない + rm -f $GITHUB_WORKSPACE/phpstan.neon.dist + rm -f $GITHUB_WORKSPACE/Resource/rector.php "$GITHUB_WORKSPACE/Resource/.php-cs-fixer.dist.php" find $GITHUB_WORKSPACE -name "dummy" -delete find $GITHUB_WORKSPACE -name ".git*" -and ! -name ".gitkeep" -print0 | xargs -0 rm -rf chmod -R o+w $GITHUB_WORKSPACE diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..756b5fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +composer.phar +/vendor/ + +# 本プラグインはライブラリ (type: eccube-plugin) のため composer.lock はコミットしない +# https://getcomposer.org/doc/02-libraries.md#lock-file +composer.lock + +# 静的解析・整形ツールのキャッシュ +.php-cs-fixer.cache +.phpunit.result.cache + +# Playwright MCP の一時生成物 +/.playwright-mcp/ diff --git a/Controller/MakerController.php b/Controller/MakerController.php index bc34813..5461628 100644 --- a/Controller/MakerController.php +++ b/Controller/MakerController.php @@ -11,52 +11,38 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Controller; +namespace Plugin\Maker44\Controller; +use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; use Eccube\Controller\AbstractController; -use Plugin\Maker42\Entity\Maker; -use Plugin\Maker42\Form\Type\MakerType; -use Plugin\Maker42\Repository\MakerRepository; -use Symfony\Component\Routing\Annotation\Route; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Plugin\Maker44\Entity\Maker; +use Plugin\Maker44\Form\Type\MakerType; +use Plugin\Maker44\Repository\MakerRepository; +use Symfony\Bridge\Doctrine\Attribute\MapEntity; +use Symfony\Bridge\Twig\Attribute\Template; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; /** * Class MakerController. */ class MakerController extends AbstractController { - /** - * @var MakerRepository - */ - protected $makerRepository; - - /** - * MakerController constructor. - * - * @param MakerRepository $makerRepository - */ - public function __construct(MakerRepository $makerRepository) - { - $this->makerRepository = $makerRepository; + public function __construct( + protected MakerRepository $makerRepository, + ) { } /** * List, add, edit maker. * - * @param Request $request - * - * @return array|\Symfony\Component\HttpFoundation\RedirectResponse - * - * @throws \Doctrine\ORM\NoResultException - * @throws \Doctrine\ORM\NonUniqueResultException - * @throws \Doctrine\ORM\OptimisticLockException - * - * @Route("/%eccube_admin_route%/maker", name="maker_admin_index") - * @Template("@Maker42/admin/maker.twig") + * @return array|RedirectResponse */ - public function index(Request $request) + #[Route('/%eccube_admin_route%/maker', name: 'maker_admin_index')] + #[Template('@Maker44/admin/maker.twig')] + public function index(Request $request): array|RedirectResponse { $Maker = new Maker(); $Makers = $this->makerRepository->findBy([], ['sort_no' => 'DESC']); @@ -120,19 +106,9 @@ public function index(Request $request) /** * Delete Maker. - * - * @param Request $request - * @param Maker $Maker - * - * @return \Symfony\Component\HttpFoundation\RedirectResponse - * - * @Route( - * "/%eccube_admin_route%/maker/{id}/delete", - * name="maker_admin_delete", requirements={"id":"\d+"}, - * methods={"DELETE"} - * ) */ - public function delete(Request $request, Maker $Maker) + #[Route('/%eccube_admin_route%/maker/{id}/delete', name: 'maker_admin_delete', requirements: ['id' => '\d+'], methods: ['DELETE'])] + public function delete(#[MapEntity(id: 'id')] Maker $Maker): RedirectResponse { $this->isTokenValid(); @@ -142,8 +118,10 @@ public function delete(Request $request, Maker $Maker) $this->addSuccess('maker.admin.delete.complete', 'admin'); log_info('メーカー削除完了', ['Maker id' => $Maker->getId()]); - } catch (\Exception $e) { - log_info('メーカー削除エラー', ['Maker id' => $Maker->getId(), $e]); + } catch (ForeignKeyConstraintViolationException $e) { + // 商品から参照されている場合のみ外部キー制約エラーとして扱う。 + // それ以外の例外(DB 障害等)は握り潰さず伝播させる。 + log_warning('メーカー削除エラー: 外部キー制約により削除できません', ['Maker id' => $Maker->getId(), 'exception' => $e]); $message = trans('admin.common.delete_error_foreign_key', ['%name%' => $Maker->getName()]); $this->addError($message, 'admin'); @@ -154,25 +132,17 @@ public function delete(Request $request, Maker $Maker) /** * Move sort no with ajax. - * - * @param Request $request - * - * @return Response - * - * @throws \Exception - * - * @Route( - * "/%eccube_admin_route%/maker/move_sort_no", - * name="maker_admin_move_sort_no", - * methods={"POST"} - * ) */ - public function moveSortNo(Request $request) + #[Route('/%eccube_admin_route%/maker/move_sort_no', name: 'maker_admin_move_sort_no', methods: ['POST'])] + public function moveSortNo(Request $request): Response { if ($request->isXmlHttpRequest() && $this->isTokenValid()) { $sortNos = $request->request->all(); foreach ($sortNos as $makerId => $sortNo) { $Maker = $this->makerRepository->find($makerId); + if (null === $Maker) { + continue; + } $Maker->setSortNo($sortNo); $this->entityManager->persist($Maker); } diff --git a/Entity/Maker.php b/Entity/Maker.php index acffded..0fdd0a8 100644 --- a/Entity/Maker.php +++ b/Entity/Maker.php @@ -11,86 +11,59 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Entity; +namespace Plugin\Maker44\Entity; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Eccube\Entity\AbstractEntity; +use Plugin\Maker44\Repository\MakerRepository; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -use Symfony\Component\Validator\Mapping\ClassMetadata; /** * Class Maker. - * - * @ORM\Table(name="plg_maker") - * @ORM\Entity(repositoryClass="Plugin\Maker42\Repository\MakerRepository") */ +#[ORM\Table(name: 'plg_maker')] +#[ORM\Entity(repositoryClass: MakerRepository::class)] +#[UniqueEntity('name')] class Maker extends AbstractEntity { - /** - * @var int - * - * @ORM\Column(name="id", type="integer", options={"unsigned":true}) - * @ORM\Id - * @ORM\GeneratedValue(strategy="IDENTITY") - */ - private $id; + #[ORM\Column(name: 'id', type: Types::INTEGER, options: ['unsigned' => true])] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] + private ?int $id = null; - /** - * @var string - * - * @ORM\Column(name="name", type="string", length=255) - */ - private $name; + #[ORM\Column(name: 'name', type: Types::STRING, length: 255)] + private ?string $name = null; - /** - * @var int - * - * @ORM\Column(name="sort_no", type="integer") - */ - private $sort_no; + #[ORM\Column(name: 'sort_no', type: Types::INTEGER)] + private ?int $sort_no = null; - /** - * @var \DateTime - * - * @ORM\Column(name="create_date", type="datetimetz") - */ - private $create_date; + #[ORM\Column(name: 'create_date', type: Types::DATETIMETZ_MUTABLE)] + private ?\DateTime $create_date = null; - /** - * @var \DateTime - * - * @ORM\Column(name="update_date", type="datetimetz") - */ - private $update_date; + #[ORM\Column(name: 'update_date', type: Types::DATETIMETZ_MUTABLE)] + private ?\DateTime $update_date = null; /** * Get id. - * - * @return int */ - public function getId() + public function getId(): ?int { return $this->id; } /** * Get name. - * - * @return string */ - public function getName() + public function getName(): ?string { return $this->name; } /** * Set name. - * - * @param string $name - * - * @return Maker */ - public function setName($name) + public function setName(?string $name): Maker { $this->name = $name; @@ -99,22 +72,16 @@ public function setName($name) /** * Get sort_no. - * - * @return int */ - public function getSortNo() + public function getSortNo(): ?int { return $this->sort_no; } /** * Set sort no. - * - * @param int $sortNo - * - * @return Maker */ - public function setSortNo($sortNo) + public function setSortNo(?int $sortNo): Maker { $this->sort_no = $sortNo; @@ -123,12 +90,8 @@ public function setSortNo($sortNo) /** * Set create_date. - * - * @param \DateTime $createDate - * - * @return Maker */ - public function setCreateDate($createDate) + public function setCreateDate(\DateTime $createDate): Maker { $this->create_date = $createDate; @@ -137,22 +100,16 @@ public function setCreateDate($createDate) /** * Get create_date. - * - * @return \DateTime */ - public function getCreateDate() + public function getCreateDate(): ?\DateTime { return $this->create_date; } /** * Set update_date. - * - * @param \DateTime $updateDate - * - * @return Maker */ - public function setUpdateDate($updateDate) + public function setUpdateDate(\DateTime $updateDate): Maker { $this->update_date = $updateDate; @@ -161,23 +118,9 @@ public function setUpdateDate($updateDate) /** * Get update_date. - * - * @return \DateTime */ - public function getUpdateDate() + public function getUpdateDate(): ?\DateTime { return $this->update_date; } - - /** - * Unique check. - * - * @param ClassMetadata $metadata - */ - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addConstraint(new UniqueEntity([ - 'fields' => 'name', - ])); - } } diff --git a/Entity/ProductTrait.php b/Entity/ProductTrait.php index 58f89f9..878f2a7 100644 --- a/Entity/ProductTrait.php +++ b/Entity/ProductTrait.php @@ -11,66 +11,44 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Entity; +namespace Plugin\Maker44\Entity; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; -use Eccube\Annotation as Eccube; +use Eccube\Attribute\EntityExtension; +use Eccube\Entity\Product; -/** - * @Eccube\EntityExtension("Eccube\Entity\Product") - */ +#[EntityExtension(Product::class)] trait ProductTrait { - /** - * @var \Plugin\Maker42\Entity\Maker - * - * @ORM\ManyToOne(targetEntity="Plugin\Maker42\Entity\Maker") - * @ORM\JoinColumns({ - * @ORM\JoinColumn(name="maker_id", referencedColumnName="id") - * }) - */ - private $Maker; + #[ORM\ManyToOne(targetEntity: Maker::class)] + #[ORM\JoinColumn(name: 'maker_id', referencedColumnName: 'id')] + private ?Maker $Maker = null; - /** - * @var string - * - * @ORM\Column(name="maker_url", type="string", length=1024, nullable=true) - */ - private $maker_url; + #[ORM\Column(name: 'maker_url', type: Types::STRING, length: 1024, nullable: true)] + private ?string $maker_url = null; - /** - * @return Maker - */ - public function getMaker() + public function getMaker(): ?Maker { return $this->Maker; } - /** - * @param Maker|null $Maker - * - * @return $this - */ - public function setMaker(Maker $Maker = null) + public function setMaker(?Maker $Maker = null): self { $this->Maker = $Maker; return $this; } - /** - * @return string - */ - public function getMakerUrl() + public function getMakerUrl(): ?string { return $this->maker_url; } - /** - * @param string $maker_url - */ - public function setMakerUrl($maker_url) + public function setMakerUrl(?string $maker_url): self { $this->maker_url = $maker_url; + + return $this; } } diff --git a/Form/Extension/ProductTypeExtension.php b/Form/Extension/ProductTypeExtension.php index 1cc053f..75aa2d5 100644 --- a/Form/Extension/ProductTypeExtension.php +++ b/Form/Extension/ProductTypeExtension.php @@ -11,12 +11,12 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Form\Extension; +namespace Plugin\Maker44\Form\Extension; use Eccube\Common\EccubeConfig; use Eccube\Form\Type\Admin\ProductType; -use Plugin\Maker42\Entity\Maker; -use Plugin\Maker42\Repository\MakerRepository; +use Plugin\Maker44\Entity\Maker; +use Plugin\Maker44\Repository\MakerRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -25,32 +25,13 @@ class ProductTypeExtension extends AbstractTypeExtension { - /** - * @var EccubeConfig - */ - private $eccubeConfig; - - /** - * @var MakerRepository - */ - protected $makerRepository; - - /** - * ProductTypeExtension constructor. - * - * @param EccubeConfig $eccubeConfig - * @param MakerRepository $makerRepository - */ - public function __construct(EccubeConfig $eccubeConfig, MakerRepository $makerRepository) - { - $this->eccubeConfig = $eccubeConfig; - $this->makerRepository = $makerRepository; + public function __construct( + private EccubeConfig $eccubeConfig, + protected MakerRepository $makerRepository, + ) { } - /** - * {@inheritdoc} - */ - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('Maker', EntityType::class, [ @@ -75,19 +56,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) ], 'constraints' => [ new Assert\Url(), - new Assert\Length(['max' => $this->eccubeConfig['eccube_url_len']]), + new Assert\Length(max: $this->eccubeConfig['eccube_url_len']), ], ]); } - /** - * {@inheritdoc} - */ - public function getExtendedType() - { - return ProductType::class; - } - /** * Return the class of the type being extended. */ diff --git a/Form/Type/MakerType.php b/Form/Type/MakerType.php index 83ed103..772cc64 100644 --- a/Form/Type/MakerType.php +++ b/Form/Type/MakerType.php @@ -11,7 +11,7 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Form\Type; +namespace Plugin\Maker44\Form\Type; use Eccube\Common\EccubeConfig; use Symfony\Component\Form\AbstractType; @@ -25,28 +25,15 @@ */ class MakerType extends AbstractType { - /** - * @var EccubeConfig - */ - private $eccubeConfig; - - /** - * MakerType constructor. - * - * @param EccubeConfig $eccubeConfig - */ - public function __construct(EccubeConfig $eccubeConfig) - { - $this->eccubeConfig = $eccubeConfig; + public function __construct( + private EccubeConfig $eccubeConfig, + ) { } /** * Build config type form. - * - * @param FormBuilderInterface $builder - * @param array $options */ - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('name', TextType::class, [ @@ -56,15 +43,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) ], 'constraints' => [ new Assert\NotBlank(), - new Assert\Length(['min' => 1, 'max' => $this->eccubeConfig['eccube_stext_len']]), + new Assert\Length(min: 1, max: $this->eccubeConfig['eccube_stext_len']), ], ]); } - /** - * {@inheritdoc} - */ - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { } } diff --git a/MakerEvent.php b/MakerEvent.php index 80fb6f8..a3375f8 100644 --- a/MakerEvent.php +++ b/MakerEvent.php @@ -11,19 +11,17 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42; +namespace Plugin\Maker44; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Eccube\Event\TemplateEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; class MakerEvent implements EventSubscriberInterface { /** * {@inheritdoc} - * - * @return array */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ 'Product/detail.twig' => ['onTemplateProductDetail', 10], @@ -32,11 +30,9 @@ public static function getSubscribedEvents() /** * Append JS to display maker - * - * @param TemplateEvent $templateEvent */ - public function onTemplateProductDetail(TemplateEvent $templateEvent) + public function onTemplateProductDetail(TemplateEvent $templateEvent): void { - $templateEvent->addSnippet('@Maker42/default/maker.twig'); + $templateEvent->addSnippet('@Maker44/default/maker.twig'); } } diff --git a/MakerNav.php b/MakerNav.php index 3989054..916d130 100644 --- a/MakerNav.php +++ b/MakerNav.php @@ -11,7 +11,7 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42; +namespace Plugin\Maker44; use Eccube\Common\EccubeNav; @@ -19,10 +19,8 @@ class MakerNav implements EccubeNav { /** * {@inheritdoc} - * - * @return array */ - public static function getNav() + public static function getNav(): array { return [ 'product' => [ diff --git a/PluginManager.php b/PluginManager.php index 5072948..074aed1 100644 --- a/PluginManager.php +++ b/PluginManager.php @@ -11,7 +11,7 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42; +namespace Plugin\Maker44; use Eccube\Plugin\AbstractPluginManager; diff --git a/Repository/MakerRepository.php b/Repository/MakerRepository.php index 0450d39..b0851db 100644 --- a/Repository/MakerRepository.php +++ b/Repository/MakerRepository.php @@ -11,43 +11,31 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Repository; +namespace Plugin\Maker44\Repository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Plugin\Maker42\Entity\Maker; use Doctrine\Persistence\ManagerRegistry; +use Plugin\Maker44\Entity\Maker; /** * Maker. * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. + * + * @extends ServiceEntityRepository */ class MakerRepository extends ServiceEntityRepository { - /** - * MakerRepository constructor. - * - * @param ManagerRegistry $registry - * @param string $entityClass - */ - public function __construct(ManagerRegistry $registry, $entityClass = Maker::class) + public function __construct(ManagerRegistry $registry, string $entityClass = Maker::class) { parent::__construct($registry, $entityClass); } /** * Maker create/update - * - * @param $Maker - * - * @return bool - * - * @throws \Doctrine\ORM\NoResultException - * @throws \Doctrine\ORM\NonUniqueResultException - * @throws \Doctrine\ORM\OptimisticLockException */ - public function save($Maker) + public function save(Maker $Maker): void { $em = $this->getEntityManager(); @@ -71,49 +59,16 @@ public function save($Maker) } $em->persist($Maker); - $em->flush($Maker); - - return true; + $em->flush(); } /** * Delete maker. - * - * @param $Maker - * - * @throws \Doctrine\ORM\OptimisticLockException */ - public function delete($Maker) + public function delete(Maker $Maker): void { $em = $this->getEntityManager(); $em->remove($Maker); - $em->flush($Maker); - } - - /** - * Move sortNo. - * - * @param array $sortNos - * - * @return array - * - * @throws \Doctrine\ORM\OptimisticLockException - */ - public function moveSortNo(array $sortNos) - { - $results = []; - foreach ($sortNos as $id => $sortNo) { - /* @var $Maker Maker */ - $Maker = $this->find($id); - if ($Maker->getSortNo() == $sortNo) { - continue; - } - $results[$id] = $sortNos; - $Maker->setSortNo($sortNo); - $this->getEntityManager()->persist($Maker); - $this->getEntityManager()->flush($Maker); - } - - return $results; + $em->flush(); } } diff --git a/Resource/.php-cs-fixer.dist.php b/Resource/.php-cs-fixer.dist.php new file mode 100644 index 0000000..043d980 --- /dev/null +++ b/Resource/.php-cs-fixer.dist.php @@ -0,0 +1,57 @@ + true, + 'array_syntax' => ['syntax' => 'short'], + 'phpdoc_align' => false, + 'phpdoc_summary' => false, + 'phpdoc_annotation_without_dot' => false, + 'no_superfluous_phpdoc_tags' => false, + 'increment_style' => false, + 'yoda_style' => false, + 'header_comment' => ['header' => $header], + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_param_order' => true, + 'phpdoc_to_comment' => false, // /** @var */ を変換してしまうため + 'phpdoc_trim' => true, + 'global_namespace_import' => [ + 'import_classes' => false, + 'import_constants' => false, + 'import_functions' => false, + ], + // PHPDocの型をネイティブ型へ + 'phpdoc_to_param_type' => true, + 'phpdoc_to_return_type' => true, + // プロパティのネイティブ型化は無効。EC-CUBE のテスト基盤が tearDown で全プロパティに + // null を代入するため、テストプロパティを非null native 型にすると TypeError になる。 + 'phpdoc_to_property_type' => false, +]; + +$finder = \PhpCsFixer\Finder::create() + ->in(dirname(__DIR__)) + ->exclude(['vendor', 'node_modules', 'Resource']) + ->name('*.php') +; +$config = new \PhpCsFixer\Config(); + +return $config + ->setRules($rules) + ->setFinder($finder) + ->setRiskyAllowed(true) + ->setUnsupportedPhpVersionAllowed(true) +; diff --git a/Resource/rector.php b/Resource/rector.php new file mode 100644 index 0000000..38b5682 --- /dev/null +++ b/Resource/rector.php @@ -0,0 +1,62 @@ +withPhpVersion(PhpVersion::PHP_82) + // プラグインのソースディレクトリ + ->withPaths([ + dirname(__DIR__).'/Controller', + dirname(__DIR__).'/Entity', + dirname(__DIR__).'/Form', + dirname(__DIR__).'/Repository', + dirname(__DIR__).'/PluginManager.php', + dirname(__DIR__).'/MakerEvent.php', + dirname(__DIR__).'/MakerNav.php', + ]) + ->withSkip([ + dirname(__DIR__).'/vendor', + dirname(__DIR__).'/node_modules', + ]) + ->withSets([ + LevelSetList::UP_TO_PHP_82, + // Symfony 7.4 対応 (@Route → #[Route], @Template, buildForm(): void 等) + SymfonySetList::SYMFONY_74, + SymfonySetList::SYMFONY_CODE_QUALITY, + // Doctrine ORM 3 対応 (@ORM → #[ORM], 型付きプロパティ)。 + // DBAL は 4.x だが rector に DBAL4 用セットが無いため DBAL30 セットを流用する。 + DoctrineSetList::DOCTRINE_CODE_QUALITY, + DoctrineSetList::DOCTRINE_DBAL_30, + DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES, + ]) + // Symfony/Doctrine 等のアノテーション → アトリビュート変換を有効化 + ->withAttributesSets() + // #[Route] は付与されるが use 文が旧 Annotation のまま残るため Attribute へ統一する + ->withConfiguredRule(RenameClassRector::class, [ + 'Symfony\Component\Routing\Annotation\Route' => 'Symfony\Component\Routing\Attribute\Route', + ]) + ->withImportNames( + importShortClasses: false, + importDocBlockNames: true, + importNames: true + ) + ->withParallel(); diff --git a/Tests/Web/Admin/MakerControllerTest.php b/Tests/Web/Admin/MakerControllerTest.php index e748db0..7ce0be7 100644 --- a/Tests/Web/Admin/MakerControllerTest.php +++ b/Tests/Web/Admin/MakerControllerTest.php @@ -11,13 +11,13 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Tests\Web\Admin; +namespace Plugin\Maker44\Tests\Web\Admin; use Eccube\Common\Constant; use Faker\Generator; -use Plugin\Maker42\Tests\Web\MakerWebCommon; +use Plugin\Maker44\Repository\MakerRepository; +use Plugin\Maker44\Tests\Web\MakerWebCommon; use Symfony\Component\DomCrawler\Crawler; -use Plugin\Maker42\Repository\MakerRepository; /** * Class MakerControllerTest. @@ -43,7 +43,7 @@ protected function setUp(): void /** * Test render maker. */ - public function testMakerRender() + public function testMakerRender(): void { $crawler = $this->client->request('GET', $this->generateUrl('maker_admin_index')); $this->assertEquals(0, $crawler->filter('.sortable-item')->count()); @@ -52,7 +52,7 @@ public function testMakerRender() /** * Test maker list */ - public function testMakerList() + public function testMakerList(): void { $numberTest = 100; for ($i = 1; $i <= $numberTest; ++$i) { @@ -70,7 +70,7 @@ public function testMakerList() /** * Test maker create. */ - public function testMakerCreateNameIsEmpty() + public function testMakerCreateNameIsEmpty(): void { $formData = $this->createMakerFormData(); $formData['name'] = ''; @@ -86,7 +86,7 @@ public function testMakerCreateNameIsEmpty() /** * Test maker create. */ - public function testMakerCreateNameIsDuplicate() + public function testMakerCreateNameIsDuplicate(): void { // Exist maker $Maker = $this->createMaker(1); @@ -105,7 +105,7 @@ public function testMakerCreateNameIsDuplicate() /** * Test maker create. */ - public function testMakerCreate() + public function testMakerCreate(): void { $formData = $this->createMakerFormData(); $this->client->request( @@ -132,10 +132,10 @@ public function testMakerCreate() /** * Test maker edit. */ - public function testMakerInlineEditNameIsEmpty() + public function testMakerInlineEditNameIsEmpty(): void { $Maker = $this->createMaker(1); - $formData = $this->createMakerFormData($Maker->getId()); + $formData = $this->createMakerFormData(); $formData['name'] = ''; /** @@ -154,11 +154,11 @@ public function testMakerInlineEditNameIsEmpty() /** * Test maker edit. */ - public function testMakerInlineEditNameIsDuplicate() + public function testMakerInlineEditNameIsDuplicate(): void { $MakerBefore = $this->createMaker(1); $Maker = $this->createMaker(1); - $formData = $this->createMakerFormData($Maker->getId()); + $formData = $this->createMakerFormData(); $formData['name'] = $MakerBefore->getName(); @@ -175,31 +175,13 @@ public function testMakerInlineEditNameIsDuplicate() $this->assertStringContainsString('既に使用されています。', $crawler->filter('#formInline'.$Maker->getId().' .invalid-feedback')->html()); } -// /** -// * Test maker edit. -// */ -// public function testMakerEditIdIsNotFound() -// { -// $Maker = $this->createMaker(1); -// $editId = $Maker->getId() + 1; -// $formData = $this->createMakerFormData($editId); -// -// $this->client->request( -// 'POST', -// $this->generateUrl('maker_admin_index', ['id' => $editId]), -// ['_maker' => $formData] -// ); -// -// $this->assertEquals(404, $this->client->getResponse()->getStatusCode()); -// } - /** * Test maker edit. */ - public function testMakerInlineEdit() + public function testMakerInlineEdit(): void { $Maker = $this->createMaker(1); - $formData = $this->createMakerFormData($Maker->getId()); + $formData = $this->createMakerFormData(); $this->client->request( 'POST', @@ -222,7 +204,7 @@ public function testMakerInlineEdit() /** * Test maker delete. */ - public function testMakerDeleteGetMethod() + public function testMakerDeleteGetMethod(): void { $Maker = $this->createMaker(); @@ -237,7 +219,7 @@ public function testMakerDeleteGetMethod() /** * Test maker delete. */ - public function testMakerDeleteIdIsNull() + public function testMakerDeleteIdIsNull(): void { $this->client->request( 'DELETE', @@ -250,7 +232,7 @@ public function testMakerDeleteIdIsNull() /** * Test maker delete. */ - public function testMakerDeleteIdIsNotExist() + public function testMakerDeleteIdIsNotExist(): void { /** * @var Generator @@ -266,7 +248,7 @@ public function testMakerDeleteIdIsNotExist() /** * Test maker edit. */ - public function testMakerDelete() + public function testMakerDelete(): void { $Maker = $this->createMaker(); @@ -291,7 +273,7 @@ public function testMakerDelete() /** * Test rank move */ - public function testMoveRankTestIsNotPostAjax() + public function testMoveRankTestIsNotPostAjax(): void { $Maker01 = $this->createMaker(1); $oldSortNo = $Maker01->getSortNo(); @@ -324,7 +306,7 @@ public function testMoveRankTestIsNotPostAjax() /** * Move rank test */ - public function testMoveRank() + public function testMoveRank(): void { $Maker01 = $this->createMaker(1); $oldSortNo = $Maker01->getSortNo(); @@ -355,11 +337,9 @@ public function testMoveRank() /** * Create data form. * - * @param null $makerId - * - * @return array + * @return array */ - private function createMakerFormData($makerId = null) + private function createMakerFormData(): array { /** * @var Generator diff --git a/Tests/Web/Admin/ProductMakerTest.php b/Tests/Web/Admin/ProductMakerTest.php index 7877b7c..e98f10d 100644 --- a/Tests/Web/Admin/ProductMakerTest.php +++ b/Tests/Web/Admin/ProductMakerTest.php @@ -11,22 +11,23 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Tests\Web\Admin; +namespace Plugin\Maker44\Tests\Web\Admin; -use Faker\Generator; use Eccube\Common\Constant; -use Plugin\Maker42\Tests\Web\MakerWebCommon; -use Symfony\Component\HttpKernel\Client; -use Symfony\Component\DomCrawler\Crawler; use Eccube\Repository\ProductRepository; +use Faker\Generator; +use Plugin\Maker44\Tests\Web\MakerWebCommon; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Class ProductMakerTest. */ class ProductMakerTest extends MakerWebCommon { - const MAKER = 'Maker'; - const MAKER_URL = 'maker_url'; + public const MAKER = 'Maker'; + public const MAKER_URL = 'maker_url'; /** * @var int @@ -41,7 +42,7 @@ class ProductMakerTest extends MakerWebCommon /** * Set up function. */ - protected function setUp() :void + protected function setUp(): void { parent::setUp(); $this->deleteAllRows(['plg_maker']); @@ -52,7 +53,7 @@ protected function setUp() :void /** * Test render */ - public function testProductNewRender() + public function testProductNewRender(): void { $crawler = $this->client->request('GET', $this->generateUrl('admin_product_product_new')); $this->assertStringContainsString('メーカー', $crawler->filter('body .c-container')->html()); @@ -61,7 +62,7 @@ public function testProductNewRender() /** * Test new */ - public function testProductNewWithoutMaker() + public function testProductNewWithoutMaker(): void { $crawler = $this->client->request('GET', $this->generateUrl('admin_product_product_new')); $this->assertStringContainsString('メーカー', $crawler->filter('body .c-container')->html()); @@ -70,7 +71,7 @@ public function testProductNewWithoutMaker() /** * Test new */ - public function testProductNewWithMakerWithoutMakerSelect() + public function testProductNewWithMakerWithoutMakerSelect(): void { $Maker = $this->createMaker(); @@ -81,7 +82,7 @@ public function testProductNewWithMakerWithoutMakerSelect() /** * Test new */ - public function testProductNewWithAddMakerURLWithoutMakerSelect() + public function testProductNewWithAddMakerURLWithoutMakerSelect(): void { /** * @var Generator @@ -92,7 +93,7 @@ public function testProductNewWithAddMakerURLWithoutMakerSelect() $formData[self::MAKER_URL] = $faker->url; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -122,7 +123,7 @@ public function testProductNewWithAddMakerURLWithoutMakerSelect() /** * Test new */ - public function testProductNewWithAddMakerInvalid() + public function testProductNewWithAddMakerInvalid(): void { $Maker = $this->createMaker(); /** @@ -134,7 +135,7 @@ public function testProductNewWithAddMakerInvalid() $formData[self::MAKER_URL] = $faker->url; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $crawler = $client->request( @@ -161,7 +162,7 @@ public function testProductNewWithAddMakerInvalid() /** * Test new */ - public function testProductNewWithAddMakerWithoutMakerUrl() + public function testProductNewWithAddMakerWithoutMakerUrl(): void { $Maker = $this->createMaker(); @@ -174,7 +175,7 @@ public function testProductNewWithAddMakerWithoutMakerUrl() $formData[self::MAKER_URL] = ''; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -185,7 +186,9 @@ public function testProductNewWithAddMakerWithoutMakerUrl() $this->assertTrue($client->getResponse()->isRedirection()); - $arrTmp = explode('/', $client->getResponse()->getTargetUrl()); + $response = $client->getResponse(); + self::assertInstanceOf(RedirectResponse::class, $response); + $arrTmp = explode('/', $response->getTargetUrl()); $productId = $arrTmp[count($arrTmp) - 2]; $crawler = $client->followRedirect(); @@ -204,7 +207,7 @@ public function testProductNewWithAddMakerWithoutMakerUrl() /** * Test render */ - public function testProductNewWithAddMakerAndMakerUrlInValid() + public function testProductNewWithAddMakerAndMakerUrlInValid(): void { $Maker = $this->createMaker(); @@ -217,7 +220,7 @@ public function testProductNewWithAddMakerAndMakerUrlInValid() $formData[self::MAKER_URL] = $faker->word; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $crawler = $client->request( @@ -243,7 +246,7 @@ public function testProductNewWithAddMakerAndMakerUrlInValid() /** * Test new */ - public function testProductNewWithAddMakerAndMakerUrlSuccess() + public function testProductNewWithAddMakerAndMakerUrlSuccess(): void { $Maker = $this->createMaker(); @@ -256,7 +259,7 @@ public function testProductNewWithAddMakerAndMakerUrlSuccess() $formData[self::MAKER_URL] = $faker->url; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -267,7 +270,9 @@ public function testProductNewWithAddMakerAndMakerUrlSuccess() $this->assertTrue($client->getResponse()->isRedirection()); - $arrTmp = explode('/', $client->getResponse()->getTargetUrl()); + $response = $client->getResponse(); + self::assertInstanceOf(RedirectResponse::class, $response); + $arrTmp = explode('/', $response->getTargetUrl()); $productId = $arrTmp[count($arrTmp) - 2]; $crawler = $client->followRedirect(); @@ -286,7 +291,7 @@ public function testProductNewWithAddMakerAndMakerUrlSuccess() /** * Test render */ - public function testProductEditRender() + public function testProductEditRender(): void { $Product = $this->createProduct(); @@ -297,7 +302,7 @@ public function testProductEditRender() /** * Test render */ - public function testProductEditWithMaker() + public function testProductEditWithMaker(): void { $Product = $this->createProduct(); $Maker = $this->createMaker(); @@ -309,7 +314,7 @@ public function testProductEditWithMaker() /** * Test new */ - public function testProductEditWithAddMakerURLWithoutMakerSelect() + public function testProductEditWithAddMakerURLWithoutMakerSelect(): void { $this->createMaker(); @@ -323,7 +328,7 @@ public function testProductEditWithAddMakerURLWithoutMakerSelect() $formData[self::MAKER_URL] = ''; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -334,7 +339,9 @@ public function testProductEditWithAddMakerURLWithoutMakerSelect() $this->assertTrue($client->getResponse()->isRedirection()); - $arrTmp = explode('/', $client->getResponse()->getTargetUrl()); + $response = $client->getResponse(); + self::assertInstanceOf(RedirectResponse::class, $response); + $arrTmp = explode('/', $response->getTargetUrl()); $productId = $arrTmp[count($arrTmp) - 2]; $client->followRedirect(); @@ -345,7 +352,7 @@ public function testProductEditWithAddMakerURLWithoutMakerSelect() $formData[self::MAKER_URL] = $faker->url; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -371,7 +378,7 @@ public function testProductEditWithAddMakerURLWithoutMakerSelect() /** * Test Edit */ - public function testProductEditWithAddMakerInvalid() + public function testProductEditWithAddMakerInvalid(): void { $this->createMaker(); @@ -385,7 +392,7 @@ public function testProductEditWithAddMakerInvalid() $formData[self::MAKER_URL] = ''; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -396,7 +403,9 @@ public function testProductEditWithAddMakerInvalid() $this->assertTrue($client->getResponse()->isRedirection()); - $arrTmp = explode('/', $client->getResponse()->getTargetUrl()); + $response = $client->getResponse(); + self::assertInstanceOf(RedirectResponse::class, $response); + $arrTmp = explode('/', $response->getTargetUrl()); $productId = $arrTmp[count($arrTmp) - 2]; $client->followRedirect(); @@ -407,7 +416,7 @@ public function testProductEditWithAddMakerInvalid() $formData[self::MAKER_URL] = $faker->url; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $crawler = $client->request( @@ -435,7 +444,7 @@ public function testProductEditWithAddMakerInvalid() /** * Test Edit */ - public function testProductEditWithAddMakerWithoutMakerUrl() + public function testProductEditWithAddMakerWithoutMakerUrl(): void { $Maker = $this->createMaker(); @@ -445,7 +454,7 @@ public function testProductEditWithAddMakerWithoutMakerUrl() $formData[self::MAKER_URL] = ''; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -456,7 +465,9 @@ public function testProductEditWithAddMakerWithoutMakerUrl() $this->assertTrue($client->getResponse()->isRedirection()); - $arrTmp = explode('/', $client->getResponse()->getTargetUrl()); + $response = $client->getResponse(); + self::assertInstanceOf(RedirectResponse::class, $response); + $arrTmp = explode('/', $response->getTargetUrl()); $productId = $arrTmp[count($arrTmp) - 2]; $client->followRedirect(); @@ -467,7 +478,7 @@ public function testProductEditWithAddMakerWithoutMakerUrl() $formData[self::MAKER_URL] = ''; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -485,15 +496,16 @@ public function testProductEditWithAddMakerWithoutMakerUrl() // Check database $Product = $this->productRepository->findOneBy([], ['id' => 'DESC']); + // 空の任意 URL は 4.4 コアのフォーム挙動で null として保存される $this->actual = [$Product->getMaker()->getId(), $Product->getMakerUrl()]; - $this->expected = [$Maker->getId(), $formData[self::MAKER_URL]]; + $this->expected = [$Maker->getId(), null]; $this->verify(); } /** * Test render */ - public function testProductEditWithAddMakerAndMakerUrlInValid() + public function testProductEditWithAddMakerAndMakerUrlInValid(): void { $Maker = $this->createMaker(); @@ -507,7 +519,7 @@ public function testProductEditWithAddMakerAndMakerUrlInValid() $formData[self::MAKER_URL] = ''; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -518,7 +530,9 @@ public function testProductEditWithAddMakerAndMakerUrlInValid() $this->assertTrue($client->getResponse()->isRedirection()); - $arrTmp = explode('/', $client->getResponse()->getTargetUrl()); + $response = $client->getResponse(); + self::assertInstanceOf(RedirectResponse::class, $response); + $arrTmp = explode('/', $response->getTargetUrl()); $productId = $arrTmp[count($arrTmp) - 2]; $client->followRedirect(); @@ -551,7 +565,7 @@ public function testProductEditWithAddMakerAndMakerUrlInValid() /** * Test Edit */ - public function testProductEditWithAddMakerAndMakerUrlSuccess() + public function testProductEditWithAddMakerAndMakerUrlSuccess(): void { $Maker = $this->createMaker(); // New product @@ -564,7 +578,7 @@ public function testProductEditWithAddMakerAndMakerUrlSuccess() $formData[self::MAKER_URL] = ''; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -575,7 +589,9 @@ public function testProductEditWithAddMakerAndMakerUrlSuccess() $this->assertTrue($client->getResponse()->isRedirection()); - $arrTmp = explode('/', $client->getResponse()->getTargetUrl()); + $response = $client->getResponse(); + self::assertInstanceOf(RedirectResponse::class, $response); + $arrTmp = explode('/', $response->getTargetUrl()); $productId = $arrTmp[count($arrTmp) - 2]; $client->followRedirect(); @@ -586,7 +602,7 @@ public function testProductEditWithAddMakerAndMakerUrlSuccess() $formData[self::MAKER_URL] = $faker->url; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( diff --git a/Tests/Web/MakerWebCommon.php b/Tests/Web/MakerWebCommon.php index 952170e..9e513ec 100644 --- a/Tests/Web/MakerWebCommon.php +++ b/Tests/Web/MakerWebCommon.php @@ -11,12 +11,12 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Tests\Web; +namespace Plugin\Maker44\Tests\Web; use Eccube\Common\Constant; use Eccube\Tests\Web\Admin\AbstractAdminWebTestCase; use Faker\Generator; -use Plugin\Maker42\Entity\Maker; +use Plugin\Maker44\Entity\Maker; /** * Class MakerWebTestCase @@ -26,9 +26,9 @@ class MakerWebCommon extends AbstractAdminWebTestCase /** * Create product form to submit. * - * @return array + * @return array */ - protected function createFormData() + protected function createFormData(): array { $faker = $this->getFaker(); @@ -80,7 +80,7 @@ protected function createFormData() * * @return Maker */ - protected function createMaker($sortNo = null) + protected function createMaker(?int $sortNo = null): Maker { /** * @var Generator diff --git a/Tests/Web/ProductDetailTest.php b/Tests/Web/ProductDetailTest.php index 771811e..a03f606 100644 --- a/Tests/Web/ProductDetailTest.php +++ b/Tests/Web/ProductDetailTest.php @@ -11,13 +11,14 @@ * file that was distributed with this source code. */ -namespace Plugin\Maker42\Tests\Web; +namespace Plugin\Maker44\Tests\Web; use Eccube\Entity\Product; -use Faker\Generator; -use Plugin\Maker42\Entity\Maker; -use Symfony\Component\HttpKernel\Client; use Eccube\Repository\ProductRepository; +use Faker\Generator; +use Plugin\Maker44\Entity\Maker; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Class ProductDetailTest @@ -56,51 +57,25 @@ protected function setUp(): void /** * Product detail render test maker */ - public function testProductDetailWhenHasMakerButUnRegister() + public function testProductDetailWhenHasMakerButUnRegister(): void { $this->markTestSkipped('Skipped due to need include template on twig file manually'); - $productId = $this->Product->getId(); - $this->Product->setMaker(null); - $this->Product->setMakerUrl(null); - $this->entityManager->persist($this->Product); - $this->entityManager->flush(); - $crawler = $this->client->request('GET', $this->generateUrl('product_detail', ['id' => $productId])); - $html = $crawler->filter('.ec-productRole__profile')->html(); - $this->assertNotContains('メーカーコード', $html); - $this->assertNotContains('メーカーURL', $html); } /** * Product detail render test maker */ - public function testProductDetailWhenRegisterMakerWithoutMakerUrl() + public function testProductDetailWhenRegisterMakerWithoutMakerUrl(): void { $this->markTestSkipped('Skipped due to need include template on twig file manually'); - $productId = $this->Product->getId(); - $this->Product->setMakerUrl(''); - $this->entityManager->persist($this->Product); - $this->entityManager->flush(); - - $crawler = $this->client->request('GET', $this->generateUrl('product_detail', ['id' => $productId])); - - $html = $crawler->filter('.ec-productRole__profile')->html(); - $this->assertStringContainsString($this->Product->getMaker()->getName(), $html); - $this->assertNotContains('メーカーURL', $html); } /** * Product detail render test maker */ - public function testProductDetailWhenRegisterMakerAndMakerUrl() + public function testProductDetailWhenRegisterMakerAndMakerUrl(): void { $this->markTestSkipped('Skipped due to need include template on twig file manually'); - $productId = $this->Product->getId(); - - $crawler = $this->client->request('GET', $this->generateUrl('product_detail', ['id' => $productId])); - - $html = $crawler->filter('.ec-productRole__profile')->html(); - $this->assertStringContainsString($this->Product->getMaker()->getName(), $html); - $this->assertStringContainsString($this->Product->getMakerUrl(), $html); } /** @@ -111,7 +86,7 @@ public function testProductDetailWhenRegisterMakerAndMakerUrl() * * @return Product */ - protected function createProductMaker(Maker $Maker, $Product = null) + protected function createProductMaker(Maker $Maker, ?Product $Product = null): Product { /** * @var Generator @@ -129,7 +104,7 @@ protected function createProductMaker(Maker $Maker, $Product = null) $formData['maker_url'] = $faker->url; /** - * @var Client + * @var KernelBrowser */ $client = $this->client; $client->request( @@ -140,7 +115,9 @@ protected function createProductMaker(Maker $Maker, $Product = null) $this->assertTrue($client->getResponse()->isRedirection()); - $arrTmp = explode('/', $client->getResponse()->getTargetUrl()); + $response = $client->getResponse(); + self::assertInstanceOf(RedirectResponse::class, $response); + $arrTmp = explode('/', $response->getTargetUrl()); $productId = $arrTmp[count($arrTmp) - 2]; $client->followRedirect(); diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index 1731e07..d089d6f 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -15,5 +15,5 @@ $envFile = __DIR__.'/../../../../.env'; if (file_exists($envFile)) { - (new \Symfony\Component\Dotenv\Dotenv())->load($envFile); + (new Symfony\Component\Dotenv\Dotenv())->load($envFile); } diff --git a/composer.json b/composer.json index 5ae0727..663c648 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,12 @@ { - "name": "ec-cube/maker42", - "version": "4.3.0", + "name": "ec-cube/maker44", + "version": "4.4.0", "description": "メーカー管理プラグイン", "type": "eccube-plugin", "require": { "ec-cube/plugin-installer": "^2.0" }, "extra": { - "code": "Maker42" + "code": "Maker44" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..b240950 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,25 @@ +parameters: + level: 6 + paths: + - . + # ProductTrait は #[EntityExtension] で Eccube\Entity\Product に getMaker()/getMakerUrl() を追加するが、 + # それらは実行時に生成される proxy (app/proxy/entity) 上にしか存在しない。テストが $Product->getMaker() + # を呼ぶため、proxy をシンボル解決用にスキャンする(解析対象=報告対象にはしないので他プラグインの proxy で落ちない)。 + scanDirectories: + - ../../../app/proxy/entity + excludePaths: + - Resource/* + - Tests/bootstrap.php + doctrine: + objectManagerLoader: ../../../tests/object-manager.php + ignoreErrors: + # ProductTrait は proxy 経由でのみ use されるため、静的解析上は「未使用トレイト」に見えるが実際は使われている。 + - + identifier: trait.unused + path: Entity/ProductTrait.php + # Entity は「未初期化アクセス」防止のため型付きプロパティを ?type = null にしているが、 + # 該当カラムは DB 上 not-null(既定値あり)。実行時は Doctrine が必ず値を入れるため実害はない。 + # 本体コアの Entity も同様の nullable プロパティ + not-null カラムで columnType を許容している。 + - + message: '#type mapping mismatch: property can contain .+ but database expects .+#' + identifier: doctrine.columnType diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c77d321..232c15f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,20 +1,20 @@ + + + - - - - - - - + + + + @@ -24,26 +24,19 @@ - - - - - + + + ./ - - ./Tests - ./Resource - ./PluginManager.php - - - + + + ./Tests + ./Resource + ./PluginManager.php + + - - - - + + +