I am using dataobjectmanager with a many_many relationship (I can't use manymanydataobjectmanager for this) between owner and car. Whenever a new car is created I iterate through all instances of owner and add it's id to a linked table along with the new car ID.
My problem is that the code for doing this is within the onafterwrite method and is called twice. I'm not sure why. I've also noticed that for my three owners it is creating rows in the linked table oddly. The first two IDs will be in order then it will stick one. So it'll be rows 1, 2 and 4 with no 3.
This is my onAfterWrite method
public function onAfterWrite() {
if(Permission::check('ADMIN')){
$Pages = DataObject::get('Owner');
foreach($Pages as $owner) {
DB::query("INSERT INTO Owner_Cars (OwnerID, CarID) VALUES ('". $owner->ID . "', '" . $this->ID . "')");
}
}
else {
echo "Failure";
return false;
}
return parent::onAfterWrite();
}
I'd appreciate any advice you could give me.
Thanks
The onAfterWrite() method is probably called twice because write() is called twice. The most common reason that this happens is that:
A write() happens to generate the ID
Then another write() happens as part of saving related records that rely on that ID.
In general, I don't think you can rely on onAfterWrite() being called once: write() is supposed to be designed so that it can be called any old number of times and will only actually affect the database if there are changes to be made.
You would need need to make your code call the necessary DELETE, INSERT, and/or UPDATE statements to be compatible with this. You might, for example:
Select all the Owner_Cars records where OwnerID = $owner->ID
Delete any not in the $Pages list
Insert any from the $Page list that aren't already in Owner_Cars
One other suggestion I would make, if you can, is to try out SilverStripe 3. SilverStripe 3's GridField handles this kind of stuff more robustly out of the box and you might find it easier to build your app on that.
Related
I continue to struggle with Symfony in regards to where to put some logic and functions.
In this case, I have a simple query. I want to know how many "Profiles" depend on an address.
the query:
SELECT count(*)
FROM beneficiary_profile AS bp
JOIN person AS p ON bp.beneficiary_id = p.id
JOIN contact_address AS ca ON p.contact_address_id = ca.id
WHERE ca.id = 2108 -- address id
the poor way of doing this [in my controller] with entity methods is this
$dependant = 0;
foreach ($address->getPeople() as $person) {
if ($person->getBeneficiaryProfile() !== null) {
$dependant++;
}
}
ultimately, this serves as a flag to print a warning about editing a dependent address record.
My first thought was to add a new method to the entity, $address->isDependent() that would return bool based on ($count > 1) but this would require me to get the entity repository from the entity.
Soon there will be a whole host of logic to go with unlinking then deletion (or not) to prevent orphan records. The logic is not as simple as cascading as there can be many people between the address and profile. there is a house record in the mix as well.
Should I just build my query in the address repository, then set the flag in the controller? (twig reads the flag and displays the warring or not)
Entity repository is the class which holds all your queries to database related to some entity, in your case it is address. So, yes, you should create new method in AddressRepository and use where you need.
Not sure what you mean by code reuse, you can get repository almost everywhere in symfony classes. So, you write something like
$count = $this->getDoctrine()
->getRepository(ContactAddress::class)
->getDependencyCount($address->getId());
And use this $count variable in your code. As I already said it is simple and clear.
And in symfony4 you can inject even the repository in your controller, so code can be reduced to something like:
public function __construct(AddressRepository $repo)
{
$this->repo = $repo;
}
public function someAction()
{
$count = $this->repo->getDependencyCount($address->getId());
}
I've got dataobjects called attribute and attribute set
The attribute set has this many_many relation to attribute
private static $many_many = array(
'Attributes' => 'Attribute'
);
on attribute I've got this
private static $belongs_many_many = array(
'Sets' => 'AttributeSet'
);
You can add attributes to an set either directly from the set or on the attribute.
Now I need to know when a new attribute is added to a set, to update another content afterwards. I tried it with
public function onBeforeWrite(){
parent::onBeforeWrite();
if( $this->isChanged('Attributes') ){
$this->Title = 'test';
}
}
on the attribute set, but like presumed, it doesn't work, because the set get's not written if a new attribute is added.
Is there a way to do this?
Thank you in advance
You can serialize in some way like json_encode the ManyManyList and store it in a private variable during the init stage, then you can deserialize it during the onBeforeWrite and check for differences.
It's not an efficient task, but I think it's the only way you have to achieve your goal.
Couldn't you do something like this?
public function onBeforeWrite(){
parent::onBeforeWrite();
foreach($this->Attributes() as $attribute) {
if($attribute->isChanged()) {
$this->Title = 'test';
break;
}
}
}
Update: I now realise that this will not work for objects that are deleted. Maybe it is an option to do things the other way around. So do an onBeforeDelete on the many_many objects that sets the field in the "parent(s)" and then saves it. You could even do this for onbeforeWrite as well...
update 2:
It is a little unclear what you want. Do you want to know if the many_many objects have changed, regardless of when this happens, or do you just want to know if they change during the current page load?
isChanged only works when you load the object from the database, and then change something during the same cycle. The remainder of the current execution cycle, isChanged will return true. The next cycle, the object is reloaded, and isChanged returns back to false.
If you want to know if something changed since the last time you opened the parent object, you should store it in the database itself, or in the parent object (also in the db). This is quite easy, by just changing the parent object(s) with a boolean flag, and then saving again. If you want to track changes you need to implement something like #g4b0 suggests, or maybe try to add versioning to your objects. But the latter would probably force you to do a lot of custom coding.
I'd like to attach autocomplete to a particular list of fields in Drupal 7. The fields have FIELD_CARDINALITY_UNLIMITED, so there could be anywhere from 1 to whatever. I'm using the following code:
function mymodule_form_alter(&$form, &$form_state, $form_id) {
if (array_key_exists('mymodule', $form)) {
$indices = array_filter(
array_keys($form['mymodule']['und']),
function($item) {
return is_numeric($item);
}
);
foreach($indices as $index) {
$form['mymodule']['und'][$index]['value']['#autocomplete_path'] = 'api/node/title';
}
}
}
...however, my autocomplete behavior is not being attached. I've used the exact same code in a similar situation - the only difference is that I was adding the autocomplete to a field that had a cardinality of 1 rather than unlimited. That doesn't seem like it should change anything. I've verified that the autocomplete is attaching by doing a debug($form['mymodule']) after the assignment statement, and it is definitely there. I have also debugged the exact array path I am trying to get in each iteration of the foreach loop, and it is definitely the correct form value.
EDIT: Is it possible that the issue is with more than one module altering this form using hook_form_alter()? I'm performing the exact same operation as above (but on a single field) in a different module, on the same form.
EDIT2: I've noticed that if I put a debug statement inside the foreach loop, I see the autocomplete value is set on the proper value each iteration. If I place the debug statement outside the foreach loop, the autocomplete path is no longer set. Somehow, either during the course of iteration, or after iteration, it looks like my changes are being destroyed? I tested this by assuming $index to be 0, and writing a hard-coded statement to attach autocomplete - this allowed auto complete to work correctly. To be clear, I am seeing something like the following:
function mymodule_form_alter(&$form, &$form_state, $form_id) {
if (array_key_exists('mymodule', $form)) {
$indices = array_filter(
array_keys($form['mymodule']['und']),
function($item) {
return is_numeric($item);
}
);
foreach($indices as $index) {
$form['mymodule']['und'][$index]['value']['#autocomplete_path'] = 'api/node/title';
// Debug statements here show that the value '#autocomplete_path' is set properly
debug($form)['mymodule']['und'][$index]['value']);
}
// Now, the '#autocomplete_path' key does not exist
debug($form)['mymodule']['und'][0]['value']);
// This will make autocomplete attach correctly
$form['mymodule']['und'][0]['value']['#autocomplete_path'] = 'api/node/title';
}
}
You've spelt it #autcomplete_path...it should be #autocomplete_path :)
If you're defining the field (and widget) yourself then you should just add the autocomplete in your module's implementation of hook_field_widget_form() rather than altering the form.
If you're not defining the widget yourself, take a look at hook_field_widget_form_alter() and hook_field_widget_WIDGET_TYPE_form_alter() which will let you alter the widget form for a specific field.
Try this:
1) change ['mymodule']['und'][$index]['value'] in your code to the id of your text form input example
$form['search_form_block']
['#autocomplete_path']='yourcall_back_function_which_returns_data';
I think the mistake is your are trying to work to replace the value of the the field but you have to change the value of the format widget. In this case the input field.
2) Also make sure 'api/node/title' call back works using x debug.
Let me know if it worked.
Cheers,
vishal
I resolved the problem by manually enumerating my indices rather than programmatically doing so, e.g. $form['mymodule']['und'][0]... - this appears to be a PHP issue related to scoping of variables in foreach rather than a Drupal problem.
I need to add an additional field to InventJournalTrans, that after posting will show up in the InventTrans table. The field is a reference column to a record in another table. What method(s) do I need to modify to make this behavior happen?
Currently, I have already added the fields to both tables and modified the Form to allow the user to enter and save the new field. I just can't seem to find the bottom of the rabbit hole on where the actual posting to InventTrans is occurring.
Ideally, it should just be a:
inventTrans.ReasonRefRecId = inventJournalTrans.ReasonRefRecId;
assignment statement before the
inventTrans.insert();
call. Anybody have a clue on where this is at?
The link above does contain the solution -- I have included the code from that page in case that page disappears or no longer becomes available. Thanks to gl00mie for answering on that site and providing this answer.
You should create a new InventMovement method like this:
public MyNewFieldType myNewField()
{
return MyNewFieldType::DefaultValue; // suppose your new field is an enum
}
Then modify \Classes\InventMovement\initInventTransFromBuffer
void initInventTransFromBuffer(InventTrans _inventTrans, InventMovement _movement_orig)
{
// ... append this line to the end of whatever else is already in this method
_inventTrans.MyNewField = this.myNewField();
}
And finally overload the new method in the InventMov_Journal class:
public MyNewFieldType myNewField()
{
return inventJournalTrans.MyNewField;
}
Please can someone help me make sense of the Batch madness?
I'm trying to debug an Axapta 3.0 implementation that has about 50 Batch Jobs. Most of the batched classes do not implement the description() method, so when you look at the Batch List form (Basic>>Inquiries>>Batch list) the description field is blank. You can see the Batch Group and the Start Time, etc. but you can't tell which class is actually being called.
The Batch table contains a hidden field called ClassNum which identifies the ID property of the class. Can anyone tell me how I can find the corresponding class from the ID? Once I've identified the culprits I can add descriptions.
I tried using the standard Find function on the AOT but it doesn't pick them up.
Any suggestions would be most welcome!
Many thanks,
Mike
Jay's answer provides two comprehensive solutions.
I've just discovered that the global class ClassId2Name does the same thing, so you can simply have:
display str Classname()
{
return ClassId2Name(this.ClassNum);
}
There atleast two ways to do this, you can use the DictClass class:
display ClassName className()
{
DictClass dictClass = new DictClass(this.ClassNum);
;
if(dictClass!=null)
return dictClass.name();
return '';
}
Or using the UtilIdElements table:
display ClassName className()
{
UtilIdElements utilIdElements;
;
select utilIdElements where utilIdElements.id==this.ClassNum && utilIdElements.recordType==UtilElementType::Class;
if(utilIdElements)
return utilIdElements.name;
return '';
}
Alternative to get ClassName if ClassNum is not available.
display str Classname()
{
return classId2Name(ClassIdGet(this));
}