Two join tables. How to administer? - silverstripe

I have this relationship in my SS3 project:
Restaurant many_many Cuisine
Cuisine many_many SubCuisine
Seems simple enough but I can't seem to find any way to administer it. Tried GridField and Listbox. Is this a limitation of SilverStripe perhaps?
Thanks for any leads!
Wilson

After our conversation on IRC we came up with the following alternative solution, which I will post here for the record.
This solution is quiet similar to the original answer, with the difference, that there is an additional object that handles the relation between Restaurant and Cuisine.
File: Restaurant.php
/**
* #method ManyManyList RestaurantCuisines
*/
class Restaurant extends Page {
private static $many_many = array(
'RestaurantCuisines' => 'RestaurantCuisine',
);
/**
* #return FieldList
*/
public function getCMSFields() {
$return = parent::getCMSFields();
$return->addFieldToTab('Root', Tab::create('Cuisines', 'The Cuisines'));
$return->addFieldToTab(
'Root.Cuisines',
GridField::create(
'RestaurantCuisines',
'The Cuisines this Restaurant offers',
$this->RestaurantCuisines(),
GridFieldConfig_RecordEditor::create()
)
);
return $return;
}
}
class Restaurant_Controller extends Page_Controller {
}
File: RestaurantCuisine.php
/**
* #property int CuisineID
* #method Cuisine Cuisine
* #method ManyManyList SubCuisines
*/
class RestaurantCuisine extends DataObject {
private static $has_one = array(
'Cuisine' => 'Cuisine',
);
private static $many_many = array(
'SubCuisines' => 'SubCuisine',
);
private static $summary_fields = array(
'getTitle' => 'Title'
);
public function getCMSFields() {
if ($this->isInDB()) {
$grid = GridField::create(
'SubCuisines',
'The Sub Cuisines of this Cuisines',
$this->SubCuisines(),
GridFieldConfig_RelationEditor::create()
);
} else {
// because this record is not saved to the DB yet, we have no ID, without ID there can be no many_many relation
$grid = ReadonlyField::create('SubCuisines', '', 'Sub Cuisines can be added after creating');
}
return FieldList::create(array(
DropdownField::create('CuisineID', 'Select a Cuisine', Cuisine::get()->map()),
$grid
));
}
/**
* overwrite getTitle and return title of Cuisine to have a nice text to display instead of the ID when displaying the save message
*/
public function getTitle() {
return $this->Cuisine() && $this->Cuisine()->exists() ? $this->Cuisine()->Title : parent::getTitle();
}
}
File: Cuisine.php
/**
* #property string Title
*/
class Cuisine extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)',
);
/**
* #return FieldList
*/
public function getCMSFields() {
return FieldList::create(array(
TextField::create('Title', 'Name of Cuisine'),
));
}
}
File: SubCuisine.php
/**
* #property string Title
*/
class SubCuisine extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)',
);
/**
* #return FieldList
*/
public function getCMSFields() {
return FieldList::create(array(
TextField::create('Title', 'Name of Cuisine'),
));
}
}

GridField is the perfect tool to manage this kind of data structure, I do it on a daily bases.
Because of your topic I feel the need to also mention this: it doesn't join here (ok, yes the ORM does joins, but not Restaurant & Cuisine & SubCuisine).
What the below example will do:
on a single Restaurant it will display a list (GridField) of Cuisines, where you can create new ones or attach existing ones.
on a single Cuisine it will display a list (GridField) of SubCuisines, where you can create new ones or attach existing ones.
(I am assuming that Restaurant is a Page, but it works just as well if its a normal DataObject)
File Restaurant.php:
/**
* #method ManyManyList Cuisines
*/
class Restaurant extends Page {
private static $many_many = array(
'Cuisines' => 'Cuisine',
);
/**
* #return FieldList
*/
public function getCMSFields() {
$return = parent::getCMSFields();
$return->addFieldToTab('Root', Tab::create('Cuisines', 'The Cuisines'));
$return->addFieldToTab(
'Root.Cuisines',
GridField::create(
'Cuisines',
'The Cuisines this Restaurant offers',
$this->Cuisines(),
GridFieldConfig_RelationEditor::create()
)
);
return $return;
}
}
class Restaurant_Controller extends Page_Controller {
}
File Cuisine.php:
/**
* #property string Title
* #method ManyManyList SubCuisines
*/
class Cuisine extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)',
);
private static $many_many = array(
'SubCuisines' => 'SubCuisine',
);
/**
* #return FieldList
*/
public function getCMSFields() {
if ($this->isInDB()) {
$grid = GridField::create(
'SubCuisines',
'The Sub Cuisines of this Cuisines',
$this->SubCuisines(),
GridFieldConfig_RelationEditor::create()
);
} else {
// because this record is not saved to the DB yet, we have no ID, without ID there can be no many_many relation
$grid = ReadonlyField::create('SubCuisines', '', 'Sub Cuisines can be added after creating');
}
return FieldList::create(array(
TextField::create('Title', 'Name of Cuisine'),
$grid,
));
}
}
File SubCuisine.php:
/**
* #property string Title
*/
class SubCuisine extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)',
);
/**
* #return FieldList
*/
public function getCMSFields() {
return FieldList::create(array(
TextField::create('Title', 'Name of Cuisine'),
));
}
}
File Restaurant.ss (template):
<h1>Restaurant: $Title</h1>
<% if $Cuisines %>
<h2>Cuisines</h2>
<ol>
<% loop $Cuisines %>
<li>
<h3>$Title</h3>
<% if $SubCuisines %>
<h4>Sub Cuisines:</h4>
<ul>
<% loop $SubCuisines %>
<li>
<h5>$Title</h5>
</li>
<% end_loop %>
</ul>
<% end_if %>
</li>
<% end_loop %>
</ol>
<% end_if %>

Related

Silverstripe ORM: sort by number of $belongs_many_many relation

How do I get the most active blog tags in a query?
I have this two DataObjects:
class BlogTag extends DataObject {
//...
/**
* #var array
*/
private static $belongs_many_many = array(
'BlogPosts' => 'BlogPost',
);
and
class BlogPost extends Page {
//...
/**
* #var array
*/
private static $many_many = array(
'Tags' => 'BlogTag',
);
Now i wonder how I can get a DataList with all BlogTags ordered by how many blog posts they're related to. This is what i have already, but somehow i don't get how to sort by BlogPosts.Count():
public function getPopularBlogTags($limit = 5) {
$tags = BlogTag::get()
->sort('BlogPosts.Count()') //doesn't work
->limit($limit);
return $tags;
}
Found a solution with help on IRC (thanks barry and mark)
public function getPopularBlogTags($limit = 5) {
$tags = BlogTag::get()
->setQueriedColumns(['ID', 'Title', 'Count(*)'])
->leftJoin('BlogPost_Tags','bpt.BlogTagID = BlogTag.ID','bpt')
->sort('Count(*) DESC')
->alterDataQuery(function($query){
$query->groupBy('BlogTag.ID');
})
->limit($limit);
return $tags;
}
And in the template:
<% loop $PopularBlogTags %>
$Title ($BlogPosts.Count())
<% if not $Last %> | <% end_if %>
<% end_loop %>

PHPStorm References to properties seems wrong

Updated question Title
Sorry if the title of this question is a little off, I'm really not sure how to phrase it.
I'm trying to create a service that will pull data from a json feed and persist/flush it to my database via doctrine.
After much searching/reading/trial and error I've managed to get my service registered:
services:
fantasyapi:
class: FantasyDataAPI\Client
arguments: ["%fantasyapi.key%"]
data_manager:
class: FantasyPro\DataBundle\DataManager\StadiumParser
arguments: ['#doctrine.orm.entity_manager', '#fantasyapi', ::DataBundle.Entity.Stadium]
I'm using php storm with the symfony2 plugin.
If i use:
$repo = $this->em->getRepository('DataBundle:Stadium');
PHP storm reports it as an undefined function.
However if i use
$repo = $this->em->em->getRepository('DataBundle:Stadium');
php storm does not report it as undefined.
I think i'm doing something wrong as using ->em->em->getRepository does not feel right.
Am i instantiating the service correctly and if so why do i have to duplicate the reference to the function?
heres the full code of my service:
<?php
namespace FantasyPro\DataBundle\DataManager;
use Doctrine\ORM\EntityManager;
use FantasyDataAPI\Client;
use FantasyPro\DataBundle\Entity\Stadium;
class StadiumParser {
private $em;
private $client;
private $stadium;
/**
* #param EntityManager $em
* #param Client $client
* #param Stadium $stadium
*/
public function __constuct(EntityManager $em, Client $client, Stadium $stadium ) {
$this->em = $em;
$this->client = $client;
$this->stadium = $stadium;
}
/**
* #return array
*/
public Function parseData(){
$stadiumData = $this->client->client->Stadiums();
//get the entity manager
$repo = $this->em->em->getRepository('DataBundle:Stadium');
$log = array();
foreach ($stadiumData as $stadium) {
// Get the current stadium in the list
$criteria = array( 'stadiumID' => $stadium['StadiumID'] );
$currentStadium = $repo-->FindOneBy( $criteria );
if ( ! $currentStadium) {
$currentStadium = new Stadium(); //no stadium with the StadiumID exists so create a new stadium
$logData = [
'action' => 'Added Stadium',
'itemID' => $stadium['StadiumID'],
'itemName' => $stadium['Name']
];
$log[] = $logData;
} else {
$logData = [
'action' => 'Updated '.$logTitle,
'itemID' => $stadium['PlayerID'],
'itemName' => $stadium['Name']
];
$log[] = $logData;
}
$currentStadium->setStadiumID( $stadium['StadiumID'] );
$currentStadium->setName( $stadium['Name'] );
$currentStadium->setCity( $stadium['City'] );
$currentStadium->setState( $stadium['State'] );
$currentStadium->setCountry( $stadium['Country'] );
$currentStadium->setCapacity( $stadium['Capacity'] );
$currentStadium->setPlayingSurface( $stadium['PlayingSurface'] );
$this->em->em->persist( $currentStadium );
}
$this->em->em->flush();
return $log;
}
}
You can document the type of each member of your class:
<?php
namespace FantasyPro\DataBundle\DataManager;
use Doctrine\ORM\EntityManager;
use FantasyDataAPI\Client;
use FantasyPro\DataBundle\Entity\Stadium;
class StadiumParser
{
/** #var EntityManager em */
private $em;
/** #var Client client */
private $client;
/** #var Stadium stadium */
private $stadium;
/* ... */
For some reason PHPStorm didn't recognized the classes when I only used the class name so I had to put the full namespace, e.g.:
/* ... */
/** #var \Doctrine\ORM\EntityManager em */
private $em;
/* ... */

Model transformer and expected form view data mismatch

I have a Symfony 2 application, with a form that needs to store a reference to another entity (project) in a hidden field. The project entity is passed in via the form options, my plan was to have a field of the type 'hidden' that simply contains the entity id, this should then be transformed into a project entity on when the form is submitted.
I'm going about this by using a model transformer to transform between the entity to a string (it's ID). However when I try to view the form, I get the following error:
The form's view data is expected to be an instance of class Foo\BarBundle\Entity\Project, but is a(n) string. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Foo\BarBundle\Entity\Project.
Here is my form class:
<?php
namespace Foo\BarBundle\Form\SED\Waste;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Foo\BarBundle\Form\DataTransformer\EntityToEntityIdTransformer;
/**
* Class WasteContractorEntryType
* #package Foo\BarBundle\Form\CommunityInvestment\Base
*/
class WasteContractorEntryType extends AbstractType
{
protected $name;
protected $type;
protected $phase;
protected $wasteComponent;
public function __construct($formName, $type, $phase, $wasteComponent)
{
$this->name = $formName;
$this->type = $type;
$this->phase = $phase;
$this->wasteComponent = $wasteComponent;
}
/**
* #return mixed
*/
public function getType()
{
return $this->type;
}
/**
* #return mixed
*/
public function getPhase()
{
return $this->phase;
}
/**
* #return mixed
*/
public function getProject()
{
return $this->project;
}
/**
* #return mixed
*/
public function getWasteComponent()
{
return $this->wasteComponent;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$em = $options['em'];
$wasteComponentTransformer = new EntityToEntityIdTransformer($em,
'Foo\BarBundle\Entity\SED\Waste\WasteComponent');
$projectTransformer = new EntityToEntityIdTransformer($em, 'Foo\BarBundle\Entity\Project');
$builder->add('id', 'hidden');
$builder->add(
$builder->create('project', 'hidden', array(
'data' => $options['project'],
'by_reference' => false
))
->addModelTransformer($projectTransformer)
);
$builder->add(
$builder->create('wasteComponent', 'hidden', array(
'data' => $this->getWasteComponent()
))
->addModelTransformer($wasteComponentTransformer)
);
$builder->add('phase', 'hidden', array(
'data' => $this->getPhase()
));
$builder->add('type', 'hidden', array(
'data' => $this->getType()
));
$builder->add('percentDivertedFromLandfill', 'text', array());
$builder->add('wasteContractor', 'entity', array(
'class' => 'Foo\BazBundle\Entity\Contractor',
'property' => 'name',
'attr' => array(
'class' => 'js-select2'
)
));
}
public function getName()
{
return $this->name;
}
/**
* {#inheritDoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => true,
'data_class' => 'Foo\BarBundle\Entity\SED\Waste\WasteContractorEntry'
))
->setRequired(array(
'em',
'project'
))
->setAllowedTypes(array(
'em' => 'Doctrine\Common\Persistence\ObjectManager',
'project' => 'Foo\BarBundle\Entity\Project'
));
}
}
And my model transformer class:
<?php
namespace Foo\BarBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
class EntityToEntityIdTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #var Entity class
*/
protected $entityClass;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om, $className)
{
$this->om = $om;
$this->entityClass = $className;
}
protected function getEntityClass()
{
return $this->entityClass;
}
/**
* Transforms an object (project) to a string (id).
*
* #param Project|null $issue
* #return string
*/
public function transform($entity)
{
if (null === $entity) {
return "";
}
return $entity->getId();
}
/**
* Transforms a string (id) to an object (project).
*
* #param string $id
*
* #return Issue|null
*
* #throws TransformationFailedException if object (project) is not found.
*/
public function reverseTransform($id)
{
if (!$id) {
return null;
}
$entity = $this->om
->getRepository($this->getEntityClass())
->find($id);
if (null === $entity) {
throw new TransformationFailedException(sprintf(
'An entity of class %s with id "%s" does not exist!',
$this->getEntityClass(),
$id
));
}
return $entity;
}
}
I've tried using a adding the transformer as a view transformer instead of a model transformer, however then I just get a slightly different error:
The form's view data is expected to be an instance of class
Foo\BarBundle\Entity\Project, but is a(n) integer. You can avoid this
error by setting the "data_class" option to null or by adding a view
transformer that transforms a(n) integer to an instance of
Foo\BarBundle\Entity\Project.
It seems that setting 'data_class' to null as suggested by the exception message above is the solution. I had previously rejected this as it seems counter-intuitive when we know that the purpose of the field is to reference a project entity.
With the 'data_class' option set to null, the hidden project field contains the project id, and upon submission, calling getProject() on the created entity returns the correct project object.

Silverstripe Model Admin for Pages

I am using the Silverstripe Swipestripe module for an online store. Because of the number of products the client has, it's not practical to nave them navigate to each individual product through the site tree when they want to make changes to a product (which happens fairly regularly) so I'd like to have a modeladmin to list all products and allow them to search for a product by name/stockcode.
I thought this would be solvable in the same way as DataObjects (and searches seem to suggest that people have done achieved this), however when I navigate to products in ModelAdmin view, I get:
Fatal Error : Call to a member function stat() on a non-object in
/path/to/folder/wwwroot/framework/model/DataObject.php on line 3192
<?php
class ProductAdmin extends ModelAdmin
{
private static $managed_models = array('Product');
private static $url_segment = 'product';
private $menu_title = 'Products';
}
Interestingly, pages and other extensions of the page class do work.
Here is the code for the Product class:
class Product extends Page {
/**
* Flag for denoting if this is the first time this Product is being written.
*
* #var Boolean
*/
protected $firstWrite = false;
/**
* DB fields for Product.
*
* #var Array
*/
private static $db = array(
'Price' => 'Decimal(19,4)',
'Currency' => 'Varchar(3)',
'StockCode' => 'Varchar(255)',
'Stock' => 'Int',
'Featured' => 'Boolean',
'YouTubeID' => 'Varchar(255)'
);
/**
* Actual price in base currency, can decorate to apply discounts etc.
*
* #return Price
*/
public function Amount() {
// TODO: Multi currency
$shopConfig = ShopConfig::current_shop_config();
$amount = new Price();
$amount->setAmount($this->Price);
$amount->setCurrency($shopConfig->BaseCurrency);
$amount->setSymbol($shopConfig->BaseCurrencySymbol);
//Transform amount for applying discounts etc.
$this->extend('updateAmount', $amount);
return $amount;
}
/**
* Display price, can decorate for multiple currency etc.
*
* #return Price
*/
public function Price() {
$amount = $this->Amount();
//Transform price here for display in different currencies etc.
$this->extend('updatePrice', $amount);
return $amount;
}
/**
* Has many relations for Product.
*
* #var Array
*/
private static $has_many = array(
'Attributes' => 'Attribute',
'Options' => 'Option',
'Variations' => 'Variation'
);
/**
* Defaults for Product
*
* #var Array
*/
private static $defaults = array(
'ParentID' => -1,
'Stock' => 999
);
/**
* Summary fields for displaying Products in the CMS
*
* #var Array
*/
private static $summary_fields = array(
'Amount.Nice' => 'Price',
'Title' => 'Title'
);
private static $searchable_fields = array(
'Title' => array(
'field' => 'TextField',
'filter' => 'PartialMatchFilter',
'title' => 'Name'
)
);
/**
* Set firstWrite flag if this is the first time this Product is written.
*
* #see SiteTree::onBeforeWrite()
* #see Product::onAfterWrite()
*/
public function onBeforeWrite() {
parent::onBeforeWrite();
if (!$this->ID) $this->firstWrite = true;
//Save in base currency
$shopConfig = ShopConfig::current_shop_config();
$this->Currency = $shopConfig->BaseCurrency;
}
/**
* Unpublish products if they get deleted, such as in product admin area
*
* #see SiteTree::onAfterDelete()
*/
public function onAfterDelete() {
parent::onAfterDelete();
if ($this->isPublished()) {
$this->doUnpublish();
}
}
/**
* Set some CMS fields for managing Products
*
* #see Page::getCMSFields()
* #return FieldList
*/
public function getCMSFields() {
$shopConfig = ShopConfig::current_shop_config();
$fields = parent::getCMSFields();
//Product fields
$fields->addFieldToTab('Root.Main', new PriceField('Price'), 'Content');
$fields->addFieldToTab('Root.Main', new TextField('StockCode'), 'Price');
$fields->addFieldToTab('Root.Main', new TextField('Stock'), 'Price');
$fields->addFieldToTab('Root.Main', new CheckBoxField('Featured'), 'Content');
$fields->addFieldToTab('Root.Images', new TextField('YouTubeID', 'YouTube Video ID (Taken from the end of the video url. ie https://www.youtube.com/watch?v=ABC123 would be ABC123)'));
//Replace URL Segment field
if ($this->ParentID == -1) {
$urlsegment = new SiteTreeURLSegmentField("URLSegment", 'URLSegment');
$baseLink = Controller::join_links(Director::absoluteBaseURL(), 'product/');
$url = (strlen($baseLink) > 36) ? "..." .substr($baseLink, -32) : $baseLink;
$urlsegment->setURLPrefix($url);
$fields->replaceField('URLSegment', $urlsegment);
}
if ($this->isInDB()) {
//Product attributes
$listField = new GridField(
'Attributes',
'Attributes',
$this->Attributes(),
GridFieldConfig_BasicSortable::create()
);
$fields->addFieldToTab('Root.Attributes', $listField);
//Product variations
$attributes = $this->Attributes();
if ($attributes && $attributes->exists()) {
//Remove the stock level field if there are variations, each variation has a stock field
$fields->removeByName('Stock');
$variationFieldList = array();
foreach ($attributes as $attribute) {
$variationFieldList['AttributeValue_'.$attribute->ID] = $attribute->Title;
}
$variationFieldList = array_merge($variationFieldList, singleton('Variation')->summaryFields());
$config = GridFieldConfig_HasManyRelationEditor::create();
$dataColumns = $config->getComponentByType('GridFieldDataColumns');
$dataColumns->setDisplayFields($variationFieldList);
$listField = new GridField(
'Variations',
'Variations',
$this->Variations(),
$config
);
$fields->addFieldToTab('Root.Variations', $listField);
}
}
//Ability to edit fields added to CMS here
$this->extend('updateProductCMSFields', $fields);
if ($warning = ShopConfig::base_currency_warning()) {
$fields->addFieldToTab('Root.Main', new LiteralField('BaseCurrencyWarning',
'<p class="message warning">'.$warning.'</p>'
), 'Title');
}
return $fields;
}
/**
* Get the URL for this Product, products that are not part of the SiteTree are
* displayed by the {#link Product_Controller}.
*
* #see SiteTree::Link()
* #see Product_Controller::show()
* #return String
*/
public function Link($action = null) {
if ($this->ParentID > -1) {
return parent::Link($action);
}
return Controller::join_links(Director::baseURL() . 'product/', $this->RelativeLink($action));
}
/**
* A product is required to be added to a cart with a variation if it has attributes.
* A product with attributes needs to have some enabled {#link Variation}s
*
* #return Boolean
*/
public function requiresVariation() {
$attributes = $this->Attributes();
return $attributes && $attributes->exists();
}
/**
* Get options for an Attribute of this Product.
*
* #param Int $attributeID
* #return ArrayList
*/
public function getOptionsForAttribute($attributeID) {
$options = new ArrayList();
$variations = $this->Variations();
if ($variations && $variations->exists()) foreach ($variations as $variation) {
if ($variation->isEnabled()) {
$option = $variation->getOptionForAttribute($attributeID);
if ($option) $options->push($option);
}
}
$options = $options->sort('SortOrder');
return $options;
}
/**
* Validate the Product before it is saved in {#link ShopAdmin}.
*
* #see DataObject::validate()
* #return ValidationResult
*/
public function validate() {
$result = new ValidationResult();
//If this is being published, check that enabled variations exist if they are required
$request = Controller::curr()->getRequest();
$publishing = ($request && $request->getVar('action_publish')) ? true : false;
if ($publishing && $this->requiresVariation()) {
$variations = $this->Variations();
if (!in_array('Enabled', $variations->map('ID', 'Status')->toArray())) {
$result->error(
'Cannot publish product when no variations are enabled. Please enable some product variations and try again.',
'VariationsDisabledError'
);
}
}
return $result;
}
}
Can anyone suggest what I'm doing wrong here or an alternative way to do what I'm trying to achieve?
Cheers
Just in case anyone still having the same problem.
So, it happens if you have installed Product Categories module for swipestripe.
The cause of this is private static $searchable_fields in ProductCategory_Extension class in ProductCategory.php file in that module. Just comment out that field and it will work.
It is because the dataobject class tries to stat Category as a class - which obviously doesnt exist.
I will fix it and push to github if I get some time. Just wanted to update here so that others dont waste time scratching head why it doesnt work.

symfony2 - error when trying to persist form data used in one-to-many/many-to-one

I have a form for submitting an article. The form has a category multi-select box that pulls categories from the DB using my Category.php entity. The form generates fine, but I get the following error when trying to submit:
Property "categories" is not public in class "Natknow\EditorBundle\Entity\Article". Maybe you should create the method "setCategories()"?
Template Form Code:
[...]
<div id="artFormG1">
<?php echo $view['form']->row($form['title']) ?>
<?php echo $view['form']->row($form['description']) ?>
<?php echo $view['form']->row($form['source']) ?>
</div>
<div id="artFormG2">
<?php echo $view['form']->row($form['categories']) ?>
</div>
<?php echo $view['form']->row($form['body']) ?>
<br />
<input class="button" type="submit" formnovalidate = "true" />
<?php echo $view['form']->rest($form) ?>
[...]
ArticleType.php - Form Class
[...]
$builder->add('title', 'text', array(
'attr' => array('class' => 'artFormLeft')
));
$builder->add('description', 'text', array(
'attr' => array('class' => 'artFormLeft')
));
$builder->add('source', 'text', array(
'attr' => array('class' => 'artFormLeft')
));
$builder->add('categories', 'entity',
array('class' => 'NatknowEditorBundle:Category',
'property' => 'name',
'expanded' => false,
'multiple' => true,
)
);
$builder->add('body', 'textarea', array(
'attr' => array('class' => 'tinymce'),
));
[...]
Article.php - article Table Entity
[...]
/**
* Add categories
*
* #param Natknow\EditorBundle\Entity\ArticleCategory $categories
*/
public function addArticleCategory(\Natknow\EditorBundle\Entity\ArticleCategory $categories)
{
$this->categories[] = $categories;
}
/**
* Get categories
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
[...]
ArticleCatigory.php - article_category Table Entity
[...]
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Article", inversedBy="categories", cascade={"all"})
*/
protected $article;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="articles", cascade={"all"})
*/
protected $category;
/**
* #ORM\Column(type="smallint")
*/
protected $isParent = 0;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set isParent
*
* #param smallint $isParent
*/
public function setIsParent($isParent)
{
$this->isParent = $isParent;
}
/**
* Get isParent
*
* #return smallint
*/
public function getIsParent()
{
return $this->isParent;
}
/**
* Set article
*
* #param Natknow\EditorBundle\Entity\Article $article
*/
public function setArticle(\Natknow\EditorBundle\Entity\Article $article)
{
$this->article = $article;
}
/**
* Get article
*
* #return Natknow\EditorBundle\Entity\Article
*/
public function getArticle()
{
return $this->article;
}
/**
* Set category
*
* #param Natknow\EditorBundle\Entity\Category $category
*/
public function setCategory(\Natknow\EditorBundle\Entity\Category $category)
{
$this->category = $category;
}
/**
* Get category
*
* #return Natknow\EditorBundle\Entity\Category
*/
public function getCategory()
{
return $this->category;
}
[...]
Category.php - category Table Entity
[...]
/**
* Add articles
*
* #param Natknow\EditorBundle\Entity\ArticleCategory $articles
*/
public function addArticleCategory(\Natknow\EditorBundle\Entity\ArticleCategory $articles)
{
$this->articles[] = $articles;
}
/**
* Get articles
*
* #return Doctrine\Common\Collections\Collection
*/
public function getArticles()
{
return $this->articles;
}
[...]
The Controller:
[...]
$art = new Article();
$em = $this->getDoctrine()
->getEntityManager();
$repo = $this->getDoctrine()
->getRepository('NatknowEditorBundle:Article');
$success = "<h3>Use this form to add an article:</h3>";
$form = $this->createForm(new ArticleType(), $art);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($art);
$em->flush();
$success = "You have successfully added the article '"
. $art->getName() . "'!";
$articles = $repo->findAllPastDay();
}else {
$success = "There was an error validating the form data!";
}
return $this->render('NatknowEditorBundle:Default:insertArticle.html.php',
array('form' => $form->createView(), 'status' => $success, 'arts' => $articles,)
);
}
[...]
Heads-up: I used the command line utility to generate the ArticleCategory.php entity, without error. However, I'm not 100% certain it's set up to do what I would like.
You need to add a setCatagories() to the Article entity to get past the posted error.
Could run into other issues. Getting many-many forms to work as desired can be tricky.
Become very familiar with:
http://symfony.com/doc/current/cookbook/form/form_collections.html
You should name addArticleCategory() as addCategory(), then Symfony (master) will recognize it just fine.
Don't forget about using:
Doctrine\Common\Collections\ArrayCollection;
and:
public function __construct()
{
$this->categories = new ArrayCollection();
}
(Article.php)
Not sure if this was the problem, but I just ran into something similar.

Resources