I am currently coding a plugin for ILIAS. The plugin itself is not at all complex but it contains several issues whereas I think we could make it simpler as it is.
The situation is following: We have a global advanced meta data field added in the user defined meta data section with a bijective identifier. The field is activated at a repository objected named course. We have manipulated the GUI with the plugin based on ilUIHookPluginGUI.
The code for this is ... well ... see it for yourself.
First of all we save the ID of the new meta data field in the settings at the ConfigGUI for the plugin:
$field_settings = new ilSetting("foo");
$field_id_value = $field_settings->set("field_id",$_POST["field_id"]);
In our class which extends ilUIHookPluginGUI we are loading the setting as following and we have the ID of the field:
$field_settings = new ilSetting("foo");
$field_id_value = $field_settings->get("field_id");
Now the fun part. With this ID and the ref_id of the object (well, we also load the object to get the ObjId) we can load the value of the meta data field setted at the course:
$object = \ilObjectFactory::getInstanceByRefId($_GET[ 'ref_id' ]);
$obj_id = $object->getId();
$result = $DIC->database()->query("SELECT value FROM adv_md_values_text WHERE obj_id = '".$obj_id."' AND field_id = '".$field_id_value."'");
$value = $DIC->database()->fetchAssoc($result);
$is_active = $value['value'];
The question is ... is there an easier way to achieve my result?
Best,
Laura
Nice question. First of all, note that I consider the advanced metadata service in ILIAS to be lacking a good readme making clear, which hooks the interface is offering for tasks such as yours. Some time ago, I had to deal with this service as well and run into similar issues. Hopefully, your question helps to document this a little better an I myself am looking forward to other suggestions, knowing that mine is not really good as well. If you have any resources, helping pushing the introduction of good readme for services and also pushing services towards using the repository pattern with a clear interface would be highly appreciated.
Concering your question of what can be improved: I see three main issues in the lines of code:
Storing an ID in the config of your plugin. Your plugin will unconfigurable for non-technical people. However, also for you this will be error prone, think about exporting-importing stuff from a test-installation to production.
Access the value by query instead of the service.
Using new and static functions inside your code making it untestable.
Step 1
Lets start with the first one. Note, that I did not manage to solve this one without introducing a new one (a new query). Bad I know. I hope that there is a better solution, I did not find one after quick research. You store the id, since the field title is not securely unique, right? This is correct, however, you could think about storing the tripplet of field_title, record_title and (maybe) scope. Note that you maybe do not need the scope since you want to use this globally. A function return you and array containing field_id and record_id could look like so:
function getFieldAndRecordIdByFieldTitles($field_title, $record_title, $scope_title){
$query = "select field.field_id,field.record_id from adv_mdf_definition as field
INNER JOIN adv_md_record as record ON record.record_id = field.record_id
INNER JOIN adv_md_record_scope as scope ON scope.record_id = field.record_id
INNER JOIN object_reference as ref ON scope.ref_id = ref.ref_id
INNER JOIN object_data as scope_data ON ref.obj_id = scope_data.obj_id
WHERE field.title='$field_title' AND record.title='$record_title' AND scope_data.title = '$scope_title'";
$set = $this->dic()->database()->query($query);
if($row = $this->dic()->database()->fetchAssoc($set))
{
return array_values($row);
}
}
Then get your values like so:
list($field_id,$record_id) = getFieldAndRecordIdByFieldTitles("my_field", "my_record", "my_scope");
Note that I am aware that I am introducing a new query here. Sorry, was the best I could come up with. I am sure there you find a better solution, if your research a bit, let us know if successful. However, we will remove one in the next step.
Step 2
Use the undocumented service, the get your value out of the advance meta data. Since you now have the record id and the field id, you can to that like so:
$record_values = new ilAdvancedMDValues($record_id, $obj_id);
$record_values->read();
$ADTGroup = $ilAdvancedMDValues->getADTGroup();
$ADT = $ilADTGroup->getElement($field_id);
$value = $ADT->getText();
/**if you have text, others are possible, such as:
switch (true) {
case ($ADT instanceof ilADTText):
break;
case ($ADT instanceof ilADTDate):
$value = $ADT->getDate();
break;
case ($ADT instanceof ilADTExternalLink):
$... = $ADT->getUrl();
$... = $ADT->getTitle();
break;
case ($ADT instanceof ilADTInternalLink):
$... = $ADT->setTargetRefId($value);
}
**/
Note that ADT's are also undocumented. There might be a better way, to get a value out of this.
Step 3
Wrap your statics and new into some injectable dependency. I usually use the bloated constructor pattern to do this. Looks like so:
public function __construct(InjectedSettings $mySettings = null)
{
if (!$mySettings) //Case in the default scenario
{
$this->mySettings = new InjectedSettings();
} else //used e.g. for unit tests, where you can stuff the constructor with a mock
{
$this->mySettings = $mySettings;
}
$this->mySettings->doSometing();
}
Note that this is not real dep. injection, still you still use new, but I think a very workable fix to use dep. injection at least for the test context in ilias.
Does this help? I hope there will be other (better answers as well).
Related
Is there is any way to Shortcut this Code
$questions = $this->getdoctrine()->getrepository('AppBundle:TicketQuestions')->find($id = 1);
$question1 = $questions->getQuestions();
$option1 = $questions->getOption1();
$option2 = $questions->getOption2();
$option3 = $questions->getOption3();
Because i want to use more than 20 times.
i use this code to get some questions and options from database for a Ticket system and i hope there is a way to not write a very long code for that.
It's hard to say without seeing your Entities. For example, do you have 20 hardcoded "options" in your "Questions" entity?
If you were to use something like an array of options, you could just use a foreach loop and get all those options. But, that also has some foundation in what your data model looks like.
If you want to use an underlying datastructure (such as an array or Doctrine ArrayCollection) you'll first have to structure your database and data scheme with the correct architecture.
This is a much deeper question than can you just write a shortcut, I don't think your underlying data and architecture are implemented correctly to use data structures (array, ArrayCollections).
Thanks any way guys, i solved it like this -> i wrote new function
public function callQuestions($id){
$questions = $this->getDoctrine()->getRepository('AppBundle:TicketQuestions')->find($id);
$question = $questions->getQuestions();
$option1 = $questions->getOption1();
$option2 = $questions->getOption2();
$option3 = $questions->getOption3();
return array($question,$option1,$option2,$option3);
}
and now i can call it easily like this:
$firstQuestion = TicketsController::callQuestions(1);
i will try also to make it shorter but this one works good now.
TLDR
I like to really focus on keeping business logic away from the view model / controller. I find this sometimes rather hard in Meteor. Maybe I'm missing the point but I am after one of two things really:
1) A really good document explaining at a really low level how reactive values are being used.
2) A package that somehow manages an object so that if any of the setters are altered, they notify all of the get functions that would change as a result.
Unfortunately I've not seen either.
My Example
I have a fair bit ob business logic sitting behind a dialog used to document a consultation. I might have an event that sets a change of state.
I'd like to do something like this in the event:
const cc = new ConsultationEditor();
cc.setChiefComplaint(event.target.value);
console.log(cc.data());
ConsultationDict.set("consEdit", cc.data() );
When the user has updated this value, I'd then like to show a number of fields, based on the change. For this I have a helper with the following:
fields: function(){
console.log("trying to get fields");
const obj = ConsultationDict.get('consEdit');
cc = new ConsultationEditor(obj);
return cc.getFields();
}
But unfortunately this does not work for me.
What is your ConsultationDict?
The way you describe it, you want it to be a ReactiveDict as in the official ReactiveDict package.
https://atmospherejs.com/meteor/reactive-dict
Check this tutorial for examples:
https://themeteorchef.com/snippets/reactive-dict-reactive-vars-and-session-variables/
If you really need more fine tuning in your reactivity, you can also set a dependency tracker tracker = new Tracker.Dependency, and then refer to it wherever you change a variable with tracker.changed() and where the data needs to be notified with tracker.depend() like this:
var favoriteFood = "apples";
var favoriteFoodDep = new Tracker.Dependency;
var getFavoriteFood = function () {
favoriteFoodDep.depend();
return favoriteFood;
};
var setFavoriteFood = function (newValue) {
favoriteFood = newValue;
favoriteFoodDep.changed();
};
getFavoriteFood();
See the full Tracker doc here:
https://github.com/meteor/meteor/wiki/Tracker-Manual
I also found this gist to be useful to build reactive objects:
https://gist.github.com/richsilv/7d66269aab3552449a4c
and for a ViewModel type of behavior, check out
https://viewmodel.meteor.com/
I hope this helps.
I have a loop that iterates through some imported Product data, and uses Doctrine2 to persist it to a database.
For each product I check to see if that productID exists already. If so, update it. If not, create it and persist it.
I do the same with associated entities, which is where I run into problems, for example each Product is related to a Manufacturer.
On each loop I will check to see is the ManufacturerID exists, and if not create/persist it.
If I create ManufacturerID=3 in one iteration, and then later on I have another product with ManufacturerID3, Doctrine doesn't know about it yet because it hasn't been flushed.
I can fix this by doing a flush() after every loop, as opposed to when the loop is completed, but I am wondering if there is a better way, maybe some way for Doctrine to search for objects with ManufacturerID=3 both in the repository and in newly persisted objects?
Flush()ing after every loop works but it doesn't seem like the right way to do it.
$manufacturer = $this->em
->getRepository('AMyBundle:Manufacturer')
->findOneByPosId($item->manufacturerID);
if (!$manufacturer)
{
$manufacturer = new Manufacturer();
$manufacturer->setPosId($item->manufacturerID);
$this->em->persist($manufacturer);
}
You know what they say: "Early optimization is the root of all evil" D. Knuth
Check this simple optimization and if you need better times then pull up your sleeves and go down, otherwise just move on.
I added a small benchmark for testing, remember to check both variants with the empty database.
$time1 = microtime(true);
// function start
if (!$manufacturer)
{
$manufacturer = new Manufacturer();
$manufacturer->setPosId($item->manufacturerID);
$this->em->persist($manufacturer);
$this->em->flush(); // only flush when there's a new manufacturer
}
// end of function
$time2 = microtime(true);
$time = $time2 - $time1;
printr("Time elapsed: $time");
Manufacturer m = new Manufacturer();
m.ManufacturerID = 123;
Database.Load(m);
m.Name = "abc";
Database.Store(m);
'much easier than symfony. Sim. Phony. J/k.
Just for my curiosity (and future knowledge), how does Entity Framework 5 decide when to create a new object vs. referencing an existing one? I might have just been doing something wrong, but it seems that every now and then if I do something along the lines of:
using (TestDB db = new TestDB())
{
var currParent = db.Parents.Where(p => p.Prop == passedProp).FirstOrDefault();
if(currParent == null) {
Parent newParent = new Parent();
newParent.Prop = passedProp;
currParent = newParent;
}
//maybe do something to currParent here
var currThing = db.Things.Where(t => t.Prop == passedPropTwo).FirstOrDefault();
currThing.Parent = currParent;
db.SaveChanges();
}
EF will create a new Parent in the database, basically a copy of the currParent, and then set the Parent_ID value of currThing to that copy. Then, if I do it again (as in, if there's already two of those parents), it won't make a new Parent and instead link to the first one. I don't really understand this behavior, but after playing around with it for a while something like:
using (TestDB db = new TestDB())
{
var currParent = db.Parents.Where(p => p.Prop == passedProp).FirstOrDefault();
if(currParent == null) {
Parent newParent = new Parent();
newParent.Prop = passedProp;
currParent = newParent;
}
//maybe do something to currParent here
var currThing = db.Things.Where(t => t.Prop == passedPropTwo).FirstOrDefault();
currThing.Parent = db.Parents.Where(p => p.ID == currParent.ID).First();
db.SaveChanges();
}
seemed to fix the problem. Is there any reason this might happen that I should be aware of, or was there just something weird about the way I was doing it at the time? Sorry I can't be more specific about what the exact code was, I encountered this a while ago and fixed it with the above code so I didn't see any reason to ask about it. More generally, how does EF decide whether to reference an existing item instead of creating a new one? Just based on whether the ID is set or not? Thanks!
If your specific instance of your DBContext provided that specific instance of that entity to you, then it will know what record(s) in the database it represents and any changes you make to it will be proper to that(those) record(s) in the database. If you instantiate a new entity yourself, then you need to tell the DBContext what exactly that record is if it's anything but a new record that should be inserted into your database.
In the special scenario where you have multiple DBContext instances and one instance provides you this entity but you want to use another instance to work with and save the entity, then you have to use ((IObjectContextAdapter)firstDbContext).ObjectContext.Detach() to orphan this entity and then use ((IObjectContextAdapter)secondDbContext).ObjectContext.Parents.Attach() to attach it (or ApplyChanges() if you're also editing it - this will call Attach for you).
In some other special scenarios (your object has been serialized and/or you have self-tracking entities), some additional steps may be required, depending on what exactly you are trying to do.
To summarize, if your specific instance of your DBContext is "aware" of your specific instance of an entity, then it will work with it as if it is directly tied to that specific row in the database.
I have some values stores in my model. I need to create a copy of those values, make some changes, and then output those changes without affecting the model values.
var my_source:Array = model.something.source
var output:Array = new Array();
for each (var vo:my_vo in my_source) {
if (vo.id == 1) {
vo.name = 'Foo';
output.push(vo);
}
else if (vo.id == 21) {
vo.name = 'Bar';
output.push(vo);
}
}
return output;
So, this works fine, except that any changes that are made when looping through my_source also seems to affect model.something. Why do changes to the my_source array affect the model? How do I prevent this from happening?
I've mentioned how to do this in my blog, but short answer is use ObjectUtil.copy(). What you're trying to do isn't copying since Flash uses reference based objects, so you're only copying the reference to the other array. By using ObjectUtil.copy(), you're doing what's called a 'deep copy' which is actually recreates the object in a new memory location.
You are dealing with references to data, not copies of data. This is how ActionScript-3 (and many other languages) works.
When you create the my_source variable, you are creating a reference to model.something.source, which also includes all of the references to your model objects. Further, when you loop through the my_vo objects, you are also getting a reference to these objects. This means that if you make changes to the object in this loop, you are making changes to the objects in the model.
How do you fix this? Inside your loop, you will need to make a copy of your object. I don't know what my_vo looks like, but if you have any other objects in that object tree, they would be references as well, which would probably require a "deep copy" to achieve what you want.
The easiest way (but usually not the most efficient way) to achieve a "deep copy" is to serialize and de-serialze. One way to achieve this:
function deepCopy(source:Object):* {
var serializer:ByteArray = new ByteArray();
serializer.writeObject(source);
serializer.position = 0;
return serializer.readObject();
}
Then, in your loop, you can make your copy of the data:
for each(var vo:my_vo in my_source) {
var copy:my_vo = deepCopy(vo);
// act on copy instead of vo
}