Doctrine Sluggable Behavior for Foreign Relations - doctrine

I have a database design case which I am curios whether Doctrine ORM supports it out-of-box.
Product:
columns:
id: {type: integer, primary: true, autoincrement: true }
type_id: { type: integer, notnull: true }
brand_id: { type: integer, notnull: true }
relations:
ProductType:
class: ProductType
local: type_id
foreign: id
Brand:
class: Brand
local: brand_id
foreign: id
ProductType:
actAs:
I18n:
fields: { name }
columns:
id: {type: integer, primary: true, autoincrement: true }
name: { type: string(255), notnull: true }
Brand:
actAs:
I18n:
fields: { name }
columns:
id: {type: integer, primary: true, autoincrement: true }
name: { type: string(255), notnull: true }
I want to slugify Products table, ie. products will be reached via their slugs. However, as you see both brand and productype tables has i18n behaviour. And moreover, product doesnt have a name. A product's slug will be: "Brand.name - ProductType.name", and vary with the language served.
For this scenario, is there anyway I can use Sluggable behaviour of Doctrine to sluggify my products automatically. Or do I have to manage it manually?
By the way my environment configuration is:
Doctrine Version: 1.2
Symfony: 1.4.1
Thanks

My understanding is that you need to have the slug in both Product Type and Brand models. You can leave the Product definition as it is. Anyway, I'm assuming from your question that there is only one product for every brand+type (even if it doesn't have to much sense). So ProductType and Brand will be defined like this:
schema.yml
----------
ProductType:
actAs:
I18n:
fields: { name }
actAs:
Sluggable: { fields: [name], uniqueBy: [lang], canUpdate: true }
columns:
...
Then you have to configure your Product route to use the slugs. And after that you will need to configure the action to check what you are getting from the route.
For example, this could be your route for Products:
routing.yml
-----------
product:
url: /:sf_culture/product/:brand_slug/:type_slug
param: { module: product, action: view }
requirements:
sf_culture: (?:en|fr)
sf_method: get
Then in the action you will call to your own findOneBySlugs($brand_slug, $type_slug) method:
product/actions/actions.class.php
---------------------------------
public function executeView(sfWebRequest $request)
{
$product = Doctrine::getTable('Product')
->findOneBySlugs(
$request->getParameter('brand_slug'),
$request->getParameter('type_slug')
);
$this->forward404Unless($product);
}

the problem with that solution are the queries. With:
$product = Doctrine::getTable('Product')
->findOneBySlugs(
$request->getParameter('brand_slug'),
$request->getParameter('type_slug')
);
you are doing a 5-join query if I'm not mistaken. You can improve to do only three (product, brand_translation and producttype_translation)
I'm in a similar situation, and the best option is to create a slug for each product using the brand or product type name in this case. So you would only need:
$product = Doctrine::getTable('Product')
->findOneBySlug($request->getParameter('slug'));
I'm thinking between two options:
Product:
actAs:
Sluggable:
unique: true
fields: [title]
builder: [Slug, slugify]
or using the getUniqueSlug() function on the record class. I think the first option is best so you don't have to worry about uniqueness.

Related

Codeigniter + doctrine weird behavior with onetomany objects

I'm just started with Doctrine with Codeigniter.
I've got a entity called Material with a relatioship onetomany with another entity called elementCombination.
To retrieve all materials I use this code
$query = $this->doctrine->em->getRepository('Entities\Material')->createQueryBuilder('m');
$materials = $query->getQuery()->getResult();
When I lookup the elements (ElementCombination entity) of each material using the getCombination() function I always get the same result for all "material", actually it looks like to merge results as one material could have more elements than others. It's weird, for me.
I "fixed" this by doing this , requerying or refreshing the combination of each material:
foreach ($materials as $key => $material)
{
$this->doctrine->em->flush();
$this->doctrine->em->clear();
//$m_combination_1 is a ArrayCollection of ElementCombination
$m_combination_1= $this->doctrine->em->getRepository('Entities\ElementCombination')->findBy(array('material' => $material->getId()));
$material->setCombination($m_combination_1);
}
I see this is not a good way to do it, so, I'd like to know if is this behavior normal?, What am I doing wrong?
The yml entities are like this.
Material
Entities\Material:
type: entity
table: materials
oneToMany:
similars:
targetEntity: Similar
mappedBy: parent
combination:
targetEntity: ElementCombination
mappedBy: material
.....
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
.....
And ElementCombination
Entities\ElementCombination:
type: entity
table: element_combinations
manyToOne:
material:
targetEntity: Material
inversedBy: combination
fields:
symbol:
type: string
length: 3
nullable: false
id: true
maxValue:
type: decimal
precision: 6
nullable: false
column: max_value
minValue:
type: decimal
precision: 6
nullable: false
column: min_value
avgValue:
type: decimal
precision: 6
nullable: false
column: avg_value
Thanks in advance.
EDIT:
I've found the solution (I tnink) the schema is wrong, I've changed to this, and I could get rid off lots of stuff in the code
Entities\Material:
type: entity
table: materials
oneToMany:
similars:
targetEntity: Similar
mappedBy: parent
manyToMany:
combination:
targetEntity: ElementCombination
inversedBy: materials
cascade: {"remove" , "persist"}
joinTable:
name: material_element_combinations
joinColumns:
material_id:
referencedColumnName: id
inverseJoinColumns:
element_combination_id:
referencedColumnName : id
uniqueConstraints:
material_index:
columns:
- reference
- inner_name
and for the elementcombination
Entities\ElementCombination:
type: entity
table: element_combinations
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
symbol:
type: string
length: 3
nullable: false
maxValue:
type: decimal
precision: 6
nullable: false
column: max_value
minValue:
type: decimal
precision: 6
nullable: false
column: min_value
avgValue:
type: decimal
precision: 6
nullable: false
column: avg_value
Regards

Propel orm 1.6, symfony 1.4 embeded I18N form

I use propelorm (propel 1.6) plugin and symfony 1.4.20.
I have book table with I18n, and this book have relation with gallery table which also have I18n. I fill all book form fields, added two gallery images, filled all images fields. Click on save - it seems all ok: book fields are ok, first and second image have been uploaded, but only first image have filled i18n fields, second image all i18n fields are empty. What's the prob ?
(there's no redeclared doSave(), save(), bind() and other form methods)
schema:
book:
id: ~
title: { type: varchar(255), required: true }
author: { type: varchar(255), required: true }
description: { type: longvarchar, required: true }
sortable_rank: { type: integer, required: true }
is_active: { type: boolean, default: 1 }
_indexes:
active_sort: [ is_active, sortable_rank ]
_propel_behaviors:
sortable:
rank_column: sortable_rank
i18n:
i18n_columns: [title, description, author]
locale_column: culture
locale_alias: culture
default_locale: en
book_gallery:
id: ~
book_id: { type: integer, foreignTable: book, foreignReference: id, required: true, onDelete: cascade }
title: { type: varchar(255), required: true }
image: { type: varchar(255), required: true }
_propel_behaviors:
i18n:
i18n_columns: [title]
locale_column: culture
locale_alias: culture
default_locale: en
book form:
$languages = LanguageQuery::create()->getAllAsArray(); // returns like array('en' => 'english', ...);
$this->embedI18n(array_keys($languages));
$this->widgetSchema->setLabels($languages);
$this->embedRelation('BookGallery', array(
'title' => 'Application book store list',
'item_pattern' => 'Application book store %index%',
));
book gallery form:
$languages = LanguageQuery::create()->getAllAsArray(); // same as in book form
$this->embedI18n(array_keys($languages));
$this->widgetSchema->setLabels($languages);

Symfony 1.4 doctrine - many-to-many relationship with extra field in intermediate table

There are 3 tables: shelf, section and shelf_has_section intermediate table to support n-m relation. The schema build from symfony doctrine:build-schema looks as following.
Simply,
shelf(id, position)
section(id, name)
shelf_has_section(shelf_id, section_id, number_of_books)
The schema.
Shelf:
connection: doctrine
tableName: shelf
columns:
id:
type: integer(4)
fixed: false
unsigned: true
primary: true
autoincrement: true
position:
type: string(255)
primary: false
notnull: true
autoincrement: false
relations:
ShelfHasSection:
local: id
foreign: shelf_id
type: many
Section:
connection: doctrine
tableName: section
columns:
id:
type: integer(1)
primary: true
autoincrement: false
name:
type: string(20)
primary: false
notnull: true
relations:
ShelfHasSection:
local: id
foreign: section_id
type: many
ShelfHasSection:
connection: doctrine
tableName: shelf_has_section
columns:
shelf_id:
type: integer(4)
primary: true
autoincrement: false
section_id:
type: integer(1)
primary: true
autoincrement: false
number_of_books:
type: integer(4)
primary: false
notnull: false
autoincrement: false
relations:
Shelf:
local: shelf_id
foreign: id
type: one
Section:
local: section_id
foreign: id
type: one
I managed to show Sections as a check box list through adding the following relation to Shelf in the schema. I also need to display a text field infront of section check box in order to enter number of books.
Sections:
class: Section
refClass: ShelfHasSection
local: shelf_id
Simply it's like checking the list of checkboxes for available sections and add the number of books for section checked.
I tried to make it through embedRelation() etc, but lack of my symfony knowledge doesn't get me there. Any help highly appreciated.
Theoretically speaking, if an n-m relation table has it own field, it becomes an entity itself, so the doctrine model for n-m relation doesn't match this problem... but:
- First, you must to redefine your schema to add foreignAliases to n-m entity:
ShelfHasSection:
connection: doctrine
tableName: shelf_has_section
columns:
shelf_id:
type: integer(4)
primary: true
autoincrement: false
section_id:
type: integer(1)
primary: true
autoincrement: false
number_of_books:
type: integer(4)
primary: false
notnull: false
autoincrement: false
relations:
Shelf:
local: shelf_id
foreign: id
type: one
**foreignAlias: ShelfHasSections**
Section:
local: section_id
foreign: id
type: one
**foreignAlias: ShelfHasSections**
If your are trying to make it work on a generated module, your checkboxes idea has no solution. I'll suggest you to use the ahDoctrineEasyEmbeddedRelationsPlugin, and embed the relation ShelfHasSections to your shelf form. The ShelfHasSectionForm embed by the plugin may have an autocomplete field for the section, and the input for the number of books. So, when you want to relate some shelf to one section, add one form (using the plugin) and select the values.
The latest problem become on how to avoid duplicate sections.... I guess you should apply some javascript filter to remember witch section are already related and send it with the autocomplete query, so the doctrine query could exclude this sections.... Sounds very nasty, and it is, but it's the only one solution I can figure out.
If you not using a generator, but some custom actions/template, I guess the problem become easier:
Embed 1 CustomShelfHasSectionForm to your Shelf Form (one for each section). Then, add one checkbox widget (maybe with sfWidgetFormInputCheckbox) it each form to select the relation or not. Them, processing the form remove those embedded form wich no checkbox selected. Somethig like:
class CustomShelfHasSectionForm extends ShelfHasSectionForm {
public function configure() {
unset($this['shelf_id']);
$this->widgetSchema['selected'] = new sfWidgetFormInputCheckbox();
$this->validatorSchema['selected'] = new sfValidatorBoolean(array('required' => false));
}
}
class CustomShelfForm extends ShelfForm {
private $unselected_sections = array();
public function configure() {
$sections = Doctrine::getTable('Section')->findAll();
foreach($sections as $section) {
$shelfHasSection = new ShelfHasSection();
$shelfHasSection->setShefl($this->getObject());
$shelfHasSection->setSection($section);
$this->embedForm('section_'.$section->getId(), new CustonShelfHasSectionForm($shelfHasSection));
}
}
public function doBind(array $values) {
$sections = Doctrine::getTable('Section')->findAll();
foreach($sections as $section) {
// Do some debug with print_r($values) to find something like
...
if(empty($values['section_'.$section->getId()]['selected']) {
$this->unselected_sections[] = $section->getId();
}
}
return parent::doBind($values);
}
public function doSave($con = null) {
foreach($this->unselected_sections as $section_id) {
// disembed form, something like
unset($this->embeddedForms["section_".$section->getId()]);
}
}
}
Then, in your action.class.php
$shelfObject = ....
$this->form = new CustomShelfForm($shelfObject);
if(....) {
$this->form->bind...
if($this->form->isValid()) {
$this->form->save();
}
}
This is all "on the air" code, so probably something doesn't work so well. Maybe you can try one of this solutions an let us know how it works for you. I hope this can helpyou.
You're not the first one to ask such a behavior. Sadly, symfony isn't designed to handle an extra field in the relation table.
I did it once in the past but I can't find source code about it. But I remember that this is kind of hack which is hard to handle with embedRelation.
I found few topics around the web that might help you (at least for a starting point):
Form for many-to-many relation with extra field
Extra Field on Doctrine Many-to-Many Relationship Table
(might be too old) Intermediary Tables Question
In the first link, it seems that johandouma has a suitable solution but didn't post it. You might contact him to see if he remember the solution..

Can't delete objects due to foreign key constraints

This is a strange one
Take this schema:
Contact:
actAs: [Timestampable,SoftDelete]
columns:
first_name: { type: string(255), notnull: true }
second_name: { type: string(255), notnull: true }
relations:
Forums:
class: Forum
refClass: ContactForum
local: forum_id
foreign: contact_id
foreignAlias: Contacts
ContactForums:
local: id
foreign: contact_id
class: ContactForum
type: many
foreignType: one
cascade: [delete]
Forum:
actAs: [Timestampable,SoftDelete]
columns:
name: { type: string(255), notnull: true }
relations:
ContactForums:
class: ContactForum
local: id
foreign: forum_id
type: many
cascade: [delete]
ContactForum:
actAs: [Timestampable]
columns:
contact_id: { type: integer, primary: true }
forum_id: { type: integer, primary: true }
Then if we associate a couple of Forum objects to a Contact object and then try and delete this Contact object we get this error message:
Integrity constraint violation: 19
contact_forum.created_at may not be
NULL
If you add SoftDelete to the link table the delete works correctly, and so does the SoftDelete. However we don't want SoftDelete on the link table as it means our primary keys don't work correctly. Is this a bug?
This is a doctrine bug. Bug report here: http://www.doctrine-project.org/jira/browse/DC-795 with patch to fix.
I think your ids for your many to many relationship are screwed up, assuming that symphony is using Doctrine 1.2.2. Try this:
Contact:
actAs: [Timestampable,SoftDelete]
columns:
first_name: { type: string(255), notnull: true }
second_name: { type: string(255), notnull: true }
relations:
Forums:
refClass: ContactForum
local: contact_id
foreign: forum_id
cascade: [delete]
Forum:
actAs: [Timestampable,SoftDelete]
columns:
name: { type: string(255), notnull: true }
relations:
Contacts:
refClass: ContactForum
local: forum_id
foreign: contact_id
cascade: [delete]
ContactForum:
actAs: [Timestampable]
columns:
contact_id: { type: integer, primary: true }
forum_id: { type: integer, primary: true }
In the relationship, when specifying the class with refClass, local and foreign mean "the column on this other class' table that represents me" and "the column on this other classes table that represents the other", respectively.
Edit: I'm not sure if your definition for the Forums relation under Contact is correct either. Assuming you just need a many-to-many relationship, it can be removed.
Double Edit: Look. Here is all the schema you should need for properly functioning and cascading many-to-many relationships. You shouldn't need two relationships defined to properly cascade deletes.

Implement self-relations

Can anyone give me an example of how to implement a class self-relation?
You need something like this:
MenuOption:
tableName: MenuOption
columns:
id:
type: integer(4)
autoincrement: true
child_menu_option_id:
type: integer(4)
null: true
... (more columns)
relations:
ChildMenuOptions:
class: MenuOption
foreignAlias: News
foreign: id
local: child_menu_option_id
You'd refer to your children like this once you've loaded a MenuOption object:
$menuOption = Doctrine_Query::create()
->from("MenuOption")
->where("stuff here")
->fetchOne();
$children = $menuOption->ChildMenuOptions; // this will be a Doctrine_Collection object

Resources