Magento 2 – Validate Condition Rules Based on Product Attribute

magento2product-attributerulesvalidation

How to validate a condition rules based on product attribute in magento 2

backend:

enter image description here

code:

    $product_id = '3';  // Crown Summit Backpack sku is 24-MB03
    $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
    $rules = $objectManager->create('Vendor\Module\Model\Rule')->getCollection();
    foreach ($rules as $rule) {
        $product = $objectManager->get('Magento\Catalog\Model\Product')->load($product_id);
        $item = $objectManager->create('Magento\Catalog\Model\Product');
        $item->setProduct($product);
        $validate = $rule->getActions()->validate($item);
    }
    var_dump($validate);
    var_dump($product);

Expected result is false but it always shows true. validation not done properly on product attributes.

var_dump($validate);

enter image description here

var_dump($product);

enter image description here

Vendor\Module\Model\Rule.php

<?php
namespace Vendor\Module\Model;
use Magento\Quote\Model\Quote\Address;
use Magento\Rule\Model\AbstractModel;
/**
 * Class Rule
 * @package Vendor\Module\Model
 *
 * @method int|null getRuleId()
 * @method Rule setRuleId(int $id)
 */
class Rule extends AbstractModel
{
    /**
     * Prefix of model events names
     *
     * @var string
     */
    protected $_eventPrefix = 'vendor_module';
    /**
     * Parameter name in event
     *
     * In observe method you can use $observer->getEvent()->getRule() in this case
     *
     * @var string
     */
    protected $_eventObject = 'rule';
    /** @var \Magento\SalesRule\Model\Rule\Condition\CombineFactory */
    protected $condCombineFactory;
    /** @var \Magento\SalesRule\Model\Rule\Condition\Product\CombineFactory */
    protected $condProdCombineF;
    /**
     * Store already validated addresses and validation results
     *
     * @var array
     */
    protected $validatedAddresses = [];
    /**
     * @param \Magento\Framework\Model\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param \Magento\Framework\Data\FormFactory $formFactory
     * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
     * @param \Magento\SalesRule\Model\Rule\Condition\CombineFactory $condCombineFactory
     * @param \Magento\SalesRule\Model\Rule\Condition\Product\CombineFactory $condProdCombineF
     * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
     * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
     * @param array $data
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        \Magento\Framework\Model\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Framework\Data\FormFactory $formFactory,
        \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
     \Magento\SalesRule\Model\Rule\Condition\CombineFactory $condCombineFactory,
     \Magento\SalesRule\Model\Rule\Condition\Product\CombineFactory $condProdCombineF,
       \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
        \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
        array $data = []
    ) {
        $this->condCombineFactory = $condCombineFactory;
        $this->condProdCombineF = $condProdCombineF;
        parent::__construct($context, $registry, $formFactory, $localeDate, $resource, $resourceCollection, $data);
    }
    /**
     * Set resource model and Id field name
     *
     * @return void
     */
    protected function _construct()
    {
        parent::_construct();
        $this->_init('Vendor\Module\Model\ResourceModel\Rule');
        $this->setIdFieldName('rule_id');
    }
    /**
     * Get rule condition combine model instance
     *
     * @return \Magento\SalesRule\Model\Rule\Condition\Combine
     */
      public function getConditionsInstance()
    {
        return $this->condCombineFactory->create();
    }

    /**
     * Get rule condition product combine model instance
     *
     * @return \Magento\SalesRule\Model\Rule\Condition\Product\Combine
     */
    public function getActionsInstance()
    {
        return $this->condProdCombineF->create();
    }
    /**
     * Check cached validation result for specific address
     *
     * @param Address $address
     * @return bool
     */
    public function hasIsValidForAddress($address)
    {
        $addressId = $this->_getAddressId($address);
        return isset($this->validatedAddresses[$addressId]) ? true : false;
    }
    /**
     * Set validation result for specific address to results cache
     *
     * @param Address $address
     * @param bool $validationResult
     * @return $this
     */
    public function setIsValidForAddress($address, $validationResult)
    {
        $addressId = $this->_getAddressId($address);
        $this->validatedAddresses[$addressId] = $validationResult;
        return $this;
    }
    /**
     * Get cached validation result for specific address
     *
     * @param Address $address
     * @return bool
     * @SuppressWarnings(PHPMD.BooleanGetMethodName)
     */
    public function getIsValidForAddress($address)
    {
        $addressId = $this->_getAddressId($address);
        return isset($this->validatedAddresses[$addressId]) ? $this->validatedAddresses[$addressId] : false;
    }
    /**
     * Return id for address
     *
     * @param Address $address
     * @return string
     */
    private function _getAddressId($address)
    {
        if ($address instanceof Address) {
            return $address->getId();
        }
        return $address;
    }
}

Finally i done it, here my updated code

public function execute()
    {  
      $validate  = array();
      $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
      $quoteId = $this->cart->getQuote()->getId();
      $quote =  $this->quoteFactory->load($quoteId);
      $fakeQuote = clone $quote;
      $fakeQuote->setId(null);
       $items = $this->cart->getQuote()->getAllItems();
       $rules = $objectManager->create('Vendor\Module\Model\Rule')->getCollection();
      foreach ($rules as $rule){
          foreach($items as $item){
            $productId = $item->getProductId();
            $product = $objectManager->create('Magento\Catalog\Model\Product')->load($productId);
            $quoteItem = $objectManager->create('Magento\Quote\Model\Quote\Item');
            $quoteItem->setQuote($fakeQuote)->setProduct($product);
            $quoteItem->setAllItems(array($product));
            $quoteItem->getProduct()->setProductId($product->getEntityId());
            $validate = $rule->getConditions()->validate($quoteItem);
        } 
    }
   // var_dump($validate); 
    return $validate;
    }

Best Answer

Seems like you validating your model (product) using the actions, but logically you should use the conditions, like:

$rule->getConditions()->validate($item);

Here is full code with changes:

$product_id = '3';  // Crown Summit Backpack sku is 24-MB03
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$rules = $objectManager->create('Vendor\Module\Model\Rule')->getCollection();
foreach ($rules as $rule) {
    $product = $objectManager->get('Magento\Catalog\Model\Product')->load($product_id);
    $item = $objectManager->create('Magento\Catalog\Model\Product');
    $item->setProduct($product);
    $validate = $rule->getConditions()->validate($item);
}
var_dump($validate);
var_dump($product);

I think the getActions() is a conditions for the Apply the rule to (see the select below your main block) or other conditions in your model.

You can detect what you are using for validation pretty simple, just print out the $rule->getConditions()->asStringRecursive() and $rule->getActions()->asStringRecursive(). You must see which method returns desired conditions.

PS: the $rule->getActions()->validate($item); always returns true because empty conditions always means the any item is valid.