2017-07-14 93 views
0

擴展了前面兩個關於form structurevalidating collections的問題我已經遇到下一個問題。ZF2 + Doctrine 2 - 當需求未滿足且值爲空時創建的實體

我的表單正確驗證。通過Fieldsets包括包含的集合。但是如果它的值沒有設置,最內層的Fieldset不應該導致實體和FK關聯到父項。

一個Address可能有或可能沒有鏈接Coordinates。可以在同一個表單中創建所有這些。

但是,Coordinates不應該創建,並且不應鏈接從Address如果沒有在窗體中給出座標。它們在表單中不是必需的,實體Coordinates本身要求設置經度和緯度的屬性。

下面,首先是實體。以下是用於AddressForm的Fieldsets。我已經刪除了與Address - >Coordinates鏈無關的內容。

Address.php

class Address extends AbstractEntity 
{ 
    // Properties 

    /** 
    * @var Coordinates 
    * @ORM\OneToOne(targetEntity="Country\Entity\Coordinates", cascade={"persist"}, fetch="EAGER", orphanRemoval=true) 
    * @ORM\JoinColumn(name="coordinates_id", referencedColumnName="id", nullable=true) 
    */ 
    protected $coordinates; 

    // Getters/Setters 
} 

Coordinates.php

class Coordinates extends AbstractEntity 
{ 
    /** 
    * @var string 
    * @ORM\Column(name="latitude", type="string", nullable=false) 
    */ 
    protected $latitude; 

    /** 
    * @var string 
    * @ORM\Column(name="longitude", type="string", nullable=false) 
    */ 
    protected $longitude; 

    // Getters/Setters 
} 

如上實體能夠被看見。 AddressCoordinates具有OneToOne單向關係。 Coordinates實體需要latitudelongitude屬性,如nullable=false所示。

它在那裏,它出錯了。如果創建了Address,但在表單中未設置Coordinates的屬性,它仍會創建Coordinates實體,但會將latitudelongitude屬性留空,即使它們是必需的。

因此,簡而言之:

  • 一個Coordinates實體創建其中不應該存在
  • Coordinates的鏈接從Address創建其中不應該存在

下面的字段集和InputFilters進一步澄清事情。

AddressFieldset.php

class AddressFieldset extends AbstractFieldset 
{ 
    public function init() 
    { 
     parent::init(); 

     // Other properties 

     $this->add([ 
      'type' => CoordinatesFieldset::class, 
      'required' => false, 
      'name' => 'coordinates', 
      'options' => [ 
       'use_as_base_fieldset' => false, 
      ], 
     ]); 
    } 
} 

CoordinatesFieldset.php

class CoordinatesFieldset extends AbstractFieldset 
{ 
    public function init() 
    { 
     parent::init(); 

     $this->add([ 
      'name' => 'latitude', 
      'required' => true, 
      'type' => Text::class, 
      'options' => [ 
       'label' => _('Latitude'), 
      ], 
     ]); 

     $this->add([ 
      'name' => 'longitude', 
      'required' => true, 
      'type' => Text::class, 
      'options' => [ 
       'label' => _('Longitude'), 
      ], 
     ]); 
    } 
} 

AddressFieldsetInputFilter.php

class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter 
{ 
    /** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */ 
    protected $coordinatesFieldsetInputFilter; 

    public function __construct(
     CoordinatesFieldsetInputFilter $filter, 
     EntityManager $objectManager, 
     Translator $translator 
    ) { 
     $this->coordinatesFieldsetInputFilter = $filter; 

     parent::__construct([ 
      'object_manager' => $objectManager, 
      'object_repository' => $objectManager->getRepository(Address::class), 
      'translator' => $translator, 
     ]); 
    } 

    /** 
    * Sets AddressFieldset Element validation 
    */ 
    public function init() 
    { 
     parent::init(); 

     $this->add($this->coordinatesFieldsetInputFilter, 'coordinates'); 

     // Other filters/validators 
    } 
} 

CoordinatesFieldsetInputFilter.php

class CoordinatesFieldsetInputFilter extends AbstractFieldsetInputFilter 
{ 
    public function init() 
    { 
     parent::init(); 

     $this->add([ 
      'name' => 'latitude', 
      'required' => true, 
      'allow_empty' => true, 
      'filters' => [ 
       ['name' => StringTrim::class], 
       ['name' => StripTags::class], 
      ], 
      'validators' => [ 
       [ 
        'name' => StringLength::class, 
        'options' => [ 
         'min' => 2, 
         'max' => 255, 
        ], 
       ], 
       [ 
        'name' => Callback::class, 
        'options' => [ 
         'callback' => function($value, $context) { 
          //If longitude has a value, mark required 
          if(empty($context['longitude']) && strlen($value) > 0) { 
           $validatorChain = $this->getInputs()['longitude']->getValidatorChain(); 

           $validatorChain->attach(new NotEmpty(['type' => NotEmpty::NULL])); 
           $this->getInputs()['longitude']->setValidatorChain($validatorChain); 

           return false; 
          } 

          return true; 
         }, 
         'messages' => [ 
          'callbackValue' => _('Longitude is required when setting Latitude. Give both or neither.'), 
         ], 
        ], 
       ], 
      ], 
     ]); 

     // Another, pretty much identical function for longitude (reverse some params and you're there...) 
    } 
} 

編輯:添加數據庫轉儲映像。顯示空的緯度,經度。

Empty latitude/longitude

EDIT2:當我刪除從AddressFieldsetInputFilter輸入'allow_empty' => true,和填單輸入(經緯度),然後它驗證正確,除非你離開兩個輸入爲空,那麼它將立即脫落到返回輸入是必需的。 (Value is required and can't be empty)。

回答

0

一次偶然的機會,我才stumple在this answer,這是允許一個字段爲空,但驗證它是否至少有一個輸入是在填補。

通過從AbstractInputFilter類擴展自己AbstractFormInputFilterAbstractFieldsetInputFilter類,其中包含答案,我現在可以提供FielsetInputFilters,如AddressFieldsetInputFilter,另外還有->setRequired(false)。然後在AbstractInputFilter中進行驗證,如果它實際上是空的。

鏈接的答案給出了這樣的代碼:

<?php 
namespace Application\InputFilter; 

use Zend\InputFilter as ZFI; 

class InputFilter extends ZFI\InputFilter 
{ 
    private $required = true; 

    /** 
    * @return boolean 
    */ 
    public function isRequired() 
    { 
     return $this->required; 
    } 

    /** 
    * @param boolean $required 
    * 
    * @return $this 
    */ 
    public function setRequired($required) 
    { 
     $this->required = (bool) $required; 
     return $this; 
    } 

    /** 
    * @return bool 
    */ 
    public function isValid() 
    { 
     if (!$this->isRequired() && empty(array_filter($this->getRawValues()))) { 
      return true; 
     } 

     return parent::isValid(); 
    } 
} 

正如我提到我用這個代碼來擴展自己的AbstractInputFilter,允許*FieldsetInputFilterFactory類的微小變化。

AddressFieldsetInputFilterFactory.php

class AddressFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory 
{ 
    /** 
    * @param ServiceLocatorInterface|ControllerManager $serviceLocator 
    * @return InputFilter 
    */ 
    public function createService(ServiceLocatorInterface $serviceLocator) 
    { 
     parent::setupRequirements($serviceLocator, Address::class); 

     /** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */ 
     $coordinatesFieldsetInputFilter = $this->getServiceManager()->get('InputFilterManager') 
      ->get(CoordinatesFieldsetInputFilter::class); 
     $coordinatesFieldsetInputFilter->setRequired(false); // <-- Added option 

     return new AddressFieldsetInputFilter(
      $coordinatesFieldsetInputFilter, 
      $this->getEntityManager(), 
      $this->getTranslator() 
     ); 
    } 
} 

可能不適合每個人的項目是個好主意,但它解決了不能總是想驗證一個字段我的問題,它肯定解決了沒有創造與實體的原始問題只是一個ID,如問題的屏幕截圖所示。