与Zend \ Form和Doctrine 2实现多对多关系

时间:2015-11-04 21:58:56

标签: php doctrine-orm zend-framework2 zend-form

我有一个Zend\Form和相应的Doctrine实体类,其中实体与另一个实体有ManyToMany的关系。更准确地说,用户需要能够从包含12,000个名称的数据表中选择一个或多个名称 - 对于普通的SELECT元素来说太多了。

在这个使用ZF1的项目的早期迭代中,我有一个MultiSelect元素,零选项,我根本就没有渲染过。相反,我使用JQueryUI创建了一个自动完成文本字段,用于动态插入人类可读的名称和id作为隐藏元素。工作得很好。

我看过Zend\Form\Element\Collection但文档说你不能用比你开始时更少的元素更新它 - 也就是说,如果在更新形式的保湿时间你有2个凡人,你必须提交至少2个whatevers。那不行。

在其他地方,我很高兴使用DoctrineModule\Form\Element\ObjectSelect,但对于这种情况,它似乎不是正确的选择。

在我开始尝试使用与ZF1相同的技术之前,如果有人能给我一个更好的主意,我会很高兴。

2 个答案:

答案 0 :(得分:0)

我现在能想到的就是使用HTML5模式(正则表达式)验证器。但我会说,这比当前的解决方案更不方便用户。我也非常怀疑正则表达式是否可以处理12000个名称作为约束。说实话听起来是个坏主意。

您可以通过限制字母字符来增强当前的解决方案:

Name: <input type="text" name="name" pattern="[A-Za-z]*" placeholder="Write a valid name">

答案 1 :(得分:0)

我的回答:没有异国情调的秘密知识。像使用任何其他工具/库一样使用自动完成和数据库。

稍微描述一下我的用例:用户提交翻译请求,以便将来进行法庭诉讼。他们必须提供被告的姓名,以及其他数据(例如语言)。我们的数据库中已经有超过12K这样的名称,我们重复使用重复名称(IOW,实体代表一个人的专有名称,而不是一个人)。

现在提供一些代码摘录。在前端,查看脚本:

<?php 
$this->headScript()->appendFile('/dev-requests/js/jquery-ui.min.js');
$this->headScript()->appendFile('/dev-requests/js/defts.js');
// stuff omitted...
// within the form:
// $element is a \Zend\Form\Element\Select with attribute 'multiple' => 'multiple', zero options,
// and it's hidden via css because no one needs to see it
<div class="form-group">
    <label class="control-label col-sm-3" for="<?=$element->getName()?>"><?=$element->getLabel()?></label>
    <div class="col-sm-9"><?= $this->formElement($element)?><?= $this->formElementErrors($element) ?>
    <?php if ($this->defendants): 
    // if we are an update (as opposed to create) action, our controller's updateAction 
    // will have set $this->defendants to a (possibly empty) array of 'defendantName' entities
    foreach($this->defendants as $deft): ?>
        <div id="deft-div-<?=$deft->getDeftId()?>"><span class="remove-div"><a href="#">[x]</a></span> 
            <?=$deft->getFullname()?>
            <input value="<?=$deft->getDeftId()?>" name="request-fieldset[defendantNames][]" type="hidden">
        </div>
        <?php
            endforeach;
        endif;
        ?>
    </div>
</div>

来自defts.js的一些Javascript,在我们的document.ready()回调中:

// the autocomplete textfield itself
$('#deft-select').after(
    $('<input>').attr({id:'deftname-autocomplete',size:25})
);
// for deleting a name from the form
($('form').on('click','span.remove-div',function(event){
    event.preventDefault();
    $(this).parent().slideUp(function(){$(this).remove();});
}));

$('#deftname-autocomplete').autocomplete({

    source : '/dev-requests/defendants/autocomplete',
    select: function( event, ui ) { 
        // add a human-readable label and hidden form element
        $(this).val('');
        var elementName = $('#deft-select').attr('name');
        var deftName = ui.item.label;
        var deft_id  = ui.item.value;
        if ($( '#deft-div-'+ deft_id ).length) {
            return false; // already exists
        }
        var div = $(this).closest('div');
        div.append(
            $('<div/>').attr({id: "deft-div-"+ deft_id})
                .html([
                    '<span class="remove-div"><a href="#">[x]</a></span> ' + deftName,
                    $('<input/>').attr({type:'hidden',name:elementName}).val(deft_id)
                ])
        );
        return false;
    }

});

在我们的控制器中:

 public function autocompleteAction()
{

    $term = $this->getRequest()->getQuery('term');
    if (! $term) { return false; }

    /**
     * @var $em Doctrine\ORM\EntityManager
     */
    $em = $this->getServiceLocator()->get('entity-manager');
    /**
     * @var $repo Application\Entity\DefendantNameRepository
     */
    $repo = $em->getRepository('Application\Entity\DefendantName');

    $data = json_encode($repo->autocomplete($term));
    $response = $this->getResponse();
    $response->getHeaders()->addHeaders(['Content-type'=>'application/json;charset=UTF-8']);
    return $response->setContent($data);
}

在我们的自定义Doctrine存储库中,Application \ Entity \ DefendantNameRepository:

/**
* return array of value/label for autocompletion via xhr
* @param string $term name 
* @param int limit max number of records to return
*  
* $term is expected to be proper name in the format la[stname][,f[irstname]] 
*/

public function autocomplete($term, $limit = 20) {

    /**
     * @var $connection Doctrine\DBAL\Connection
     */
    $connection = $this->getEntityManager()->getConnection();
    list($lastname,$firstname) = $this->parseName($term);
    if (! strstr($lastname,'-')) {
        $where = 'lastname LIKE '.$connection->quote("$lastname%");
    } else {
        // they frequently insert gratuitous hyphens between the 
        // paternal and maternal surnames of Spanish-speaking people
        $lastname = str_replace('-','( |-)',$lastname);
        $where = 'lastname REGEXP '.$connection->quote("^$lastname");
    }
    if ($firstname) {
        $where .= " AND firstname LIKE ".$connection->quote("$firstname%");
    } else {
        $where .= " AND firstname <> '' "; // some old records have no firstname, but we don't like that
    }
    $sql = 'SELECT CONCAT(lastname, ", ",firstname) AS label, deft_id AS value 
            FROM deft_names WHERE '.$where . " ORDER BY lastname, firstname LIMIT $limit ";
    return $connection->fetchAll($sql);
}

...还有这个小帮手,我们仓库的其他地方:

/**
 * parses first and last names out of $name. expected format is
 * la[stname][,f[irstname]] 
 * @param string $name
 * @return array ($lastname, $firstname)
 */
public function parseName($name) {

    $name = preg_split('/ *, */',trim($name),2,PREG_SPLIT_NO_EMPTY);
    if (2 == sizeof($name)) {
        list($last, $first) = $name;
    } else {
        $last = $name[0];
        $first = false;
    }
    return array($last,$first);
}

类定义Application \ Entity \ DefendantName很简单,为简洁起见省略。

仍然要做:在我们的自动填充文本元素的右侧添加一个搜索按钮,以便他们在没有自动完成匹配时单击,这样我们就可以告诉他们“抱歉,找不到匹配的记录”。而且 - 尽管这与原始问题相关 - 为他们提供了一种方式来提交我们从未听说过的名字。