“中国要复兴、富强,必须在开源软件领域起到主导作用,为了国家安全和人类发展,责无旁贷,我们须为此而奋斗”——By:云客
在Drupal电商中,有时候需要依据用户输入的信息来计算价格,比如文字数量等,这些输入信息因为其有巨大的可能性而不方便或根本无法预设成为选项,只能提供表单由用户录入,后台再用专门的函数去解析价格,用户录入的表单其实就是订单条目对象的创建表单,计算价格的关键在于在价格解析器中如何获取到订单条目对象实体,以下代码展示了其方法:
<?php
/**
* 未来很美(深圳)科技有限公司
* by:yunke
*
*/
namespace Drupal\neon\Resolver;
use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\commerce_price\Resolver\PriceResolverInterface;
use Drupal\Core\Form\FormState;
/**
* 定制产品价格解析器
* 将自定义价格的解析委派给自定义函数,向其传递订单条目实体对象,其中包含了用户提交的自定义数据信息
*/
class CustomNeonPriceResolver implements PriceResolverInterface {
/**
* {@inheritdoc}
*/
public function resolve(PurchasableEntityInterface $purchased_entity, $quantity, Context $context) {
//return new \Drupal\commerce_price\Price(90,'USD');
static $price = NULL;
/**
* 设置此静态变量是因为性能原因 一个请求过程中会多次调用本解析器
* 每一次传递的可购买实体可能不同,因此定制产品不宜设置多个变体,通常会进行以下调用
* 第一次是渲染产品页面时,在注入变体字段过程中,此时传递的是URL中通过V参数指定的变体
* 第二次是订单条目实体表单处理过程中的buildEntity方法
* 第三次是被选中变体的渲染,在AJAX回调中调用,或者在添加到购物车提交后,在订单刷新处理器中
*
*/
$request = \Drupal::request();
if ($request->getMethod() !== 'POST') {
return NULL;
}
///前端价格不会从后端取 custom-neon-sign?ajax_form=1&_wrapper_format=drupal_ajax
$wrapper_format = $request->query->get("_wrapper_format");
if ($wrapper_format) {
return NULL;
}
//仅在商品页有效
$route_match = \Drupal::routeMatch();
if ($route_match->getRouteName() != 'entity.commerce_product.canonical') {
return NULL;
}
//仅作用于定制产品
if ('custom_neon_sign' != $purchased_entity->bundle()) {
return NULL;
}
//仅解析实价 不处理挂牌价
$field_name = $context->getData('field_name', 'price');
if ($field_name !== 'price') {
return NULL;
}
//提高性能
if ($price) {
return $price;
}
$product_id = $purchased_entity->getProductId();
$entityTypeManager = \Drupal::entityTypeManager();
$product = $entityTypeManager->getStorage('commerce_product')->load($product_id);
$order_item_storage = $entityTypeManager->getStorage('commerce_order_item');
$order_item = $order_item_storage->createFromPurchasableEntity($purchased_entity);
$order_item->set('overridden_unit_price', TRUE);//防止循环解析价格
$form_object = $entityTypeManager->getFormObject('commerce_order_item', 'add_to_cart');
$form_object->setEntity($order_item);
$form_object->setFormId($form_object->getBaseFormId() . '_commerce_product_' . $product_id);
$form_state = (new FormState())->setFormState([
'product' => $product,
'view_mode' => 'add_to_cart',
'settings' => [
'combine' => FALSE,
],
]);
$formBuilder = \Drupal::formBuilder();
$form_id = $formBuilder->getFormId($form_object, $form_state);
$input = $request->request->all();
$form_state->setUserInput($input);
$form = $formBuilder->retrieveForm($form_id, $form_state);
$formBuilder->prepareForm($form_id, $form, $form_state);
//表单值转化为实体字段值
$form = $formBuilder->doBuildForm($form_id, $form, $form_state);
/**
* 进行验证处理 因为部分控件的表单值在验证器中设置 比如:options_buttons 因此必须调用验证器
* 但不要有任何表单验证错误显示,你可能会想到在触发元素上面采用错误抑制属性 就像这样:
* $triggering_element['#limit_validation_errors']=[];
* 但这将在验证器中清除表单值 见:\Drupal\Core\Form\FormValidator::handleErrorsWithLimitedValidation
* 因此我们将表单改为以非提交元素进行的提交,这样既可以避免错误显示也能保留提交值
*/
$buttons = $form_state->getButtons();
if (!$form_state->getTriggeringElement()) {
$form_state->setTriggeringElement(reset($buttons));
}
$form_state->setFormState(['submitted' => FALSE]);
$formBuilder->validateForm($form_id, $form, $form_state);
$order_item = $form_object->buildEntity($form, $form_state);
$order_item->set('overridden_unit_price', FALSE);
$price = neon_getCustomPrice($order_item, $purchased_entity, $quantity, $context);//自定义价格解析函数
return $price;
}
}
该代码即是价格解析器的实现。
为了应对这种复杂情况,在价格解析器中本应该直接由系统传入订单条目对象实体的,但目前的架构暂不支持,故通过以上关键代码来实现。
交流互动