dennis-fedco Posted June 22, 2015 Share Posted June 22, 2015 (edited) I was doing various separation of concerns refactoring but I think I went too far. I think I have too many layers and want to do a reality check. Originally there was some code in the Product class that called native functionality directly. Now it's just too many layers, I think. So what I have now is: "real" business-layer code (calls) --> (i.e.) Motor class (which calls) --> DAO layer (that calls) --> Data layer (that calls) --> a global function that calls native MySql function Is there a healthier way to restructure this complexity without losing benefits of SOC and OO? class Product { ... { // business logic needs motors $this->product->motor->loadMotors(); } } //class to handle motor functions class Motor { public function loadMotors() { return $this->motorDao->loadMotors(); } } //layer to handle connection between "object" and "database result" class LoadMotorDao { public function loadMotors() { $motorDbResultObject = $this->motorData->giveMeMotors(); //some data-to-object handling code return $motorObject; } } //class that purely returns just data from DB as-is //typically a DB result set class LoadMotorData { public function giveMeMotors() { return db_query("SELECT ... "); } } //global function that calls native functionality function db_query($query, $database = "", $conn = "") { return ... mysql_db_query($database, $query, $conn); } Edited June 22, 2015 by dennis-fedco Quote Link to comment Share on other sites More sharing options...
Solution ignace Posted June 23, 2015 Solution Share Posted June 23, 2015 Yes, you went too far. Also your business classes should not be aware of DB classes. If your Product needs a Motor then make sure your DAO injects it. class ProductMapper { private $SQL_LOAD_PRODUCT_WITH_MOTOR = 'SELECT .. FROM .. JOIN .. WHERE ..'; public function someBusinessIdentifiableMethod() { $rows = db_query($this->SQL_LOAD_PRODUCT_WITH_MOTOR, [..]); foreach ($rows as $row) { $products[] = $product = new Product(); //.. $product->setMotor($motor = new Motor()); } return $products; } }Your Mapper class would query your database and then "map" the result to objects. Much like the ResultSetMapping from Doctrine. Quote Link to comment Share on other sites More sharing options...
dennis-fedco Posted June 24, 2015 Author Share Posted June 24, 2015 (edited) Thanks I understand that I went too far, but after that I am not sure I understand the points completely. Product and Motor are my business classes. The rest is various DB-functionality and computation and display. Are you saying that my bussiness class depends on DB? Where exactly is the dependency, because I don't think I am aware of it, or I don't see it. The way Product and Motor interact are not as clear cut as I've made it appear to be and their purpose currently is more messy. So I want to focus on overall theme and not on any specific usage of the clasess. But, for example, I load motors and products separately and then run varoius simulations. There is no direct motor-product connection. More like there are "motors" and "product" and then they interact. Can you also show an example of how to use the ProductMapper? Is it just //using the mapper $products = (new ProductMapper())->someBusinessIdentifiableMethod(); //calls DB-functions directly I do not see how it is different from this (what I have now) : //what I currently have - invoking DAO->Data->Db->MySQL $motors = $this->product->motor->loadMotors(); //calls a DAO class, which calls DB functions direclty They both provide me with the same stuff - product or motor objects that were loaded from DB in some way or another... my way takes a lot more calls (various object invocations) though. Edited June 24, 2015 by dennis-fedco Quote Link to comment Share on other sites More sharing options...
dennis-fedco Posted June 24, 2015 Author Share Posted June 24, 2015 (edited) I supose another question too is : Why use a Mapper pattern opposed to any other DB pattern available? And also I do plan to eventually (if time/resources allow) to migrate to Doctrine or similar layer Edited June 24, 2015 by dennis-fedco Quote Link to comment Share on other sites More sharing options...
ignace Posted June 24, 2015 Share Posted June 24, 2015 The difference is that in my case the Mapper has context so related objects are also loaded. While your chained method calls need to be called every time you need more data, resulting in more queries. Also because your Entity uses the DAO directly it is actually tied to your DB which has the dependency the wrong way like an object created by a Factory object would be tied to its creator. These should be decoupled. As to why the Mapper I link to this article: http://stackoverflow.com/a/814479 Quote Link to comment Share on other sites More sharing options...
dennis-fedco Posted June 25, 2015 Author Share Posted June 25, 2015 thanks I think I understand now and don't need any further help so far but I am curious -- what is it that's undesirable towards dependency in the current version of code that I have? namely, if I wish to change the underlying database, I do not really have to touch my object. so there is some decomposition there that works. I may not even have to change the MotorDAO or the MotorData class, and change just the db_query() function when I switch native functionalityIf structure of SQL changes, then I have to change MotorData. But other than that, my entitity remains intact. Or is it? What is bad about dependency in the code? Quote Link to comment Share on other sites More sharing options...
ignace Posted June 26, 2015 Share Posted June 26, 2015 (edited) The dependency is "wrong" because your entities depend on a higher layer. In other words the DAO layer should be dependent upon the entities (Motor, Product) not the other way round. Doctrine solves this problem by using Proxy Entity objects. More on that here: http://stackoverflow.com/a/17787070 This is kinda the same as what you did with the exception that this dependency is hidden and inexistent when the full object is loaded. Which is also what the Mapper pattern allows you to do. For example when a User logs in, it may not be required to load the groups he is a member of. So in this case this becomes either null (resulting in an Exception or Fatal Error) or a ProxyCollection (in case of someone does access it, of course for performance reasons it is best to not be used). However when you want to view the groups he is a member of it is required to load the Groups. Since you access business methods on the Mapper object. You know exactly what you should and should not load (or lazy-load). Edited June 26, 2015 by ignace Quote Link to comment Share on other sites More sharing options...
dennis-fedco Posted July 1, 2015 Author Share Posted July 1, 2015 I have been playing with this a lot recently. And I thought -- Why do I have to populate Product with a Motor? I do not have to! I guess if my business application needed a Product with a Motor, as a unit, then yes, that is one way to do it.But if my application matches various combinations of Products and Motors, then I can have them be loaded separately via DataMappers. Basically whatever suits my needs. This begs for a more general question -- If I say have an object that is comprised of smaller objects, when is it appropriate to load them all up and when is it appropriate to just load them on as-needed basis. //do I end up with this class BigProductMapper { public function loadEverythingUp() { //load all subproducts into BigProduct } } //or with this class BigProductMapper { public function loadJustTheBigProduct() {} } class SubProductMapper1 { public function loadSubProduct1() {} } class SubProductMapper2 { public function loadSubProduct2() {} } ... class SubProductMapperN { public function loadSubProductN() {} } //have production code load things up as part of bigger class as-needed Quote Link to comment Share on other sites More sharing options...
ignace Posted July 3, 2015 Share Posted July 3, 2015 Like I already said you know when to load up dependency X and Y when you call the method on the mapper. Loading extra during presentation will lead to problems. Doctrine provides this functionality just to not break your code. And it is also easy to trace back when reviewing your query log. It'll show up something like: SELECT .. FROM table WHERE id = 1 SELECT .. FROM table WHERE id = 2 SELECT .. FROM table WHERE id = 3 .. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.