Cakephp4中无模型表单继承的问题

huangapple go评论52阅读模式
英文:

Problem with Modelless Form inheritance in Cakephp4

问题

我正在构建一个名为CommerceManager的插件,用于管理在线商店的购买。

CommerceManager插件部分

为了处理结算/交付步骤,我创建了一个无模型表单DeliveryForm,用于:

  • 设置可用于交付的国家列表
  • 验证结算/交付表单的数据
  • 在数据经过验证后将数据写入会话
  • 从会话中读取结算/交付数据
// 在插件/CommerceManager/src/Form/DeliveryForm.php
class DeliveryForm extends Form
{
    /**
     * 用于交付表单中的<select>的可用国家列表
     * 
     * @var array
     */
    public $countries = [
        'AL' => ['name' => '阿尔巴尼亚', 'code' => '008'],
        // [...]
        'FR' => ['name' => '法国', 'code' => '250'],
        // [...]
    ];

    public function validationDefault(Validator $validator): Validator
    {
        // 验证表单数据
    }

    public function read()
    {
        $session = Session::create();
        return $session->read('Buyer') ?? [];
    }

    protected function _execute(array $data): bool
    {
        $session = Session::create();
        $session->write('Buyer', $data);

        return true;
    }
}

我的CommerceManager插件有一个CheckoutController,它使用插件的DeliveryForm

// 在插件/CommerceManager/src/Controller/CheckoutController.php
use CommerceManager\Form\DeliveryForm; // 使用插件的DeliveryForm

class CheckoutController extends AppController
{
    public function delivery()
    {
        $deliveryForm = new DeliveryForm();
        if ($this->request->is('post')) {
            if ($deliveryForm->execute($this->request->getData())) {
                return $this->redirect(['action' => 'confirm']);
            }
        }
        else {
            $deliveryForm->setData($deliveryForm->read() ?? []);
        }

        $this->set(compact('deliveryForm'));
    }

    // [...]
}

应用程序部分

我想创建DeliveryForm的子类,以覆盖可用于交付的国家列表。

为此,我创建了CheckoutController的子类,它仅继承其父类的操作,并使用应用程序的DeliveryForm

// 在src/Controller/CheckoutController.php
use CommerceManager\Controller\CheckoutController as BaseCheckoutController;
use App\Form\DeliveryForm; // 使用应用程序的DeliveryForm

class CheckoutController extends BaseCheckoutController
{
    // 继承CommerceManager.CheckoutController的操作
}
// 在src/Form/DeliveryForm.php
use CommerceManager\Form\DeliveryForm as BaseDeliveryForm;

class DeliveryForm extends BaseDeliveryForm
{
    public $countries = [
        'FR' => ['name' => '法国', 'code' => '250'] // 只有"法国"可用
    ];
}

问题在于应用程序仍在使用CommerceManagerDeliveryForm

我对如何处理DeliveryForm的继承感到非常困惑...

我应该如何让我的应用程序的CheckoutController能够使用应用程序的DeliveryForm子类,而不是插件的那个呢?

英文:

I'm building a CommerceManager plugin to manage purchases for online shops.

CommerceManager plugin side

To deal with the billing/delivery step I have created a Modelless Form DeliveryForm which is useful for :

  • Setting a list of countries available for delivery
  • Validating the data of the billing/delivery form
  • Writing the data in session (once it has been validated)
  • Reading the billing/delivery data in session
// in plugin/CommerceManager/src/Form/DeliveryForm.php
class DeliveryForm extends Form
{
    /**
     * List of countries available used for &lt;select&gt; in delivery form
     * 
     * @var array
     */
    public $countries = [
    	&#39;AL&#39; =&gt; [&#39;name&#39; =&gt; &#39;ALBANIA&#39;, &#39;code&#39; =&gt; &#39;008&#39;],
    	// [...]
    	&#39;FR&#39; =&gt; [&#39;name&#39; =&gt; &#39;FRANCE&#39;, &#39;code&#39; =&gt; &#39;250&#39;],
    	// [...]
    ];

    public function validationDefault(Validator $validator): Validator
    {
        // validates form data
    }

    public function read()
    {
        $session = Session::create();
        return $session-&gt;read(&#39;Buyer&#39;) ?? [];
    }

	protected function _execute(array $data): bool
	{
	    $session = Session::create();
	    $session-&gt;write(&#39;Buyer&#39;, $data);

	    return true;
	}
}

My CommerceManager plugin has a CheckoutController which uses the plugin's DeliveryForm :

// in plugin/CommerceManager/src/Controller/CheckoutController.php
use CommerceManager\Form\DeliveryForm; // use plugin&#39;s DeliveryForm

class CheckoutController extends AppController
{
	public function delivery()
	{
		$deliveryForm = new DeliveryForm();
		if ($this-&gt;request-&gt;is(&#39;post&#39;)) {
			if ($deliveryForm-&gt;execute($this-&gt;request-&gt;getData())) {
				return $this-&gt;redirect([&#39;action&#39; =&gt; &#39;confirm&#39;]);
			}
		}
		else {
			$deliveryForm-&gt;setData($deliveryForm-&gt;read() ?? []);
		}

		$this-&gt;set(compact(&#39;deliveryForm&#39;));
	}

	// [...]
}

App side

I would like to make a subclass of DeliveryForm for overriding the list of countries available for delivery.

To do that I've created a subclass of CheckoutController which only inherits the actions of its parent and uses App's DeliveryForm.

// in src/Controller/CheckoutController.php
use CommerceManager\Controller\CheckoutController as BaseCheckoutController;
use App\Form\DeliveryForm; // use App&#39;s DeliveryForm 

class CheckoutController extends BaseCheckoutController
{
	// inherits actions of CommerceManager.CheckoutController
}

// in src/Form/DeliveryForm.php
use CommerceManager\Form\DeliveryForm as BaseDeliveryForm;

class DeliveryForm extends BaseDeliveryForm
{
    public $countries = [
    	&#39;FR&#39; =&gt; [&#39;name&#39; =&gt; &#39;FRANCE&#39;, &#39;code&#39; =&gt; &#39;250&#39;] // Only &quot;France&quot; is available
    ];
}

The problem comes from the fact that App still using CommerceManager's DeliveryForm.

I'm totally confused about how to deal with DeliveryForm inheritance...

How can I do for my App's CheckoutController could understand to use App's DeliveryForm subclass and not the plugin's one ?

答案1

得分: 1

你误解了导入解析的工作方式,导入名称并不会 "扩展",它是一个编译时功能,仅适用于当前文件,参见https://www.php.net/manual/en/language.namespaces.importing.php

基本上,在编译时(在代码实际运行之前),PHP将根据文件中的导入替换引用,然后您的应用级类将扩展一个插件级类,该类的所有引用已经解析,就像您扩展了一个在所有地方都使用完全合格名称的类,例如:

$deliveryForm = new \CommerceManager\Form\DeliveryForm();

有许多不同的方法来解决您的问题。很难说哪种方法是最佳的,因为它实际上取决于您的应用程序特定的要求和架构。例如,您可以在控制器上设置配置,并相应地将其传递给 delivery() 操作中的表单。或者,您可以将整个逻辑放在一个组件中,然后可以相应地在扩展的控制器中进行配置。或者使用可重写的表单工厂方法在控制器上创建表单实例。或者或者或者...

另外,避免手动创建会话实例,除非您正在为全局请求对象定义会话,否则不应该这样做,即不要在您的表单中使用 Session::create()!而是,例如,从外部注入它作为依赖项,如:

$deliveryForm = new DeliveryForm($this->request->getSession());
英文:

You are misunderstanding how import resolution works, importing names does not "extend", it is a compilation time feature that only applies in the current file, see https://www.php.net/manual/en/language.namespaces.importing.php.

Basically at compilation time (before code actually runs) PHP will replace the references according to the imports in the file, and your app level class will then extend a plugin level class which has all its references already resolved, ie it will be like you're extending a class that has used fully qualified names everywhere, like:

$deliveryForm = new \CommerceManager\Form\DeliveryForm();

There's lots of different ways to solve your problem. It's hard for me to say what would be the best way, as it really depends on your application's specific requirements and architecture. You could for example set the config on the controller, and pass it to the form in the delivery() action accordingly. Or you could put the whole logic in a component, which you could configure in the extended controller accordingly. Or use an overridable form factory method on the controller to create the form instance. Or or or...

ps, avoid creating session instances manually, unless you're defining the session for the global request object, there should be no need to do that, ie do not use Session::create() in your form! Instead for example inject it as a dependency from the outside, like:

$deliveryForm = new DeliveryForm($this-&gt;request-&gt;getSession());

答案2

得分: 0

// 在 plugin/CommerceManager/src/Controller/CheckoutController.php 文件中
use CommerceManager\Form\DeliveryForm; // 使用插件的 DeliveryForm

class CheckoutController extends AppController
{
	/**
	 * 要实例化的类
	 */
	protected $_classnameDeliveryForm = \CommerceManager\Form\DeliveryForm::class;

	/**
	 * 实例
	 */
	protected $DeliveryForm;

	public function initialize(): void
    {
    	parent::initialize();
    	$this->DeliveryForm = new $this->_classnameDeliveryForm();
    }

    public function delivery()
    {
        if ($this->request->is('post')) {
            if ($this->DeliveryForm->execute($this->request->getData())) {
                return $this->redirect(['action' => 'confirm']);
            }
        }
        else {
            $this->DeliveryForm->setData($this->DeliveryForm->read() ?? []);
        }

        $this->set('deliveryForm', $this->DeliveryForm);
    }

    // [...]
}
英文:
// in plugin/CommerceManager/src/Controller/CheckoutController.php
use CommerceManager\Form\DeliveryForm; // use plugin&#39;s DeliveryForm

class CheckoutController extends AppController
{
	/**
	 * The class to instanciate
	 */
	protected $_classnameDeliveryForm = \CommerceManager\Form\DeliveryForm::class;

	/**
	 * The instance
	 */
	protected $DeliveryForm;

	public function initialize(): void
    {
    	parent::initialize();
    	$this-&gt;DeliveryForm = new $this-&gt;_classnameDeliveryForm();
    }

    public function delivery()
    {
        if ($this-&gt;request-&gt;is(&#39;post&#39;)) {
            if ($this-&gt;DeliveryForm-&gt;execute($this-&gt;request-&gt;getData())) {
                return $this-&gt;redirect([&#39;action&#39; =&gt; &#39;confirm&#39;]);
            }
        }
        else {
            $this-&gt;DeliveryForm-&gt;setData($this-&gt;DeliveryForm-&gt;read() ?? []);
        }

        $this-&gt;set(&#39;deliveryForm&#39;, $this-&gt;DeliveryForm);
    }

    // [...]
}

huangapple
  • 本文由 发表于 2023年2月14日 01:30:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75439305.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定