Skip to content

Commit

Permalink
Merge pull request #63 from lfolco/option-builder
Browse files Browse the repository at this point in the history
Add builder for product attributes that have options
  • Loading branch information
schmengler authored Jan 11, 2021
2 parents 6d8f19b + 9c5d3a0 commit 6333145
Show file tree
Hide file tree
Showing 7 changed files with 722 additions and 0 deletions.
214 changes: 214 additions & 0 deletions src/Catalog/OptionBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<?php
declare(strict_types=1);

namespace TddWizard\Fixtures\Catalog;

use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
use Magento\Catalog\Model\Product;
use Magento\Eav\Api\AttributeOptionManagementInterface;
use Magento\Eav\Api\Data\AttributeOptionLabelInterface;
use Magento\Eav\Model\Entity\Attribute\Option as AttributeOption;
use Magento\TestFramework\Helper\Bootstrap;

/**
* Create a source-model option for an attribute.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class OptionBuilder
{

/**
* @var AttributeOptionManagementInterface
*/
private $optionManagement;

/**
* @var AttributeOption
*/
private $option;

/**
* @var AttributeOptionLabelInterface
*/
private $optionLabel;

/**
* @var string
*/
private $attributeCode;

/**
* OptionBuilder constructor.
*
* @param AttributeOptionManagementInterface $optionManagement
* @param AttributeOption $option
* @param AttributeOptionLabelInterface $optionLabel
* @param string $attributeCode
*/
public function __construct(
AttributeOptionManagementInterface $optionManagement,
AttributeOption $option,
AttributeOptionLabelInterface $optionLabel,
string $attributeCode
) {
$this->optionManagement = $optionManagement;
$this->option = $option;
$this->optionLabel = $optionLabel;
$this->attributeCode = $attributeCode;
}

/**
* Clone the builder.
*/
public function __clone()
{
$this->option = clone $this->option;
}

/**
* Create an option.
*
* @param string $attributeCode
* @return OptionBuilder
* @throws \Magento\Framework\Exception\InputException
* @throws \Magento\Framework\Exception\StateException
*/
public static function anOptionFor(string $attributeCode): OptionBuilder
{
$objectManager = Bootstrap::getObjectManager();
/** @var AttributeOptionManagementInterface $optionManagement */
$optionManagement = $objectManager->create(AttributeOptionManagementInterface::class);
$items = $optionManagement->getItems(Product::ENTITY, $attributeCode);

/** @var AttributeOptionLabelInterface $optionLabel */
$optionLabel = $objectManager->create(AttributeOptionLabelInterface::class);
$label = uniqid('Name ', true);
$optionLabel->setStoreId(0);
$optionLabel->setLabel($label);

/** @var AttributeOption $option */
$option = $objectManager->create(AttributeOption::class);
$option->setLabel($label);
$option->setStoreLabels([$optionLabel]);
$option->setSortOrder(count($items) + 1);
$option->setIsDefault(false);

return new static(
$optionManagement,
$option,
$optionLabel,
$attributeCode
);
}

/**
* Set label.
*
* @param string $label
* @return OptionBuilder
*/
public function withLabel(string $label): OptionBuilder
{
$builder = clone $this;
$builder->optionLabel->setLabel($label);
$builder->option->setStoreLabels([$builder->optionLabel]);
$builder->option->setLabel($label);

return $builder;
}

/**
* Set sort order.
*
* @param int $sortOrder
* @return OptionBuilder
*/
public function withSortOrder(int $sortOrder): OptionBuilder
{
$builder = clone $this;
$builder->option->setSortOrder($sortOrder);

return $builder;
}

/**
* Set default.
*
* @param bool $isDefault
* @return OptionBuilder
*/
public function withIsDefault(bool $isDefault): OptionBuilder
{
$builder = clone $this;
$builder->option->setIsDefault($isDefault);

return $builder;
}

/**
* Set store ID.
*
* @param int $storeId
* @return OptionBuilder
*/
public function withStoreId(int $storeId): OptionBuilder
{
$builder = clone $this;
$builder->optionLabel->setStoreId($storeId);

return $builder;
}

/**
* Build the option and apply it to the attribute.
*
* @return AttributeOption
* @throws \Magento\Framework\Exception\InputException
* @throws \Magento\Framework\Exception\StateException
*/
public function build(): AttributeOption
{
$builder = clone $this;

// add the option
$this->optionManagement->add(
\Magento\Catalog\Model\Product::ENTITY,
$builder->attributeCode,
$builder->option
);

$optionId = $this->getOptionId();
$builder->option->setId($optionId);

return $builder->option;
}

/**
* Get the option ID.
*
* @return int
*/
private function getOptionId(): int
{
$objectManager = Bootstrap::getObjectManager();
// the add option above does not return the option, so we need to retrieve it
$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class);
$attribute = $attributeRepository->get($this->attributeCode);
$attributeValues[$attribute->getAttributeId()] = [];

// We have to generate a new sourceModel instance each time through to prevent it from
// referencing its _options cache. No other way to get it to pick up newly-added values.
$tableFactory = $objectManager->get(\Magento\Eav\Model\Entity\Attribute\Source\TableFactory::class);
$sourceModel = $tableFactory->create();
$sourceModel->setAttribute($attribute);
foreach ($sourceModel->getAllOptions() as $option) {
$attributeValues[$attribute->getAttributeId()][$option['label']] = $option['value'];
}
if (isset($attributeValues[$attribute->getAttributeId()][$this->optionLabel->getLabel()])) {
return (int)$attributeValues[$attribute->getAttributeId()][$this->optionLabel->getLabel()];
}

throw new \RuntimeException('Error building option');
}
}
66 changes: 66 additions & 0 deletions src/Catalog/OptionFixture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);

namespace TddWizard\Fixtures\Catalog;

use Magento\Eav\Model\Entity\Attribute\Option as AttributeOption;
use Magento\Framework\Exception\LocalizedException;

/**
* Class OptionFixture
*/
class OptionFixture
{

/**
* @var string
*/
private $attributeCode;

/**
* @var AttributeOption
*/
private $option;

/**
* OptionFixture constructor.
*
* @param AttributeOption $option
* @param string $attributeCode
*/
public function __construct(AttributeOption $option, string $attributeCode)
{
$this->attributeCode = $attributeCode;
$this->option = $option;
}

/**
* Get the attribute code.
*
* @return string
*/
public function getAttributeCode(): string
{
return $this->attributeCode;
}

/**
* Get the option.
*
* @return AttributeOption
*/
public function getOption(): AttributeOption
{
return $this->option;
}

/**
* Rollback the option(s).
*
* @throws LocalizedException
*/
public function rollback(): void
{
OptionFixtureRollback::create()->execute($this);
}
}
51 changes: 51 additions & 0 deletions src/Catalog/OptionFixturePool.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);

namespace TddWizard\Fixtures\Catalog;

use Magento\Eav\Model\Entity\Attribute\Option as AttributeOption;
use function array_values as values;

/**
* Class OptionFixturePool
*/
class OptionFixturePool
{

/**
* @var OptionFixture[]
*/
private $optionFixtures = [];

public function add(AttributeOption $option, string $attributecode, string $key = null): void
{
if ($key === null) {
$this->optionFixtures[] = new OptionFixture($option, $attributecode);
} else {
$this->optionFixtures[$key] = new OptionFixture($option, $attributecode);
}
}

/**
* Returns option fixture by key, or last added if key not specified
*
* @param string|null $key
* @return OptionFixture
*/
public function get(string $key = null): OptionFixture
{
if ($key === null) {
$key = \array_key_last($this->optionFixtures);
}
if ($key === null || !\array_key_exists($key, $this->optionFixtures)) {
throw new \OutOfBoundsException('No matching option found in fixture pool');
}
return $this->optionFixtures[$key];
}

public function rollback(): void
{
OptionFixtureRollback::create()->execute(...values($this->optionFixtures));
$this->optionFixtures = [];
}
}
Loading

0 comments on commit 6333145

Please sign in to comment.