phpstan-magento: Generated class ProductExtension is passed on incorrectly to phpstan

Hi there! Just updated from v0.23.1 (and phpstan 1.7) to v0.24.0 (and phpstan 1.8). Since the update I’m getting the following error when running phpstan:

Child process error (exit code 255): PHP Fatal error:  Declaration of      
     Magento\Catalog\Api\Data\ProductExtension::setWebsiteIds(?array            
     $websiteIds) must be compatible with                                       
     Magento\Catalog\Api\Data\ProductExtensionInterface::setWebsiteIds($websit  
     eIds) in                                                                   
     /tmp/phpstan/cache/PHPStan/95/c3/95c3cd23c340083ec99b41a2ad173115a2e32d1c  
     .php on line 109                                                           
     Fatal error: Declaration of                                                
     Magento\Catalog\Api\Data\ProductExtension::setWebsiteIds(?array            
     $websiteIds) must be compatible with                                       
     Magento\Catalog\Api\Data\ProductExtensionInterface::setWebsiteIds($websit  
     eIds) in                                                                   
     /tmp/phpstan/cache/PHPStan/95/c3/95c3cd23c340083ec99b41a2ad173115a2e32d1c  
     .php on line 109             

Those are two generated classes from Magento: ProductExtensionInterface

<?php
namespace Magento\Catalog\Api\Data;

/**
 * ExtensionInterface class for @see \Magento\Catalog\Api\Data\ProductInterface
 */
interface ProductExtensionInterface extends \Magento\Framework\Api\ExtensionAttributesInterface
{
    /**
     * @return int[]|null
     */
    public function getWebsiteIds();

    /**
     * @param int[] $websiteIds
     * @return $this
     */
    public function setWebsiteIds($websiteIds);

ProductExtension

<?php
namespace Magento\Catalog\Api\Data;

/**
 * Extension class for @see \Magento\Catalog\Api\Data\ProductInterface
 */
class ProductExtension extends \Magento\Framework\Api\AbstractSimpleObject implements ProductExtensionInterface
{
    /**
     * @return int[]|null
     */
    public function getWebsiteIds()
    {
        return $this->_get('website_ids');
    }

    /**
     * @param int[] $websiteIds
     * @return $this
     */
    public function setWebsiteIds($websiteIds)
    {
        $this->setData('website_ids', $websiteIds);
        return $this;
    }

As we can see both classes are generated correctly. In the error message it says that Magento\Catalog\Api\Data\ProductExtension::setWebsiteIds(?array $websiteIds) must be compatible with its interface declaration but in the actual code there is no typed parameter ?array so it has to be somehow generated and passed to phpstan virtually.

FYI: I deleted the phpstan cache before running phpstan.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 30 (15 by maintainers)

Most upvoted comments

I’ve just published version 0.25.0 of the extension.

Thanks for the update @shochdoerfer

It fixed the issue for me. After upgrading to dev-master + clearing phpstan cache, error is gone.

@tszmyt apologies for this mess. I realize that the naive approach to simply generate the files based on the XML configuration is not enough. If extension interfaces already exist, we need to inspect them to see if typehints are used in the method declarations or not.

Unfortunately, the issue is still the same in my case, when trying it with your fix. @oneserv-heuser , yes, indeed, we do. Maybe that’s the reason? @shochdoerfer , I can not come this weekend, but @sprankhub will be there and will be happy to show you the code. Do you have any idea, what we could check, to make it easier to debug for you?

Here are the two classes ProductExtension and ProductExtensionInterface as generated as of Magento 2.4.4 and 2.4.5:

Magento 2.4.4

ProductExtensionInterface
<?php
namespace Magento\Catalog\Api\Data;

/**
* ExtensionInterface class for @see \Magento\Catalog\Api\Data\ProductInterface
*/
interface ProductExtensionInterface extends \Magento\Framework\Api\ExtensionAttributesInterface
{
  /**
   * @return int[]|null
   */
  public function getWebsiteIds();

  /**
   * @param int[] $websiteIds
   * @return $this
   */
  public function setWebsiteIds($websiteIds);

  /**
   * @return \Magento\Catalog\Api\Data\CategoryLinkInterface[]|null
   */
  public function getCategoryLinks();

  /**
   * @param \Magento\Catalog\Api\Data\CategoryLinkInterface[] $categoryLinks
   * @return $this
   */
  public function setCategoryLinks($categoryLinks);

  /**
   * @return \Magento\CatalogInventory\Api\Data\StockItemInterface|null
   */
  public function getStockItem();

  /**
   * @param \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem
   * @return $this
   */
  public function setStockItem(\Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem);

  /**
   * @return \Magento\Bundle\Api\Data\OptionInterface[]|null
   */
  public function getBundleProductOptions();

  /**
   * @param \Magento\Bundle\Api\Data\OptionInterface[] $bundleProductOptions
   * @return $this
   */
  public function setBundleProductOptions($bundleProductOptions);

  /**
   * @return \Magento\Downloadable\Api\Data\LinkInterface[]|null
   */
  public function getDownloadableProductLinks();

  /**
   * @param \Magento\Downloadable\Api\Data\LinkInterface[] $downloadableProductLinks
   * @return $this
   */
  public function setDownloadableProductLinks($downloadableProductLinks);

  /**
   * @return \Magento\Downloadable\Api\Data\SampleInterface[]|null
   */
  public function getDownloadableProductSamples();

  /**
   * @param \Magento\Downloadable\Api\Data\SampleInterface[] $downloadableProductSamples
   * @return $this
   */
  public function setDownloadableProductSamples($downloadableProductSamples);

  /**
   * @return \Magento\ConfigurableProduct\Api\Data\OptionInterface[]|null
   */
  public function getConfigurableProductOptions();

  /**
   * @param \Magento\ConfigurableProduct\Api\Data\OptionInterface[] $configurableProductOptions
   * @return $this
   */
  public function setConfigurableProductOptions($configurableProductOptions);

  /**
   * @return int[]|null
   */
  public function getConfigurableProductLinks();

  /**
   * @param int[] $configurableProductLinks
   * @return $this
   */
  public function setConfigurableProductLinks($configurableProductLinks);

  /**
   * @return int|null
   */
  public function getCalculationSchemaId();

  /**
   * @param int $calculationSchemaId
   * @return $this
   */
  public function setCalculationSchemaId($calculationSchemaId);
}

ProductExtension
<?php
namespace Magento\Catalog\Api\Data;

/**
* Extension class for @see \Magento\Catalog\Api\Data\ProductInterface
*/
class ProductExtension extends \Magento\Framework\Api\AbstractSimpleObject implements ProductExtensionInterface
{
  /**
   * @return int[]|null
   */
  public function getWebsiteIds()
  {
      return $this->_get('website_ids');
  }

  /**
   * @param int[] $websiteIds
   * @return $this
   */
  public function setWebsiteIds($websiteIds)
  {
      $this->setData('website_ids', $websiteIds);
      return $this;
  }

  /**
   * @return \Magento\Catalog\Api\Data\CategoryLinkInterface[]|null
   */
  public function getCategoryLinks()
  {
      return $this->_get('category_links');
  }

  /**
   * @param \Magento\Catalog\Api\Data\CategoryLinkInterface[] $categoryLinks
   * @return $this
   */
  public function setCategoryLinks($categoryLinks)
  {
      $this->setData('category_links', $categoryLinks);
      return $this;
  }

  /**
   * @return \Magento\CatalogInventory\Api\Data\StockItemInterface|null
   */
  public function getStockItem()
  {
      return $this->_get('stock_item');
  }

  /**
   * @param \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem
   * @return $this
   */
  public function setStockItem(\Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem)
  {
      $this->setData('stock_item', $stockItem);
      return $this;
  }

  /**
   * @return \Magento\Bundle\Api\Data\OptionInterface[]|null
   */
  public function getBundleProductOptions()
  {
      return $this->_get('bundle_product_options');
  }

  /**
   * @param \Magento\Bundle\Api\Data\OptionInterface[] $bundleProductOptions
   * @return $this
   */
  public function setBundleProductOptions($bundleProductOptions)
  {
      $this->setData('bundle_product_options', $bundleProductOptions);
      return $this;
  }

  /**
   * @return \Magento\Downloadable\Api\Data\LinkInterface[]|null
   */
  public function getDownloadableProductLinks()
  {
      return $this->_get('downloadable_product_links');
  }

  /**
   * @param \Magento\Downloadable\Api\Data\LinkInterface[] $downloadableProductLinks
   * @return $this
   */
  public function setDownloadableProductLinks($downloadableProductLinks)
  {
      $this->setData('downloadable_product_links', $downloadableProductLinks);
      return $this;
  }

  /**
   * @return \Magento\Downloadable\Api\Data\SampleInterface[]|null
   */
  public function getDownloadableProductSamples()
  {
      return $this->_get('downloadable_product_samples');
  }

  /**
   * @param \Magento\Downloadable\Api\Data\SampleInterface[] $downloadableProductSamples
   * @return $this
   */
  public function setDownloadableProductSamples($downloadableProductSamples)
  {
      $this->setData('downloadable_product_samples', $downloadableProductSamples);
      return $this;
  }

  /**
   * @return \Magento\ConfigurableProduct\Api\Data\OptionInterface[]|null
   */
  public function getConfigurableProductOptions()
  {
      return $this->_get('configurable_product_options');
  }

  /**
   * @param \Magento\ConfigurableProduct\Api\Data\OptionInterface[] $configurableProductOptions
   * @return $this
   */
  public function setConfigurableProductOptions($configurableProductOptions)
  {
      $this->setData('configurable_product_options', $configurableProductOptions);
      return $this;
  }

  /**
   * @return int[]|null
   */
  public function getConfigurableProductLinks()
  {
      return $this->_get('configurable_product_links');
  }

  /**
   * @param int[] $configurableProductLinks
   * @return $this
   */
  public function setConfigurableProductLinks($configurableProductLinks)
  {
      $this->setData('configurable_product_links', $configurableProductLinks);
      return $this;
  }

  /**
   * @return int|null
   */
  public function getCalculationSchemaId()
  {
      return $this->_get('calculation_schema_id');
  }

  /**
   * @param int $calculationSchemaId
   * @return $this
   */
  public function setCalculationSchemaId($calculationSchemaId)
  {
      $this->setData('calculation_schema_id', $calculationSchemaId);
      return $this;
  }
}

Magento 2.4.5

ProductExtensionInterface
<?php
namespace Magento\Catalog\Api\Data;

/**
* ExtensionInterface class for @see \Magento\Catalog\Api\Data\ProductInterface
*/
interface ProductExtensionInterface extends \Magento\Framework\Api\ExtensionAttributesInterface
{
  /**
   * @return int[]|null
   */
  public function getWebsiteIds();

  /**
   * @param int[] $websiteIds
   * @return $this
   */
  public function setWebsiteIds($websiteIds);

  /**
   * @return \Magento\Catalog\Api\Data\CategoryLinkInterface[]|null
   */
  public function getCategoryLinks();

  /**
   * @param \Magento\Catalog\Api\Data\CategoryLinkInterface[] $categoryLinks
   * @return $this
   */
  public function setCategoryLinks($categoryLinks);

  /**
   * @return \Magento\CatalogInventory\Api\Data\StockItemInterface|null
   */
  public function getStockItem();

  /**
   * @param \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem
   * @return $this
   */
  public function setStockItem(\Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem);

  /**
   * @return \Magento\Bundle\Api\Data\OptionInterface[]|null
   */
  public function getBundleProductOptions();

  /**
   * @param \Magento\Bundle\Api\Data\OptionInterface[] $bundleProductOptions
   * @return $this
   */
  public function setBundleProductOptions($bundleProductOptions);

  /**
   * @return \Magento\Downloadable\Api\Data\LinkInterface[]|null
   */
  public function getDownloadableProductLinks();

  /**
   * @param \Magento\Downloadable\Api\Data\LinkInterface[] $downloadableProductLinks
   * @return $this
   */
  public function setDownloadableProductLinks($downloadableProductLinks);

  /**
   * @return \Magento\Downloadable\Api\Data\SampleInterface[]|null
   */
  public function getDownloadableProductSamples();

  /**
   * @param \Magento\Downloadable\Api\Data\SampleInterface[] $downloadableProductSamples
   * @return $this
   */
  public function setDownloadableProductSamples($downloadableProductSamples);

  /**
   * @return \Magento\ConfigurableProduct\Api\Data\OptionInterface[]|null
   */
  public function getConfigurableProductOptions();

  /**
   * @param \Magento\ConfigurableProduct\Api\Data\OptionInterface[] $configurableProductOptions
   * @return $this
   */
  public function setConfigurableProductOptions($configurableProductOptions);

  /**
   * @return int[]|null
   */
  public function getConfigurableProductLinks();

  /**
   * @param int[] $configurableProductLinks
   * @return $this
   */
  public function setConfigurableProductLinks($configurableProductLinks);

  /**
   * @return \Magento\SalesRule\Api\Data\RuleDiscountInterface[]|null
   */
  public function getDiscounts();

  /**
   * @param \Magento\SalesRule\Api\Data\RuleDiscountInterface[] $discounts
   * @return $this
   */
  public function setDiscounts($discounts);

  /**
   * @return int|null
   */
  public function getCalculationSchemaId();

  /**
   * @param int $calculationSchemaId
   * @return $this
   */
  public function setCalculationSchemaId($calculationSchemaId);
}

ProductExtension
<?php
namespace Magento\Catalog\Api\Data;

/**
* Extension class for @see \Magento\Catalog\Api\Data\ProductInterface
*/
class ProductExtension extends \Magento\Framework\Api\AbstractSimpleObject implements ProductExtensionInterface
{
  /**
   * @return int[]|null
   */
  public function getWebsiteIds()
  {
      return $this->_get('website_ids');
  }

  /**
   * @param int[] $websiteIds
   * @return $this
   */
  public function setWebsiteIds($websiteIds)
  {
      $this->setData('website_ids', $websiteIds);
      return $this;
  }

  /**
   * @return \Magento\Catalog\Api\Data\CategoryLinkInterface[]|null
   */
  public function getCategoryLinks()
  {
      return $this->_get('category_links');
  }

  /**
   * @param \Magento\Catalog\Api\Data\CategoryLinkInterface[] $categoryLinks
   * @return $this
   */
  public function setCategoryLinks($categoryLinks)
  {
      $this->setData('category_links', $categoryLinks);
      return $this;
  }

  /**
   * @return \Magento\CatalogInventory\Api\Data\StockItemInterface|null
   */
  public function getStockItem()
  {
      return $this->_get('stock_item');
  }

  /**
   * @param \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem
   * @return $this
   */
  public function setStockItem(\Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem)
  {
      $this->setData('stock_item', $stockItem);
      return $this;
  }

  /**
   * @return \Magento\Bundle\Api\Data\OptionInterface[]|null
   */
  public function getBundleProductOptions()
  {
      return $this->_get('bundle_product_options');
  }

  /**
   * @param \Magento\Bundle\Api\Data\OptionInterface[] $bundleProductOptions
   * @return $this
   */
  public function setBundleProductOptions($bundleProductOptions)
  {
      $this->setData('bundle_product_options', $bundleProductOptions);
      return $this;
  }

  /**
   * @return \Magento\Downloadable\Api\Data\LinkInterface[]|null
   */
  public function getDownloadableProductLinks()
  {
      return $this->_get('downloadable_product_links');
  }

  /**
   * @param \Magento\Downloadable\Api\Data\LinkInterface[] $downloadableProductLinks
   * @return $this
   */
  public function setDownloadableProductLinks($downloadableProductLinks)
  {
      $this->setData('downloadable_product_links', $downloadableProductLinks);
      return $this;
  }

  /**
   * @return \Magento\Downloadable\Api\Data\SampleInterface[]|null
   */
  public function getDownloadableProductSamples()
  {
      return $this->_get('downloadable_product_samples');
  }

  /**
   * @param \Magento\Downloadable\Api\Data\SampleInterface[] $downloadableProductSamples
   * @return $this
   */
  public function setDownloadableProductSamples($downloadableProductSamples)
  {
      $this->setData('downloadable_product_samples', $downloadableProductSamples);
      return $this;
  }

  /**
   * @return \Magento\ConfigurableProduct\Api\Data\OptionInterface[]|null
   */
  public function getConfigurableProductOptions()
  {
      return $this->_get('configurable_product_options');
  }

  /**
   * @param \Magento\ConfigurableProduct\Api\Data\OptionInterface[] $configurableProductOptions
   * @return $this
   */
  public function setConfigurableProductOptions($configurableProductOptions)
  {
      $this->setData('configurable_product_options', $configurableProductOptions);
      return $this;
  }

  /**
   * @return int[]|null
   */
  public function getConfigurableProductLinks()
  {
      return $this->_get('configurable_product_links');
  }

  /**
   * @param int[] $configurableProductLinks
   * @return $this
   */
  public function setConfigurableProductLinks($configurableProductLinks)
  {
      $this->setData('configurable_product_links', $configurableProductLinks);
      return $this;
  }

  /**
   * @return \Magento\SalesRule\Api\Data\RuleDiscountInterface[]|null
   */
  public function getDiscounts()
  {
      return $this->_get('discounts');
  }

  /**
   * @param \Magento\SalesRule\Api\Data\RuleDiscountInterface[] $discounts
   * @return $this
   */
  public function setDiscounts($discounts)
  {
      $this->setData('discounts', $discounts);
      return $this;
  }

  /**
   * @return int|null
   */
  public function getCalculationSchemaId()
  {
      return $this->_get('calculation_schema_id');
  }

  /**
   * @param int $calculationSchemaId
   * @return $this
   */
  public function setCalculationSchemaId($calculationSchemaId)
  {
      $this->setData('calculation_schema_id', $calculationSchemaId);
      return $this;
  }
}

FYI: The functions getCalculationSchemaId and setCalculationSchemaId are added by us via ExtensionAttribute functionality.

Edit: Just did a text-compare, they are exactly the same, only the functions getDiscounts and setDiscounts are new, but they appear correctly in the Interface and the concrete class