реализация отношений «многие ко многим» с Zend \ Form и Doctrine 2

1

У меня есть Zend\Form и соответствующий класс сущности Doctrine, где объект как отношение ManyToMany к другому объекту. Точнее, пользователь должен иметь возможность выбирать одно или несколько имен из таблицы данных, содержащей 12 000 имен - слишком много для обычного элемента SELECT.

В более ранней итерации этого проекта, в котором использовался ZF1, у меня был элемент MultiSelect с нулевыми параметрами, который я просто никогда не отображал. Вместо этого я создал текстовое поле автозаполнения с JQueryUI для динамической вставки человекочитаемых имен и идентификаторов в виде скрытых элементов. Отлично.

Я посмотрел на Zend\Form\Element\Collection но документы говорят, что вы не можете обновить его с меньшим количеством элементов, чем вы начали с этого, то есть, если в момент гидратации формы обновления у вас есть 2 whatevers, вы должны отправить по крайней мере 2 удара. Этого не будет.

В другом месте я с радостью использую DoctrineModule\Form\Element\ObjectSelect но это не похоже на правильный выбор для этого случая.

Прежде чем я уйду и попытаюсь использовать ту же технику, которую использовал с ZF1, я был бы рад, если бы кто-нибудь мог дать мне лучшую идею.

  • 0
    Если вы никогда не рендерили, то как вы получили значения и идентификаторы для проверки ввода в ваш JQueryUI в вашем предыдущем решении?
  • 0
    извини, не уверен, что понимаю. Моя форма содержала текстовое поле автозаполнения jquery ui. К его обратному вызову select добавлены элементы DOM - скрытый идентификатор строки, удобное для пользователя текстовое значение, кнопка для удаления обоих. Добавленный таким образом массив идентификаторов будет опубликован. На стороне сервера я проверил ссылочную целостность, если это то, что вы имеете в виду. Когда форма загружалась для обновления, а не для создания, я помещал данные в представление и использовал помощник для печати массива уже существующих идентификаторов / значений, а не Javascript. Отправка этого массива удовлетворила валидаторы неопределяемого элемента - Zend_Form не был мудрее.
Показать ещё 2 комментария
Теги:
zend-form
doctrine2
zend-framework2

2 ответа

0
Лучший ответ

Мой ответ: нет экзотических, секретных знаний. Используйте автозаполнение и базу данных, как и для любых других инструментов/библиотек.

Несколько описать мой прецедент: пользователи отправляют запросы на переводчика для будущего судебного разбирательства. Они должны указать имя (имена) ответчика (-ов), среди других данных (например, язык). В нашей базе уже более 12 тыс. Таких имен, и мы повторно используем повторяющиеся имена (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 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 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>

И некоторые Javascript из defts.js, внутри нашего 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 прост и опущено для краткости.

Еще нужно: добавьте кнопку поиска справа от нашего текстового элемента автозаполнения, чтобы они нажимали, когда не получили совпадений автозаполнения, поэтому мы можем сказать им "извините, никаких совпадающих записей не найдено". И, хотя это касательно первоначального вопроса, предоставьте им возможность представить имена, о которых мы никогда не слышали.

0

Все, что я могу придумать, это использовать валидатор шаблона HTML5 (regex). Но я бы сказал, что это гораздо менее удобное для пользователя, а затем ваше текущее решение. Я также очень сомневаюсь в том, может ли регулярное выражение обрабатывать 12000 имен в качестве ограничения. Честно говоря, это звучит неплохо.

Возможно, вы можете улучшить свое текущее решение с помощью ограничения алфавитных символов:

Name: <input type="text" name="name" pattern="[A-Za-z]*" placeholder="Write a valid name">
  • 0
    спасибо - но проверка не та часть, которая меня беспокоит. входные данные будут поступать из моей базы данных и будут проходить проверку ввода, или же, если они умно попытаются ввести что-то еще, это вызовет исключение ограничения внешнего ключа.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню