T&A: Export/Import Refactoring - #11673
lukas-heinrich wants to merge 62 commits into
Conversation
79f1241 to
62522d4
Compare
0bcf590 to
1ffbb7e
Compare
There was a problem hiding this comment.
Thank you very much for the PR @lukas-heinrich
This is a huge improvement! ...even if my list of "complaints" is rather long, from where I stand, they deal with "technical details".
Please see this as a first review, so that we can start iterating. I tried to be thorough, but this is quite a bit to process. I added some details just because they registered with my senses, but mostly it will be about the general structure as this is a lot to go through.
First a very general observation:
For me the deep structure of this code on a first read looks sound. Thanks again for this! ...but it also looks like it tries very hard to be fancy and I think this reduces readability by a lot and leads to code that lies, like here.
For me this feels a lot like you tried to build a generalizable infrastructure for a case were we only need a specific solution. I understand that this seems to come from Symphony and there it might have its reasons and applications, here I do not think it does.
Simplifying the code for me is a rather hard requirement and you would need to show me that life gets really, really hard if we don't simplify it, to sway me. I like boring. This observation will guide a lot of my feedback below.
Additionally, all these Generics, Attributes and similar concepts in PHP look very much tagged on. The moment you need a ReflectionClass to run your code, in my eyes, things are just wrong. ReflectionClasses are for debugging and nowhere else. I think we should get rid of all of it again. I do not believe, as far as I can see, that there is a single thing here that you cannot easily do with good, old, boring code.
E.g. the use of Attributes here seems to only by a solution for a temporary issue as later on we will get the transformations right out Components, but for now you can simply require a Builder that you can then collect and that must have an empty constructor. We can then simply get rid of it as soon as we can use the Components directly.
So I would first ask you:
- Is there an absolute reason we should not get rid of the
Generics, theAttributes, mostmixedtypes and most of the type declarations in Doc-Blocks?
One structural thing, though, and this might rather be a biggy:
-
Normalizable(see below for nomenclature), cannot contain a functionfromNormalized. This MUST go to a factory. We should never require to have an unconfigured object
Then one request that we need to tackle anyway before we can merge this:
- Do we really need the changes outside of the test? They look rather secondary and like they either provide issue fixes or just be there, because you want to do something your way, instead of the way the creator intended it. We should in any case separate them out into their own PRs if we are absolutely sure we really, really need them.
And then there is the nomenclature. I'm not really happy about that (I do not believe this comes as a surprise):
- I will first simply reiterate and make it very clear that you are not going to get this past me: Do not call anything that is not a
normalizationanormalization. How are primitives more "normal" than classes in OOP? I think in this case it should beFromImportandToExport. If you want to make this usable in a more general way (I'm not sure we want, though), then it should be something likeToPrimitivesandFromPrimitives(orToPrintableandFromPrintableorToExchangeFormatandFromExchangeFormat), but then I think we should also move the Logger to use this infrastructure. By the way: I understand why Symphony calls it normalization as the "normal" there is theAPI, I still do not think it is right, even in their case. - Why is
ILIAS\TestQuestionPool\ExportImport\Foundation\Contracts\Envelopecalled "Envelope", but then hastoArrayandfromArray-functions? I think an envelope is actuallyopenedandclosedor (and this is already stretching it)packedandunpacked. I think this should be calledArrayTransformation. - Please do not call
interfacescontracts. they are not really. They are what they say they are: "interfaces". Please remove the whole namespaceContractsand move the corresponding interfaces to the corresponding namespaces. Please also remove allinterfacesthat are not absolutely necessary, i.e. interfaces that only have one implementation and do not function to reduce the public interface of the class, as far as I can see e.g.PipelineandTransformationsdo not provide any added value whatsoever. - We should not talk about "Solutions". It would have been an option, but there is a lot of code that does not take this approach. In general, what you are exporting with the "Solutions" is an "Attempt".
- Can we get rid of the
Bridge\StateHolder? The nomenclature smells (the actual holder is the surrounding class and this is just some kind of "cradle") and it looks utterly unnecessary. - Please remove all functions from
Pipelinethat are not used. I think it is quite all of them. And what is left over, as far as I can see, will be a queue, that you can then execute in one go. This cannot be called aPipelineanyway and thePipesare noPipesas far as I can see. A pipeline never processes its contents, it just transports them and the same goes for a pipe. This for me looks over complicated and overengeneered to the max. There even are\Closuresreturning\Closures,Pipescan also be\Closuresand so on. Please simplify as we need something that only solves our problem. So, from where I stand, this should probably be aQueueofTransformations. Could we simply implement this as anIteratoror a derivative thereof? Please move them to a sub-namespaceQueue. - Please synchronize nomenclature inside the
Foundationand outside of it. I think it should beImport,Export,Normalize,Serialize, andQueue.Bridge, as far as I can see, should beExport.Contracts, as written, must go. - Ok, I put this in here, but it is only loosely about nomenclature. Clean-up of
Foundation:- If I'm understanding this right, we do not need the JSON-implementations, right (at least I didn't find any usages)? So they should leave us. ...and then I'm rather unclear why we have two different XML-Deserializers, but actually no corresponding Serializer. I think we should create symmetry here and only have one Deserializer handling both cases and a corresponding Serializer.
- We have three different concepts with overlapping meaning:
Step,State, andStage.StepandStatein Export andStagein Import, if I'm getting this right. Couldn't we just move everything to aStage-concept to create some symmetry?
Please make the following changes (this is just a list of random observations):
- Please have a look at the different
*.md-files and update them correspondingly (this probably is for later on). - This here is unclear: Why should you be able to process an
ImportObject(whatever that is), if you have read permissions? ...and why do you then get redirected because of a "missing write" even though what you are potentially only missing a read? This looks like a very brown smarty. Additionally: Please do not introduce intermediate variables that are only used once, unless the corresponding code would get really, really hard to read (not the case here, if you strategically employ line breaks). - Please remove the "@inheritdoc"-comments, but add
#[\Override]. I do not think the former does much. - Please make the
ImportContextconcrete. Currently we have no idea what is in there and why. Additionally, it will be serialized at a certain point, so you cannot put in whatever you want and one simply needs to know that. Please convert it to JSON instead of serializing it. - Please do not check for truth where there is none. e.g. here.
- Please do not add
usestatements for the global namespace (e.g. here). - Please remove
ilAssQuestionLifecycle::getInstance()and make the constructorpublic. Looks like a very easy win to get rid of a static method. - Please do not assign variables in conditions and then check for them to be
true, e.g. here. Put the assignment into brackets and then make it very clear what you are expecting (in this case!== null, but you should also check for=== truein case ofbools, as this pattern is very dangerous, but sadly not always easily avoidable). - I think we should add parenthesis around
(!$a instanceof A). I know, I'm mostly against parenthesis, but this is non-obvious and the visual suggests another binding than it actually has. A example can be found here. - Please make all
protectedproperties and functionsprivateunless you are absolutely sure you want to allow somebody to overwrite them (which should very rarely be the case). - I do not think the Id-Envelope is right. It is completely random: Why should an ID be a
booland what do the different things you can put in here have in common? Why would we want an object-string (as far as I can see, it is not even used now)? I think we can simply get rid of this. -
Carrys shouldn't expose their properties. Please provide corresponding getters or even better: More complex functions that provide the information that is really needed outside.
Ok, I will stop here as this is already quite a lot. Maybe we should have a call to expedite the further development? ...otherwise you will also write a book, to really answer everything here.
Thanks again for all the work and best,
@kergomard
This commit was created from various other commits across multiple branches. These were combined to simplify the merge process and to separate the foundation from the implementations in the Questions and Test components.
…r better traceability
Replace the string-key session bag and PHP serialize so wizard state is a typed optional value object and unknown or leftover payloads start empty.
f1b97a3 to
7458098
Compare
7458098 to
9b352dc
Compare
|
Hi @kergomard , Thank you very much for the detailed review and your suport on this major refactoring. As I mentioned in our correspondence via discord, I support most of the suggestions and have implemented them accordingly. Below, I’d like to address a few open points:
That is absolutely correct. When I rebased the code, I noticed that the changes outside the
I understand the reluctance to use this pattern here. In my pov there is unfortunately no other way. The export component from the core creates two
In my opinion, the two concepts
The lifecycles are opposite. The export clears the state after
I've modified the class so that it now only supports numbers, strings, and UUIDs. However, it remains essential to the functionality. Since each ID is also associated with its corresponding object type, we can specifically collect IDs for certain objects during an export. This is what happens f.e. in Thanks again for all the effort you put into this review! |
Hi everyone,
this PR concerns the refactoring of export/import in the Test and TestQuestionPool components.
Objective
This refactoring reintegrates the test export into the intended export process of ILIAS. In detail, the PR pursues the following objectives:
Core idea
The export process is divided into two clearly separated responsibilities:
This separation ensures that the export can be implemented independently of the output format, so that not every exportoption needs to build its own process.
The complete concept, including more detailed explanations, is attached here:
Konzeption_ Test Export.pdf