I have 2 entities:
ADS\LinkBundle\Entity\Link:
type: entity
table: null
repositoryClass: ADS\LinkBundle\Entity\LinkRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
dateAdded:
type: datetime
expirationDate:
type: datetime
nullable: true
designator:
type: string
length: 255
nullable: false
unique: true
slug:
type: string
length: 255
nullable: true
unique: true
manyToOne:
company:
targetEntity: ADS\UserBundle\Entity\Company
inversedBy: link
joinColumn:
name: company_id
referencedColumnName: id
nullable: true
createdBy:
targetEntity: ADS\UserBundle\Entity\User
inversedBy: link
joinColumn:
name: createdBy_id
referencedColumnName: id
domain:
targetEntity: ADS\DomainBundle\Entity\Domain
inversedBy: link
joinColumn:
name: domain_id
referencedColumnNames: id
oneToMany:
paths:
targetEntity: ADS\LinkBundle\Entity\Path
mappedBy: link
cascade: [persist]
lifecycleCallbacks: { }
and
ADS\LinkBundle\Entity\Path:
type: entity
table: null
repositoryClass: ADS\LinkBundle\Entity\PathRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
pathAddress:
type: string
length: 255
pathWeight:
type: string
length: 255
manyToOne:
link:
targetEntity: ADS\LinkBundle\Entity\Link
inversedBy: paths
joinColumn:
name: link_id
referencedColumnName: id
lifecycleCallbacks: { }
I have everything figured out except for the paths portion of the entity. This is for an A/B split test, so each link can have 2 paths. Each path will consist of a web address, and a number ( 0 - 100 )
Here is my form in it's current state:
<?php
namespace ADS\LinkBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PathType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('pathAddress')
->add('pathWeight')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Path'));
}
public function getName() { return 'ads_linkbundle_link'; }
}
and
<?php
namespace ADS\LinkBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LinkType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('designator')
->add('domain', 'entity', array(
'class' => 'ADS\DomainBundle\Entity\Domain',
'property' => 'domainAddress'
))
->add('paths', 'collection', array('type' => new PathType(), 'allow_add' => true))
->add('Submit', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Link'));
}
public function getName() { return 'ads_linkbundle_link'; }
}
What I need to figure out, is when creating a link, I need to also be able to create the correct path and weight to go with it. The paths won't be in the database before a link is created.
Here is what I have for my controller:
public function newAction(Request $request) {
$entity = new Link();
$form = $this->createForm(new LinkType(), $entity);
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
$code = $this->get('ads.default');
$em = $this->getDoctrine()->getManager();
$user = $this->getUser();
$entity->setDateAdded(new \DateTime("now"));
$entity->setCreatedBy($user);
$entity->setSlug($code->generateToken(5));
$entity->setCompany($user->getParentCompany());
$em->persist($entity);
$em->flush();
return new Response(json_encode(array('error' => '0', 'success' => '1')));
}
return new Response(json_encode(array('error' => count($form->getErrors()), 'success' => '0')));
}
return $this->render('ADSLinkBundle:Default:form.html.twig', array(
'entity' => $entity,
'saction' => $this->generateUrl('ads.link.new'),
'form' => $form->createView()
));
}
You can use multiple Doctrine entity managers or connections in a Symfony application. This is necessary if you are using different databases or even vendors with entirely different sets of entities. In other words, one entity manager that connects to one database will handle some entities...
You've also defined two connections, one for each entity manager, but you are free to define the same connection for both. When working with multiple connections and entity managers, you should be explicit about which configuration you want. If you do omit the name of the connection or entity manager, the default (i.e. default) is used.
If you do omit the name of the connection or entity manager, the default (i.e. default) is used. If you use a different name than default for the default entity manager, you will need to redefine the default entity manager in the prod environment configuration and in the Doctrine migrations configuration (if you use that):
In other words, one entity manager that connects to one database will handle some entities while another entity manager that connects to another database might handle the rest. It is also possible to use multiple entity managers to manage a common set of entities, each with their own database connection strings or separate cache configuration.
Thanks to @Onema ( read the comments above ), I've figured this out. By reading the documentation at http://symfony.com/doc/current/cookbook/form/form_collections.html It gave me information I needed to get this done.
First step in doing what I needed to do, was to create a new form type
called PathsType.php
which houses the fields associated with the Paths Entity
<?php
namespace ADS\LinkBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PathType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('pathAddress')
->add('pathWeight')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Path'));
}
public function getName() { return 'ads_linkbundle_path'; }
}
Then modifying the LinkType.php
to utilize this new form
<?php
namespace ADS\LinkBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LinkType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('designator')
->add('domain', 'entity', array(
'class' => 'ADS\DomainBundle\Entity\Domain',
'property' => 'domainAddress'
))
->add('paths', 'collection', array(
'type' => new PathType(),
'allow_add' => true,))
->add('Submit', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\LinkBundle\Entity\Link'));
}
public function getName() { return 'ads_linkbundle_link'; }
}
The addition of allow_add
makes it so that you can add multiple instances of that form.
Within the view, I now utilize the data-prototype
attribute. In the documentation, it has the example using a list item - so that's where I started.
<ul class="tags" data-prototype="{{ form_widget(form.paths.vars.prototype)|e }}"></ul>
Then came the jQuery functions ( listed on the documentation link above, simple copy/paste will work )
This got the system working, with 1 small issue and that in my paths entity
, I have a relationship to the Link entity
but it was not noticing this relationship and had the link_id
field as null
To combat this, we edit LinkType.php
one more time, and add by_reference = false
to the collection
definition. We then edit the addPath
method inside the entity to look like so:
public function addPath(\ADS\LinkBundle\Entity\Path $paths)
{
$paths->setLink($this);
$this->paths->add($paths);
}
This sets the current link object, as the link the path is associated with.
At this point, the system is working flawlessly. It's creating everything that it needs to, only need to adjust the display a little bit. I personally opted to use a twig macro
to modify the html output contained in data-prototype
my macro as it currently sits (incomplete - but working ) which I added to the beginning of my form.html.twig
{% macro path_prototype(paths) %}
<div class="form-group col-md-10">
<div class="col-md-3">
<label class="control-label">Address</label>
</div>
<div class="col-md-9">
{{ form_widget(paths.pathAddress, { 'attr' : { 'class' : 'form-control required' }}) }}
</div>
</div>
{% endmacro %}
In the HTML
for the form itself, I removed the list
creation, and replaced it with:
<div class="form-group">
{{ form_label(form.paths,'Destination(s)', { 'label_attr' : {'class' : 'col-md-12 control-label align-left text-left' }}) }}
<div class="tags" data-prototype="{{ _self.path_prototype(form.paths.vars.prototype)|e }}">
</div>
</div>
I then modified my javascript to use the div
as a starting point instead of the ul
in the example.
<script type="text/javascript">
var $collectionHolder;
// setup an "add a tag" link
var $addTagLink = $('<a href="#" class="add_tag_link btn btn-xs btn-success">Add Another Destination</a>');
var $newLinkLi = $('<div></div>').append($addTagLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of tags
$collectionHolder = $('div.tags');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
addTagForm($collectionHolder, $newLinkLi);
$addTagLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm($collectionHolder, $newLinkLi);
});
});
function addTagForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
console.log(index);
if (index == 1) {
console.log('something');
$('a.add_tag_link').remove();
}
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = newForm;
$newLinkLi.before($newFormLi);
}
</script>
Being that these paths
are destination addresses for an A/B split test within my marketing app, I opted to limit the paths to 2 per link. And with this, I have successfully setup a form to use a collections
type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With