Dependency Injection Breaks Encapsulation

This is unfortunately Tony’s basic problem. He only sees a part of a concept or twists even the meaning of a concept, in order to fit his own needs. He doesn’t learn what is actually being said/ taught/ communicated. His reasoning for his 9000 line monster class being within the definition of SRP is another perfect example.

Scott

And that part is easy. He based it on the article you provided and all the comments you’ve made thus far in this topic… so again, I sit here confused.

The articles on your website don’t tell even half the story of the truth your code speaks Tony.

How about explaining how you unit test your code? Or does unit testing also solve a problem you don’t have?

Scott

I think Tony already explained in one of the older posts he made, that he never did Unit Testing and doesnt plan on doing it anyway. Apparently he doesnt see this as a problem, and hes proud of the fact that he does no Unit Testing.

To set a good example. To not fall to their level. Because you don’t need everyone to agree with you. Because you don’t have to justify every single opinion you have or action you take.

Pick any one of these, really.

3 Likes

I’d be so bold to also say this is another sign of the Dunning Kruger effect. You know I am getting at something, which you can’t properly answer, so you call my arguments petty and groundless accusations, as if it would take me having written a framework to be able to tell Tony the Great he is doing something wrong. It is you considering yourself an expert, because you have built a framework, which is unfortunately further from the truth than you’d like to realize. You are an expert of your own framework, which I gather very few people have the want to master. The reason nobody wants to master it or use it is because you are NOT an expert at OOP.

I don’t have a beef with you or your framework Tony. I have a beef that you come to forums like this one or LinkedIn, where I got to know you first, and you make arguments nobody really wants to hear and you make arguments, which might lead the unwitting to think wrongly about OOP methodologies. And you write blog posts that are arrogantly wrong and insulting. It is the “in the land of the blind, the one-eyed man (thinks he) is king” issue.

In some ways, I get the feeling you actually know you are wrong in your actions and thinking, deep down inside, but the big hairy monster called Radicore Framework causes you to be like you are. Although, I don’t know how an inanimate object can control ones own opinion, viewpoints and actions, you seem to be in that terrible position.

In other words, if you said yesterday, today I am throwing away everything I had done in the past (and you can make that decision), you’d know that today, you’d have to do your OOP differently…do OOP properly. You’d have to actually learn and use the better methods and practices 99% of the devs use today (and used in the past too) and stop looking at the other 1% for validation for doing things wrong, as being right.

The other thing you’ll be able to do, if the above ever happened is, you’ll be able to ask questions and instead of have it be a lead in to argue your incorrect view points and defending your framework. It will be a way to learn and constantly expand your knowledge, which is what a community is for.

Scott

2 Likes

This is a very good point. Tony, this is a serious question: If you were starting a new project that wasn’t based on RADICORE, maybe a e-commerce website or something else that’s not suited to it, would you use things like autoloaders, visibility modifiers, unit testing, php5 constructors, namespaces, DI and all that other stuff you call “optional”?

I 100% agree with this. I’ve always come to the conclusion that the reason tony is so argumentative is because he thinks if he can justify his bad practices to us (or others) and people agree with him, he doesn’t have to admit his code is awful by any standard.

What’s more worrying, though is Tony’s unwillingness to listen or learn. I look back at code I wrote 5 years ago and realise how much I’ve improved and can identify what I’d do differently now, and most importantly why. In 5 years I’ll probably do the same looking at code I wrote today. To me that’s a good thing as it allows me to grow as a developer. Tony, however, looks back at his 10 year old code and says “Ahh still perfect” which demonstrates an inability to grow as a programmer or learn new techniques.

Some of the code I wrote 10 years ago is as bad as Tony’s (No 9000 line classes, mind, but all the other stuff with mixed responsibilities, procedural code shoe-horned in to classes, global state, etc… however, I was 18 with no real formal training or experience!), the difference is I am happy to admit it was bad and can highlight how I’ve improved as a developer and you know what, if I hadn’t written it, I wouldn’t have learnt from it. Tony, on the other hand, once he’s written something it seems the ideas within it are then set it stone and can never be changed or even questioned.

1 Like

Incorrect. I still do unit testing, integration testing, system testing and user acceptance testing, but it is not automated.

It says quite specifically that Dependency Injection can be applied wherever one class sends a message to another. This implies that I still have a choice as to whether I use DI or not, and I choose not to in those circumstances which are outside the original design parameters.

Just because some people have (incorrectly) assumed that information hiding is the same as implementation hiding does not make it so.

Haha wow that’s some good post-rationalisation there. Again we’re back to this nonsense about “original design parameters” Stop trying to weasel your way out of actually making a valid point. Given any two methods you can, by some metric argue that one is better than another. You have yet to show a situation where a singleton is measurably better than DI (And remember you need to tell us the metric you’re using!)

This statement is also true, of course:

A singleton can be applied wherever one class sends a message to another. This implies that I still have a choice as to whether I use a singleton or not, and I choose not to in those circumstances which are outside the original design parameters.

Your argument is dead in the water here, tony and you know it.

And for the hundredth time: Stop changing this subject and answer this question:

Why is a singleton more appropriate than DI when there is only one possible implementation of the dependency? And how are you measuring that “appropriateness”?

I disagree. Robert C. Martin’s article clearly demonstrates a situation where DI has distinct benefits. Just because the same technique can be used in other circumstances for other purposes is no justification for saying that DI should be used for every single dependency regardless of the circumstances.

Don’t you see how nonsensical this argument is? The inverse is also true: It’s also no justification for not using DI in other circumstances. The very paper you’re quoting simply shows one benefit.

Once again I point you to:

and

This is the argument you are making here. It’s a fallacy so stop making it, it’s not a valid argument.

Information hiding is not important. Changing class properties from “public” to “private” maes absolutely no difference to the code which is executed. All it does is impose restrictions on developers.

…and if you cannot see why imposing restrictions on developers is important then there really is no point continuing. I’m not going to explain it to you again but there’s plenty of information (not hidden!) easily accessible via google.

I keep asking you this and you keep ignoring it: What is the point you are trying to make? Or are you just diverting attention away from the question you cannot answer about DI?

DI is for injecting dependencies, and may be implemented in several ways, either with or without a dependency injection container. When I want to inject a dependency then I use DI. My implementation may be different from yours, but that is irrelevant. Where I have a dependency but choose not to inject it, then I don’t use DI, and the method I use instead of DI should be irrelevant. I should not have to prove that my method of not implementing DI is better than implementing DI. It should be sufficient for me to say that when I do not have the circumstances for which that pattern was initially designed then I do not have a good reason to implement that pattern.

If you look at all the proper descriptions od design patterns you should notice that they each identify the particular problem for which they are supposed to be the solution. It should be obvious that implementing a solution when you don’t actually have that problem is not a good idea. Have you heard of those programmers who try to write a program containing all 40+ patterns from the GoF book? Do you think it is a good idea to implement a pattern that you don’t actually need? I personally do not, and neither does Eric Gamma who wrote the following:

He also wrote:

You see the words “when you don’t need it”? That is why I feel justified in saying that when I don’t need a pattern I see no reason to implement it. You may disagree with me, but who are you to say that Eric Gamma is wrong?

The example there is fitting:

If someone wants to believe in leprechauns, they can avoid ever being proven wrong by using ad hoc hypotheses (l.g. by adding “they are invisible”, then “their motives are complex”, and so on)

This is exactly what you are doing here. with “that’s not what it’s intended for”.

Wrong again. If you’re choosing method A over method B, you must have reasoning for choosing one over another. It’s this reasoning we are questioning.

To use your own example: I can choose to crack open a nut with a sledgehammer and it may be justifiable if a sledgehammer is the only tool I have available. The problem is, all your arguments lack this justification.

You may say your reasoning is:

  • A coin toss
  • I didn’t know about DI
  • I didn’t understand why DI is better

Which are all valid reasons for using an alternative in the past, what it doesn’t do is justify their use in the future now you know better. If you want to keep using singletons, you need to justify why they should be used instead of DI. You have not been able to do this.

@TomB I’ve been quiet so far but still reading all these threads. Do you feel that it’s worth keeping this conversation? If you read one of the Tony’s article back in 2003 - 12 years ago (http://www.tonymarston.net/php-mysql/good-bad-oop.html) - we was confronted with the same questions and answers exactly the same way, probably the other guys just given up by exhaustion.

  • The language allows me to do it, therefore it cannot be wrong.
  • It works, therefore it cannot be wrong.

I cannot conceive of any way to achieve my objective without having those variables pass through the business layer, therefore I cannot accept any arguments that say they should not be there.


An abstract superclass containing generic code which can be applied to any database table/entity.
A concrete subclass for each individual database table/entity containing the specific information required to process that entity, which includes all the data validation and business rules.
A separate data access object (DAO) which can generate the SQL query for any table in any database. I have a separate class for each DBMS so I can easily switch from one DBMS to another.


and unless you can find any fault with my implementation of these two principles I think you will find it extremely difficult to come up with ANY design that could possibly offer less maintenance.


This is supposed to be the right way using ‘setters’:

<?php 

$client = new Client(); 
$client->setUserID    ( $_POST['userID'   ); 
$client->setEmail     ( $_POST['email'    ); 
$client->setFirstname ( $_POST['firstname'); 
$client->setLastname  ( $_POST['lastname' ); 
$client->setAddress1  ( $_POST['address1' ); 
$client->setAddress2  ( $_POST['address2' ); 
$client->setCity      ( $_POST['city'     ); 
$client->setProvince  ( $_POST['province' ); 
$client->setCountry   ( $_POST['country'  ); 

if ($client->submit($db) !== true) 
{ 
    // do error handling 
} 
?> 

This is my way:

 <?php 
  $dbobject = new Client;
  $dbobject->insertRecord($_POST);
  $errors = $dbobject->getErrors();
 ?> 

Why should I waste effort in unpacking the $_POST array and feeding the data in one field at a time when I can provide it all in one single step? It is just as easy for code inside the class to address each field as $array[‘field’] as it is by $this->field.


One of the benefits of reusable code is that you an write it once and share it many times. One of the drawbacks of reusable code is that you can screw up that one copy and you effectively screw up every place where it is shared. You simply have to weigh up the benefits and risks of a particular implementation, make a choice, then live with the consequences. My design has the same ratio of risk/benefit as any other design, so your statement has no value (IMHO).


Someone said: You should use arguments, not variables

I disagree entirely. The $where string may contain any number of variables, but I do not see any reason why I should unpack it into its component parts, pass in each part individually, then reassemble the parts into a string which can be used as the WHERE clause in an SQL query. I pass it in as a single string and use it to send out as a single string, so all that unpacking and re packing is totally unnecessary. A competent programmer would not suggest writing code to perform something which is not necessary.

You could say that a word is a collection of letters, so using your logic would you suggest that each individual word be handled as a collection of separate letters instead of a simple string? No? I thought not.


Someone said: Your design is less reusable, because the validation code cannot be used in other places where it makes sense due to the lack of orthogonality.

Also used loosely to mean ‘irrelevant to’, e.g. ‘This may be orthogonal to the discussion, but …’, similar to ‘going off at a tangent’.


If I have 12 database tables it is obvious that I am dealing with 12 different entities with different properties, and it is a fundamental principle of OO that each different entity is required to have its own class. Each different entity also requires its own database table, therefore there is a one-to-one relationship between entity, class and table. Having a separate class for each database table most certainly does NOT lose the potential benefit of low maintenance


So may nice examples in a single page that are still (12 years latter) used in this thread by Tony.

2 Likes

It’s a fair point. Replace “Science” with “Best practices” in this article: http://grist.org/climate-skeptics/the-anatomy-of-denial-why-truth-doesnt-always-win/ and you have Tony

1 Like

Yet, another example of, sorry, complete arrogance:

I do not use separate DBMS developers because they will want to follow their own set of stupid rules for designing databases. When a database is being used by MY application then I insist on designing that database according to MY rules.

also

Glen wrote: ps: This is not written out of arrogance and I don’t think I know it better than anyone else. I’m just a student, and also was pondering about a nice oo-approach to php/mysql scripting.

Here’s my advice (for what it’s worth):
Don’t give up your day job.
If you are paying for lessons then ask for your money back.
If you are teaching yourself then you need a new teacher.
If you are reading books then they are the wrong books, or they have some pages missing.


But there is one answer question that really explains the last 400 messages of this thread and explains the “why” of the 9000 LoC class. On this own thread Tony posts the question made by André Næss :

You should try object composition instead of inheritance
There is a very common error which people do the first time they do OO, and that is to overuse inheritance. It’s not that strange really as there’s always a lot of fighting over inheritance. But object composition is a second technique that frequently applies. Where you see only inheritance, I see inheritance and composition.

Which Tony answered, and based on this thread, still defends:

You have totally misunderstood my design as there is no way that I overuse inheritance. All my concrete table classes, and I have hundreds, inherit from a single abstract class. My inheritance hierarchy is only one level deep, so how can this be described as “overuse”?
(…)
In the first place I do not have a large inheritance hierarchy as all my concrete table classes inherit from a single abstract table class, therefore I am not overusing inheritance by any stretch of the imagination. If I don’t have a problem then why do I need a solution?

In the second place I am not going to change a method which is simple and effective for one that introduces another level of complexity for absolutely no discernible benefit. This is another one of those loony OO ideas that I shall consign to the khazi.

My conclusion from your statements is that, if a entity A share some responsibilities with entity B then you put the code in the parent generic entity X, but because this is “related” to the entity itself you place it there and nowhere else. So, which means that the generic entity is responsible for anything that happens one more that one entity. In your example, the GenericTable is responsible for binding the post data, validating, fetching, paging, foreing key validation, transaction, formating, uploading files, translating, holding data, some sql composition, … thus making 232 methods available there:

function Default_Table ()
function array2string ($array)
function cascadeDelete ($where, $parent_table=null)
function cascadeNullify ($update_array, $where)
function changeConfig ($fieldarray)
function checkWorkflow ($where)
function clearEditableData ($fieldarray)
function clearScrollArray ()
function commit ()
function convertTimeZone ($fieldarray, $fieldspec)
function currentOrHistoric ($string, $start_date='start_date', $end_date='end_date')
function customButton ($fieldarray, $button, $postarray, $row=null)
function deleteMultiple ($fieldarray)
function deleteRecord ($fieldarray, $parent_table=null)
function deleteRelations ($fieldarray)
function deleteScrollItem ($index)
function deleteSelection ($selection)
function eraseRecord ($fieldarray)
function eraseRelations ($fieldarray)
function executeQuery ($query)
function fetchRow ($resource)
function fetchRowChild ($row)
function filePickerSelect ($selection)
function fileUpload ($input_name, $temp_file)
function formatData ($fieldarray, &$css_array)
function free_result ($resource)
function &getChildData ()
function getClassName ()
function getColumnNames ($where=null)
function getCount ($where=null)
function getData ($where=null)
function getData_raw ($where=null)
function getData_serial ($where=null, $rdc_limit=null, $rdc_offset=null, $unbuffered=false)
function getDBname ()
function getEnum ($fieldname)
function getErrors ()
function getExpanded ()
function getExtraData ($input, $where=null)
function getFieldArray ()
function getFieldSpec ()
function getFieldSpec_original ()
function getForeignData ($fieldarray)
function getInitialData ($where)
function getInitialDataMultiple ($where)
function getInstruction ()
function getLanguageArray ($id)
function getLanguageEntries ($rows, $parent_data, $fieldlist)
function getLanguageText ($id, $arg1=null, $arg2=null, $arg3=null, $arg4=null, $arg5=null)
function getLastIndex ()
function getLastPage ()
function getLookupData ()
function getMessages ()
function getNodeData ($expanded, $where=null)
function getNumRows ()
function getOrderBy ()
function getOrderBySeq (&$orderby=null, $orderby_seq=null)
function getPageNo ()
function &getParentData ()
function getPkeyArray ($fieldarray=null, $next_task=null)
function getPkeyNames ()
function getPkeyNamesAdjusted ()
function getScrollIndex ()
function getScrollItem (&$index)
function getScrollSize ()
function getSearch ()
function getTableName ()
function getValRep ($item, $where=null, $orderby=null)
function getWhere ($next_task)
function initialise ($where=null, &$selection=null, $search=null)
function initialiseFileDownload ($where)
function initialiseFilePicker ($where, $search=null)
function initialiseFileUpload ($where)
function insertMultiple ($fieldarray)
function insertOrUpdate ($fieldarray)
function insertRecord ($fieldarray)
function multiQuery ($query)
function popupCall (&$popupname, $where, &$script_vars, $fieldarray, &$settings)
function popupReturn ($fieldarray, $return_from, $selection, $popup_offset=null)
function post_fileUpload ($filename, $filesize)
function post_search ($search, $selection)
function reInitialise ($fieldarray, $where)
function reset ($where=null, $keep_orderby=false)
function restart ($return_from, $return_action, $return_string=null)
function rollback ()
function scriptNext ($task_id, $where=null, $selection=null, $task_array=array())
function scriptPrevious ($errors=null, $messages=NULL, $action=NULL, $instruction=NULL)
function selectDB ($dbname)
function setAction ($action)
function setChildData ($data)
function setChildObject (&$childOBJ)
function setCurrentOrHistoric ()
function setDefaultOrderBy ($sql_orderby='')
function setFieldAccess ()
function setFieldArray ($fieldarray, $reset_pageno=true)
function setInstruction ($instruction)
function setLookupData ($input=null)
function setOrderBy ($sql_orderby, $sql_orderby_seq=null, $toggle=true)
function setOrderBySeq ($sql_orderby_seq)
function setPageNo ($pageno=null)
function setParentData ($data)
function setParentObject (&$parentOBJ)
function setRowsPerPage ($rows_per_page)
function setScrollArray ($where)
function setScrollIndex ($index='1')
function setSelectedRows ($select_string, $rows)
function setSqlSearch ($sql_search=null, $save=false)
function setSqlGroupBy ($sql_groupby=null)
function setSqlWhere ($sql_where)
function sqlSelectDefault ()
function sqlSelectInit ()
function startTransaction ()
function unFormatData ($fieldarray)
function unFormatNumber ($fieldarray)
function updateFieldArray ($fieldarray, $postarray)
function updateLinkData ($fieldarray, $postarray)
function updateMultiple ($fieldarray, $postarray=array())
function updateRecord ($fieldarray)
function updateSelection ($selection, $replace)
function validateDelete ($fieldarray, $parent_table=null)
function validateSearch ($fieldarray)
function validateUpdate ($fieldarray)
function _cm_changeConfig ($where, $fieldarray)
function _cm_commonValidation ($fieldarray, $originaldata)
function _cm_customButton ($fieldarray, $button)
function _cm_deleteSelection ($selection)
function _cm_filePickerSelect ($selection)
function _cm_fileUpload ($input_name, $temp_file, $wherearray)
function _cm_filterWhere ($array=null)
function _cm_formatData ($fieldarray, &$css_array)
function _cm_getColumnNames ($fieldarray)
function _cm_getDatabaseLock ()
function _cm_getExtraData ($where, $fieldarray)
function _cm_getForeignData ($fieldarray)
function _cm_getInitialData ($fieldarray)
function _cm_getInitialDataMultiple ($fieldarray)
function _cm_getNodeData ($expanded, $where, $where_array = null)
function _cm_getNodeKeys ($keys)
function _cm_getOrderBy ($orderby)
function _cm_getPkeyNames ($pkey_array, $task_id, $pattern_id)
function _cm_getValRep ($item=null, $where=null, $orderby=null)
function _cm_getWhere ($where, $task_id, $pattern_id)
function _cm_initialise ($where, &$selection, $search)
function _cm_initialiseFileDownload ($fieldarray)
function _cm_initialiseFilePicker ($fieldarray, $search)
function _cm_initialiseFileUpload ($fieldarray)
function _cm_ListView_header ($fieldarray)
function _cm_ListView_print_before ($prev_row, $curr_row)
function _cm_ListView_print_after ($curr_row, $next_row)
function _cm_ListView_total ()
function _cm_output_multi ($name, $fieldarray)
function _cm_popupCall (&$popupname, $where, $fieldarray, &$settings)
function _cm_popupReturn ($fieldarray, $return_from, &$select_array)
function _cm_post_deleteMultiple ($rows)
function _cm_post_deleteRecord ($fieldarray)
function _cm_post_eraseRecord ($fieldarray)
function _cm_post_fetchRow ($fieldarray)
function _cm_post_fileUpload ($filename, $filesize)
function _cm_post_getData ($rows, &$where)
function _cm_post_insertMultiple ($rows)
function _cm_post_insertOrUpdate ($fieldarray, $insert_count, $update_count)
function _cm_post_insertRecord ($fieldarray)
function _cm_post_lastRow ()
function _cm_post_output ($string, $filename)
function _cm_post_popupReturn ($fieldarray, $return_from, $select_array)
function _cm_post_search ($search, $selection)
function _cm_post_updateLinkdata ($rows, $postarray)
function _cm_post_updateMultiple ($rows)
function _cm_post_updateRecord ($fieldarray, $old_data)
function _cm_post_updateSelection ($selection, $replace)
function _cm_pre_cascadeDelete ($fieldarray)
function _cm_pre_deleteMultiple ($rows)
function _cm_pre_deleteRecord ($fieldarray)
function _cm_pre_eraseRecord ($fieldarray)
function _cm_pre_getData ($where, $where_array, $fieldarray=null)
function _cm_pre_insertMultiple ($rows)
function _cm_pre_insertOrUpdate ($rows)
function _cm_pre_insertRecord ($fieldarray)
function _cm_pre_output ($filename)
function _cm_pre_updateLinkdata ($rows, &$postarray)
function _cm_pre_updateMultiple ($rows)
function _cm_pre_updateRecord ($fieldarray)
function _cm_pre_updateSelection ($selection, $replace)
function _cm_reset ($where)
function _cm_restart ($pattern_id, $zone, $return_from, $return_action, $return_string)
function _cm_setJavaScript ($javascript)
function _cm_setScrollArray ($where, $where_array)
function _cm_unFormatData ($fieldarray)
function _cm_updateFieldarray ($fieldarray, $postarray, $rownum)
function _cm_updateSelection ($selection, $replace)
function _cm_validateDelete ($rowdata, $parent_table)
function _cm_validateInsert ($rowdata)
function _cm_validateSearch ($fieldarray)
function _cm_validateUpdate ($fieldarray, $originaldata)
function _ddl_getColumnSpecs ()
function _ddl_showColumns($dbname, $tablename)
function _ddl_showCreateTable ($dbname, $tablename)
function _ddl_showDatabases ($dbprefix)
function _ddl_showTables ($dbname)
function _ddl_showTableKeys ($dbname, $tablename)
function _dml_deleteRecord ($fieldarray)
function _dml_adjustWhere ($string_in)
function _dml_deleteSelection ($selection, $from=null, $using=null, $limit=0)
function _dml_free_result ($resource)
function _dml_getCount ($where)
function _dml_getData ($where, $raw=false)
function _dml_getData_serial ($where=null, $rdc_limit=null, $rdc_offset=null, $unbuffered_query=false)
function _dml_getEnum ($item)
function _dml_insertRecord ($fieldarray)
function _dml_multiQuery ($query)
function _dml_ReadBeforeUpdate ($where, $reuse_previous_select=false)
function _dml_updateRecord ($fieldarray, $oldarray, $where=null)
function _dml_updateSelection ($selection, $replace, $limit=0)
function _examineWorkflow ($input)
function _examineWorkflowInstance ($where)
function _extract_custom_fields ($rowdata)
function _getCustomProcessingObject ()
function &_getDBMSengine ($dbname=null, $unbuffered_query=false)
function _getInitialValues ($task_id=null)
function _getInitialWhere ($where)
function _processInstruction ($fieldarray)
function _qualify_dbname ($target_db, $this_db)
function _sqlAssembleWhere ($where, $where_array)
function _sqlAssembleWhereLink ($where, $where_array)
function _sqlForeignJoin (&$select, $from, $parent_relations)
function _sqlProcessJoin (&$select, $from, $reldata, &$new_relations)
function _sqlSelectAlternateLanguage ($sql_select, $reldata, $subquery=false)
function _switch_database ($tablename, $dbname)
function _validateInsert ($fieldarray)
function _validateUpdate ($fieldarray)
function __call ($method, $arguments)
function __sleep ()
function __wakeup ()

Just even looking at the “public” methods (without the underscore) there are 121 methods.
A few examples of the current problems, in my vision of a framework:

  • commit, rollback - What if I want to commit/rollback a transaction involving more that one entity ? In which entity should I use this? A random one, all? And if all, what if one fails?
  • post_fileUpload - Why should I care of this in the “database” layer? And if I don’t support file upload on a “Car” entity?
  • getLanguageArray - I don’t care about multilanguage, and isn’t this a front-end responsibility?
  • customButton - Again isn’t this front-end responsibility? Does it make sense use it in batch program? If not, what it is doing here?

Also, another quote from that article:

As far as I am concerned there is a one-to-one relationship between ‘entity’, ‘class’ and ‘database table’, so if ‘entity=class’ is correct and ‘entity=table’ is correct, how can ‘class=table’ be incorrect?

This is all plain wrong, any system/component can abstract another. So I can have an entity on my code (Person) that abstracts more that one table in the ER (Login + PersonDetails + AddressInfo + what ever I want) for many reasons: performance, obscure complexity, … I can have a many-to-many-to-many-to-many relationships. This is so true that if I had a one-to-one-to-one than I wouldn’t need an interface (different visions of my entities) just have a database system and let my users write it directly.