英文:
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'] // 只有"法国"可用
];
}
问题在于应用程序仍在使用CommerceManager
的DeliveryForm
。
我对如何处理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 <select> in delivery form
*
* @var array
*/
public $countries = [
'AL' => ['name' => 'ALBANIA', 'code' => '008'],
// [...]
'FR' => ['name' => 'FRANCE', 'code' => '250'],
// [...]
];
public function validationDefault(Validator $validator): Validator
{
// validates form data
}
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;
}
}
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's 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'));
}
// [...]
}
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'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 = [
'FR' => ['name' => 'FRANCE', 'code' => '250'] // Only "France" 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->request->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'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->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);
}
// [...]
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论