Skip to content

ApiPlatform 3.0 breaks with Money #4965

Description

@NotionCommotion

API Platform version(s) affected: 3.0

Description
I recently tried upgrading to api-platform 3.0 and none of my entities were shown in the open-api documentation and got the message "No operations defined in spec!"

I later found that tbbc/money-bundle was responsible and when removing "tbbc/money-bundle": "^5.0" from composer.json, all was good.

How to reproduce
Create a minimum ApiPlatform installation and execute composer require tbbc/money-bundle.
Note that I haven't tested it using a dockers installation but only a composer install.

Possible Solution
ApiPlatformExtension::getBundlesResourcesPaths() appears to return bundle paths (i.e. not application paths) which might be an ApiResource.

ApiPlatformExtension::getResourcesToWatch() appears to return both bundle paths and application paths which might be an ApiResource.

If ApiPlatformExtension::getBundlesResourcesPaths() returns any paths, then ApiPlatformExtension::getResourcesToWatch() incorrectly does not return any application paths per below.

final class ApiPlatformExtension extends Extension implements PrependExtensionInterface
{
	...
	private function getResourcesToWatch(ContainerBuilder $container, array $config): array
    {
        $paths = array_unique(array_merge($this->getBundlesResourcesPaths($container, $config), $config['mapping']['paths']));

        // Default paths
        if (!$paths) {
            $projectDir = $container->getParameter('kernel.project_dir');
            foreach (["$projectDir/config/api_platform", "$projectDir/src/ApiResource", "$projectDir/src/Document", "$projectDir/src/Entity"] as $dir) {
                if (is_dir($dir)) {
                    $paths[] = $dir;
                }
            }
        }
		...
	}
	...
}

To fix the problem, ApiPlatformExtension::getResourcesToWatch() must be changed as shown:

final class ApiPlatformExtension extends Extension implements PrependExtensionInterface
{
	...
	private function getResourcesToWatch(ContainerBuilder $container, array $config): array
    {
        $paths = array_unique(array_merge($this->getBundlesResourcesPaths($container, $config), $config['mapping']['paths']));

        // Default paths
        //if (!$paths) {
            $projectDir = $container->getParameter('kernel.project_dir');
            foreach (["$projectDir/config/api_platform", "$projectDir/src/ApiResource", "$projectDir/src/Document", "$projectDir/src/Entity"] as $dir) {
                if (is_dir($dir)) {
                    $paths[] = $dir;
                }
            }
        //}
		...
	}
	...
}

Additional Context

Tring to determine the cause, I found that AttributesResourceNameCollectionFactory::__construct() was being passed [/var/www/facdocs/api2/vendor/tbbc/money-bundle/src/Entity] when having money installed and [/var/www/facdocs/api2/src/ApiResource, /var/www/facdocs/api2/src/Entity]when it wasn't.

<?php

namespace ApiPlatform\Metadata\Resource\Factory;

final class AttributesResourceNameCollectionFactory implements ResourceNameCollectionFactoryInterface
{
    public function __construct(private readonly array $paths, private readonly ?ResourceNameCollectionFactoryInterface $decorated = null)
    {
		w/o money, $path is [/var/www/facdocs/api2/src/ApiResource, /var/www/facdocs/api2/src/Entity]
		w/ money, $path is [/var/www/facdocs/api2/vendor/tbbc/money-bundle/src/Entity]
    }
}

Next I looked App_KernelDevDebugContainer which is sourcing AttributesResourceNameCollectionFactory::__construct().

AttributesResourceNameCollectionFactory_construct_with_money

I then looked at the generated App_KernelDevDebugContainer under the two scenarios and found the following differences (full files are at the bottom of this post).

michael@michael-HP-EliteBook-830-G5:/var/www/facdocs/api2$ diff App_KernelDevDebugContainer_with_money.php App_KernelDevDebugContainer_without_money.php
3c3
< namespace Container6vIlWzN;
---
> namespace ContainerIRTbYPQ;
70,77d69
<             'tbbc_money.formatter.money_formatter' => 'getTbbcMoney_Formatter_MoneyFormatterService',
<             'tbbc_money.money_manager' => 'getTbbcMoney_MoneyManagerService',
<             'tbbc_money.pair.csv_storage' => 'getTbbcMoney_Pair_CsvStorageService',
<             'tbbc_money.pair_manager' => 'getTbbcMoney_PairManagerService',
<             'tbbc_money.ratio_provider.google' => 'getTbbcMoney_RatioProvider_GoogleService',
<             'tbbc_money.ratio_provider.yahoo_finance' => 'getTbbcMoney_RatioProvider_YahooFinanceService',
<             'tbbc_money.twig.currency' => 'getTbbcMoney_Twig_CurrencyService',
<             'tbbc_money.twig.money' => 'getTbbcMoney_Twig_MoneyService',
530a523
>         $b->addDriver(($this->privates['doctrine.orm.default_attribute_metadata_driver'] ?? ($this->privates['doctrine.orm.default_attribute_metadata_driver'] = new \Doctrine\ORM\Mapping\Driver\AttributeDriver([0 => (\dirname(__DIR__, 4).'/src/Entity')]))), 'App\\Entity');
532,537c525
<         $c = ($this->privates['doctrine.orm.default_attribute_metadata_driver'] ?? ($this->privates['doctrine.orm.default_attribute_metadata_driver'] = new \Doctrine\ORM\Mapping\Driver\AttributeDriver([0 => (\dirname(__DIR__, 4).'/src/Entity'), 1 => (\dirname(__DIR__, 4).'/vendor/tbbc/money-bundle/src/Entity')])));
<
<         $b->addDriver($c, 'App\\Entity');
<         $b->addDriver($c, 'Tbbc\\MoneyBundle\\Entity');
<
<         $a->setEntityNamespaces(['App' => 'App\\Entity', 'TbbcMoneyBundle' => 'Tbbc\\MoneyBundle\\Entity']);
---
>         $a->setEntityNamespaces(['App' => 'App\\Entity']);
1488d1475
<         $instance->addXmlMappings([0 => (\dirname(__DIR__, 4).'/vendor/symfony/form/Resources/config/validation.xml')]);
1623d1609
<                 'TbbcMoneyBundle' => 'Tbbc\\MoneyBundle\\TbbcMoneyBundle',
1658,1661d1643
<                 'TbbcMoneyBundle' => [
<                     'path' => (\dirname(__DIR__, 4).'/vendor/tbbc/money-bundle/src'),
<                     'namespace' => 'Tbbc\\MoneyBundle',
<                 ],
1670,1674d1651
<                 'Symfony\\Component\\Form\\Event\\PreSubmitEvent' => 'form.pre_submit',
<                 'Symfony\\Component\\Form\\Event\\SubmitEvent' => 'form.submit',
<                 'Symfony\\Component\\Form\\Event\\PostSubmitEvent' => 'form.post_submit',
<                 'Symfony\\Component\\Form\\Event\\PreSetDataEvent' => 'form.pre_set_data',
<                 'Symfony\\Component\\Form\\Event\\PostSetDataEvent' => 'form.post_set_data',
1717,1718d1693
<             'form.type_extension.csrf.enabled' => true,
<             'form.type_extension.csrf.field_name' => '_token',
1896c1871,1872
<                 0 => (\dirname(__DIR__, 4).'/vendor/tbbc/money-bundle/src/Entity'),
---
>                 0 => (\dirname(__DIR__, 4).'/src/ApiResource'),
>                 1 => (\dirname(__DIR__, 4).'/src/Entity'),
1938,1964d1913
<             'tbbc_money.pair_manager.class' => 'Tbbc\\MoneyBundle\\Pair\\PairManager',
<             'tbbc_money.pair_manager_interface.class' => 'Tbbc\\MoneyBundle\\Pair\\PairManagerInterface',
<             'tbbc_money.money_manager.class' => 'Tbbc\\MoneyBundle\\Money\\MoneyManager',
<             'tbbc_money.pair_history_manager.class' => 'Tbbc\\MoneyBundle\\PairHistory\\PairHistoryManager',
<             'tbbc_money.pair.csv_storage.class' => 'Tbbc\\MoneyBundle\\Pair\\Storage\\CsvStorage',
<             'tbbc_money.pair_manager.ratio_file_name' => (\dirname(__DIR__, 4).'/../data/tbbc_money/ratio_file_name.csv'),
<             'tbbc_money.ratio_provider.yahoo_finance.class' => 'Tbbc\\MoneyBundle\\Pair\\RatioProvider\\YahooFinanceRatioProvider',
<             'tbbc_money.ratio_provider.google.class' => 'Tbbc\\MoneyBundle\\Pair\\RatioProvider\\GoogleRatioProvider',
<             'tbbc_money.ratio_provider.ecb.class' => 'Tbbc\\MoneyBundle\\Pair\\RatioProvider\\ECBRatioProvider',
<             'tbbc_money.formatter.money_formatter.class' => 'Tbbc\\MoneyBundle\\Formatter\\MoneyFormatter',
<             'tbbc_money.command.ratio_fetch.class' => 'Tbbc\\MoneyBundle\\Command\\RatioFetchCommand',
<             'tbbc_money.command.ratio_list.class' => 'Tbbc\\MoneyBundle\\Command\\RatioListCommand',
<             'tbbc_money.command.ratio_save.class' => 'Tbbc\\MoneyBundle\\Command\\RatioSaveCommand',
<             'tbbc_money.form_type.money.class' => 'Tbbc\\MoneyBundle\\Form\\Type\\MoneyType',
<             'tbbc_money.form_type.currency.class' => 'Tbbc\\MoneyBundle\\Form\\Type\\CurrencyType',
<             'tbbc_money.form_type.simple_money.class' => 'Tbbc\\MoneyBundle\\Form\\Type\\SimpleMoneyType',
<             'tbbc_money.twig.money.class' => 'Tbbc\\MoneyBundle\\Twig\\Extension\\MoneyExtension',
<             'tbbc_money.twig.currency.class' => 'Tbbc\\MoneyBundle\\Twig\\Extension\\CurrencyExtension',
<             'tbbc_money.currencies' => [
<                 0 => 'USD',
<                 1 => 'EUR',
<             ],
<             'tbbc_money.reference_currency' => 'EUR',
<             'tbbc_money.decimals' => 2,
<             'tbbc_money.enable_pair_history' => false,
<             'tbbc_money.ratio_provider' => 'tbbc_money.ratio_provider.ecb',
<             'tbbc_money.pair.storage' => 'csv',
michael@michael-HP-EliteBook-830-G5:/var/www/facdocs/api2$

Not positive but looks like this script is the culprit:

namespace Container6vIlWzN;
class App_KernelDevDebugContainer extends Container
{
    ...
    protected function getDefaultParameters(): array
    {
		return [
			...
			// with money bundle.
			'api_platform.resource_class_directories' => [
				0 => (\dirname(__DIR__, 4).'/vendor/tbbc/money-bundle/src/Entity'),
			],
			// instead of without money bundle installed.
			'api_platform.resource_class_directories' => [
				0 => (\dirname(__DIR__, 4).'/src/ApiResource'),
				1 => (\dirname(__DIR__, 4).'/src/Entity'),
			],
			...
		];
    }
    ...
}

Tried a little to determine how Symfony creates this file but way on uncharted territory now.

Is this an api-platform issue or a tbbc/money-bundle issue? If an api-platform issue, are you able to fix? If a tbbc/money-bundle issue, any recommendations what I should tell them so they can fix it on their side?

Thanks in advance for a response, and more so thanks for building ApiPlatform 3.0!

Reference Only Information - Full App_KernelDevDebugContainer of both scenarios
Turns out it is too long and exceeds 65536 characters. If helpful, I can send it via different means,.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions