请务必诚实,如果必须,请将我的工作分开。
所以我正在重写我最近制作的一个小型网络应用程序。原因很简单,代码变得非常混乱,我想学习并应用更好的OO设计。这个应用程序应该做的只是简单的CRUD。
我有一个包含3个表的数据库companies
和partners
,它们彼此无关,city
与公司和合作伙伴有1:n的关系。很简单,真的。现在,我有几个问题,我将在帖子的最后说明。在这里,我将尝试解释:
我的第一个方法是创建类公司,合作伙伴和城市,从数据库中获取所有数据集并从中创建对象:
class company {
private $id = null;
private $name = null;
private $city = null;
//many more attributes
function __construct( $id, $name, $city, [...] ) {
$this->id = $id;
$this->name = $name;
$this->city = $city;
//huge constructor
}
/*
* getters + setters here
*
* no need to paste the partner class as it looks just like this one
*
*/
}
这就是所有这些课程。我从数据库中获取了每个数据集并构建了公司,合作伙伴和城市对象(这些类中的属性城市本身就是一个具有多个属性的对象),并将它们保存为两个数组arr_companies
和arr_partners
,然后拿着这些东西......它就像那样工作得很好。
现在,我想要的是更新,插入,删除数据库,并且所有3个类(城市,公司,合作伙伴)都需要此功能。我的方法是我创建了一个带有构造函数的新类,该构造函数基本上采用2个字符串命令和对象,例如('update', 'company')
然后它将直接在数据库中更新公司,使我的对象保持不变。这让我很伤心,因为我有很好的构造对象,我不知道如何利用它们。
问题:
拥有如此庞大的构造者(我最大的构建者)是不是很糟糕 28个参数)?
您是否应该为数据库设置单独的类 操作或者更好的是有一个抽象类或者 它的接口,让子类本身处理更新,删除,插入?
只要编写,从数据库中删除,只要将这些更改应用到我的对象并且稍后只执行命令到数据库,例如会话结束时,是否常见?
< / LI>我认为像这样的应用程序必须在幻想时代完成。这里适当的方法是什么?创建对象,使用对象,将它们保存到数据库?
我有很多问题,但我想很多问题我只是不知道该怎么问。
请注意,如果可能的话,我现在不想使用ORM。
非常感谢你的时间。
答案 0 :(得分:6)
OP提出的问题:
“拥有如此庞大的构造函数(我最大的构造函数需要28个参数)是不是很糟糕”?
“你是否应该有一个单独的数据库操作类,或者最好是为它创建一个抽象类或接口,让子类本身处理更新,删除,插入?”
一般来说,在创建课程时,您希望尝试识别最能代表您的业务需求的名词。在您的具体情况下,您可能会有三个班级;公司,合作伙伴和城市。
现在在每个类(名词)中,你的方法将采用动词的形式,因此在语法上你的调用代码是有意义的:if ($company->getName() === 'forbes')
正如您所提到的,每个类都需要一个数据库对象(dbo)才能使用,因此您可以实现任意数量的模式来公开与类的数据连接;单身,工厂单身或依赖注入等
抽象(父)类非常适合跨子类共享通用算法,并且应该在您处于设计的伪代码阶段时进行标识。父类还允许您通过在父级中声明抽象方法来强制子类具有方法。
在某些情况下,接口是一个有用的工具,但我发现它们不如在父类中声明抽象方法那么灵活。但是在类不共享父母的情况下,这种情况很好。
“只要编写,从数据库中删除,只要或者我应该将这些更改应用于我的对象并且稍后只执行命令到数据库,例如当会话结束时”?
CRUD活动应在执行操作时发生。如果您等待会话结束,则可能会遇到由于用户关闭浏览器而导致会话过早结束的情况。为了更好地保护您的数据,您可以将CRUD活动包装在事务中。
如果您正在运行高流量应用程序,则可以实施排队系统并将要完成的工作排队。
“我认为像这样的应用程序之前必须完成幻想。这里的正确方法是什么?创建对象,使用对象,将它们保存到数据库中”?
你是对的,这是以前做过的,通常被称为ORM(对象关系映射器)。基本上,ORM会自省数据库模式,并创建表示模式的对象(和关系)。因此,您不使用本机SQL,而是使用对象。虽然您可以使用SQL来满足自定义业务需求,但是对于Doctrine,您可以使用Doctrine查询语言(DQL)与本机SQL。
我强烈推荐的ORM是Doctrine。
如果您不想使用ORM,可以将CRUD方法添加到主类。我选择了一个接口,因此您的类不必从包含数据库操作的父级扩展。另外,请查看this post使用单例/工厂公开类数据库对象。
请考虑以下事项:
// Company.php
class Company implements iDatabaseOperation
public function delete()
{
// Lets use a DBO singleton/factory for DB access
// Uses PDO, which is strongly recommended
$dbo = Database::factory(Database::DATABASE_NAME);
$dbo->beginTransaction();
try {
$sql =
"DELETE FROM " .
" company " .
"WHERE " .
" id = :companyId " .
"LIMIT 1";
$stmt = $dbo->prepare($sql);
$stmt->bindValue(':companyId', $this->getId());
$stmt->execute();
$dbo->commit();
} catch (Exception $e) {
$dbo->rollback();
error_log($e->getMessage();
$e = null; // Php's garbage collection sucks
}
}
}
// iDatabaseOperation.php
interface iDatabaseOperation
{
public function delete();
public function update();
public function insert();
}
答案 1 :(得分:1)
您实际上是在编写自己的ORM。所以,我不打算只换成已经为你写过的那个。滚动自己的优势在于,您可以在写作时了解它的工作原理。但缺点是其他人可能已经做得更好了。但假设你想继续......
一般建议:记住要始终将问题分解为更简单,更简单的部分。每个类只应执行一个简单的功能。此外,您不必担心缓存更新...除非您的数据库可能位于调制解调器远程连接的另一端。
具体建议如下:
我会将您的实体实例类设置为容纳数据而不是进行大量数据加载。使用其他类和逻辑来加载数据。我将仅使用实体类的构造函数来填充与该类相关的数据(以及它的子项)。
一件简单的事情是在实体类上使用静态方法来加载和保存数据。 E.g。
class city {
private $id = null;
private $name = null;
function __construct( $id, $name ) {
$this->id = $id;
$this->name = $name;
}
// getters and setters
...
// ---------------------
// static functions
// ---------------------
public static function loadById($cityId) {
// pull up the city by id
$retval = new city(row["id"], row["name"]);
// close db connection
return $retval;
}
public static function loadByCustomerId($customerId) {
// pull up multiple cities by customer id
// loop through each row and make a new city object
// return a hash or array of cities
}
public static function update($city) {
// generate your update statement with $city->values
}
// other methods for inserting and deleting cities
...
}
所以现在获取和更新城市的代码看起来像这样:
// loading city data
$city = city::loadById(1); // returns a city instance
$cities = city::loadByCustomerId(1); // returns an array of city instances
// updating city data
$city->name = "Chicago"; // was "chicago"
city::update($city); // saves the change we made to $city
静态方法不是实现此方法的最佳方法,但它可以指向正确的方向。 repository pattern会更好,但这超出了这个答案的范围。我发现在我遇到更简单的解决方案问题之前,我常常看不到像存储库模式这样的更复杂的解决方案的优点。
答案 2 :(得分:1)
class CompanyBuilder { private final Company c; public CompanyBuilder() { c = new Company();
} CompanyBuilder addId(String id){c.id = id;} // id should be package visible and class should be located in the same package with builder CompanyBuilder addName(String name){...} CompanyBuilder addCity(String city){...} Company build(){ return c;} }
class Company { ... Company addId(String id){ this.id = id; return this;
} Company addName(String name){...} ... } Usage: Company c = new Company().addId("1").addName("Name1");
最后,我建议阅读“清洁代码:敏捷软件工艺手册”R.Martin。
答案 3 :(得分:0)
你在做什么看起来很棒。您可以添加的是一个中间层,它将您的业务对象映射到您的数据库(对象关系映射)。那里有很多对象关系映射api。查看this维基百科列表,了解可用于PHP的文件
答案 4 :(得分:0)
我认为一个包含28个参数的构造函数太多了,你应该管理一些属性的其他类有一些共同点。您应该告诉我们您实例化的其他属性,它可以帮助您找到制作更常见对象的方法。
我认为您还应该创建一个管理操作和数据库的类,如带有删除,更新等的DBHandler。 在我看来,在调用函数后直接对数据库中的元组进行修改非常重要。
为什么呢?因为它可以避免冲突,就像你尝试更新一个应该被删除的对象的情况,例如,如果你最后对你的数据库进行了修改。
答案 5 :(得分:0)
您可能需要查看ruby on rails。
您不一定要切换到它,但要看看它们如何实现MVC模式并实现CRUD。