Len*_*eng 14 php database oop model-view-controller codeigniter
我真的在反复出现OOP /数据库概念.
请允许我解释伪PHP代码的问题.
假设您有一个"用户"类,它users在其构造函数中从表中加载其数据:
class User {
public $name;
public $height;
public function __construct($user_id) {
$result = Query the database where the `users` table has `user_id` of $user_id
$this->name= $result['name'];
$this->height = $result['height'];
}
}
Run Code Online (Sandbox Code Playgroud)
简单,棒极了.
现在,我们有一个"group"类,它从与groups表连接的groups_users表中加载其数据,并user从返回的user_ids 创建对象:
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['user_ids'] as $user_id) {
// Make the user objects
$users[] = new User($user_id);
}
}
}
Run Code Online (Sandbox Code Playgroud)
一个组可以拥有任意数量的用户.
美丽,优雅,令人惊叹......在纸上.但实际上,制作一个新的群组对象......
$group = new Group(21); // Get the 21st group, which happens to have 4 users
Run Code Online (Sandbox Code Playgroud)
...执行5个查询而不是1.(1个用于组,1个用于每个用户.)更糟糕的是,如果我创建一个community类,其中包含许多组,其中每个组中都有许多用户,则查询数量不合适跑了!
解决方案,不适合我
多年来,我身边有这样的方式,是不代码以上述方式,而是制作时group,例如,我会加入groups表的groups_users表的users表,以及和用户创建的数组对象中的类对象数组group(从不使用/触及user类):
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
**and also joining the `users` table,**
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['users'] as $user) {
// Make user arrays
$users[] = array_of_user_data_crafted_from_the_query_result;
}
}
}
Run Code Online (Sandbox Code Playgroud)
...但是,当然,如果我创建一个"社区"类,在它的构造函数中,我需要将communities表与communities_groups表一起加入groups表,groups_users表中包含users表.
...如果我创建一个"city"类,在它的构造函数中,我需要将cities表与cities_communities表一起加入communities表,communities_groups表中包含groups表与groups_users表的users表.
真是一场彻头彻尾的灾难!
我是否必须在具有一百万个查询VS的美丽OOP代码之间进行选择.1为每个超集手动查询和编写这些连接?有没有自动化系统的系统?
我正在使用CodeIgniter,并查看无数其他MVC,以及在其中构建的项目,并且找不到任何人使用模型的一个好例子,而不采用我概述的两种有缺陷的方法之一.
看来以前从未做过这件事.
我的一位同事正在编写一个完全符合这一要求的框架 - 您创建了一个包含数据模型的类.另一方面,较高的模型可以包括该单一模型,它工艺和自动化的表连接,以创建更高的模型,其中包括的对象实例下的模型,都在一个单一的查询.他声称他以前从未见过这样做的框架或系统.
请注意: 我确实总是使用单独的类来进行逻辑和持久化.(VO和DAO - 这是MVC的全部内容).为了简单起见,我只是在这个思想实验中将两者结合在一个类似MVC的架构之外.请放心,无论逻辑和持久性的分离如何,这个问题都会持续存在.我相信詹姆斯在这个问题下面的评论中向我介绍的这篇文章似乎表明我提出的解决方案(我多年来一直关注的)实际上是开发人员目前为解决这个问题所做的工作.然而,这个问题试图找到自动化这种精确解决方案的方法,因此并不总是需要为每个超集手动编码.从我所看到的情况来看,这在以前从未在PHP中完成,我的同事的框架将是第一个这样做的,除非有人可以指向我这样做.
而且,当然我从不在构造函数中加载数据,而且我只调用我在实际需要数据时创建的load()方法.然而,这是无关的这个问题,因为在这个思想实验(以及在我需要自动执行此现实生活中的情况下),我总是需要急于加载的数据孩子的所有子集就下了线为它会去,而不是根据需要在未来的某个时间点加载它们.思想实验很简洁 - 它不遵循最佳实践是一个没有实际意义的点,试图解决其布局的答案同样忽略了这一点.
编辑:为清楚起见,这是一个数据库模式.
CREATE TABLE `groups` (
`group_id` int(11) NOT NULL, <-- Auto increment
`make` varchar(20) NOT NULL,
`model` varchar(20) NOT NULL
)
CREATE TABLE `groups_users` ( <-- Relational table (many users to one group)
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
)
CREATE TABLE `users` (
`user_id` int(11) NOT NULL, <-- Auto increment
`name` varchar(20) NOT NULL,
`height` int(11) NOT NULL,
)
Run Code Online (Sandbox Code Playgroud)
(还要注意我最初使用的是wheels和cars 的概念,但那是愚蠢的,这个例子更加清晰.)
解:
我最终找到了一个完全符合这个要求的PHP ORM.这是Laravel's Eloquent.您可以指定模型之间的关系,并使用以下语法智能地为急切加载构建优化查询:
Group::with('users')->get();
Run Code Online (Sandbox Code Playgroud)
这是一个绝对的救命.我没有必要写一个查询.它也不能使用连接,它可以根据外键智能地编译和选择.
假设您有一个"wheel"类,它在其构造函数中从wheels表加载其数据
构造函数不应该做任何工作.相反,它们应该只包含作业.否则,您很难测试实例的行为.
现在,我们有一个"car"类,它从与cars_wheels表连接的cars表中加载数据,并从返回的wheel_ids创建wheel对象:
不,这有两个问题.
您的Car类不应包含用于实现"汽车逻辑"和"持久性逻辑"的代码.否则你就破坏了SRP.轮子是类的依赖,这意味着轮子应该作为构造函数的参数注入(最有可能 - 作为轮子的集合,或者可能是数组).
相反,你应该有一个mapper类,它可以从数据库中检索数据并将其存储在WheelCollection实例中.还有一个汽车映射器,它将Car实例存储数据.
$car = new Car;
$car->setId( 42 );
$mapper = new CarMapper( $pdo );
if ( $mapper->fetch($car) ) //if there was a car in DB
{
$wheels = new WheelCollection;
$otherMapper = new WheelMapper( $pdo );
$car->addWheels( $wheels );
$wheels->setType($car->getWheelType());
// I am not a mechanic. There is probably some name for describing
// wheels that a car can use
$otherMapper->fetch( $wheels );
}
Run Code Online (Sandbox Code Playgroud)
像这样的东西.在这种情况下,映射器负责执行查询.并且您可以为它们提供多个源,例如:有一个映射器检查缓存,如果失败,则从SQL中提取数据.
我真的必须在美丽的OOP代码与百万查询VS之间做出选择.1查询和恶心,非OOP代码?
不,丑陋来自于这样一个事实:活动记录模式仅适用于最简单的用例(其中几乎没有逻辑关联,带有持久性的美化值对象).对于任何非平凡的情况,最好应用数据映射器模式.
..如果我创建一个"城市"类,在其构造函数中,我需要将cities_dealerships表与citiessdealerships表一起使用,经销商表与dealerships_cars表一起加入汽车表,其中cars_wheels表与wheel表一起.
因为你需要关于"莫斯科每个经销商的可用Car护理"的数据并不意味着你需要创建实例,你肯定不会关心那里的轮子.站点的不同部分将具有不同的操作规模.
另一件事是你应该停止将类视为表抽象.没有规则说"你必须在类和表之间有1:1的关系".
再举一个Car例子.如果你看一下它,分开Wheel(甚至WheelSet)分类只是愚蠢的.相反,你应该只有一个Car已经包含它的所有部分的类.
$car = new Car;
$car->setId( 616 );
$mapper = new CarMapper( $cache );
$mapper->fetch( $car );
Run Code Online (Sandbox Code Playgroud)
映射器不仅可以从"Cars"表中轻松获取数据,还可以从"Wheel"和"Engines"以及其他表中轻松获取数据并填充$car对象.
my 2 cents
Rails 中的 ActiveRecord 实现了延迟加载的概念,即推迟数据库查询,直到您真正需要数据为止。因此,如果您实例化一个my_car = Car.find(12)对象,它只会查询 cars 表中的这一行。如果稍后您需要my_car.wheels,它会查询轮子表。
我对上面的伪代码的建议是不要加载构造函数中的每个关联对象。汽车构造函数应该只查询汽车,并且应该有一个方法来查询它的所有车轮,还有另一个方法来查询它的经销店,该方法只查询经销店并推迟收集所有其他经销店的汽车,直到你具体说了什么喜欢my_car.dealership.cars
后记
ORM 是数据库抽象层,因此必须对它们进行调整以方便查询,而不是进行微调。它们允许您快速构建查询。如果稍后您决定需要微调查询,那么您可以切换为发出原始 sql 命令或尝试以其他方式优化要获取的对象数量。当您开始进行性能调优时,这是 Rails 中的标准做法 - 查找使用原始 sql 发出时效率更高的查询,并查找在需要对象之前避免急切加载(与延迟加载相反)的方法。