【问题标题】:Create database model classes dynamically动态创建数据库模型类
【发布时间】:2011-10-18 13:50:25
【问题描述】:

我正在尝试改进在我构建的轻型框架中用于数据库事务的方法。

理解问题的信息:

这是我编写的一个类(connect.php 加载数据库凭据;PHP PDO 的包装器,存储在$db;和Base.php):

<?php
    require_once('connect.php');

    class Advertiser extends Base
    {
        public static function getByID($id)
        {
            global $db;
            $sql = "SELECT * FROM advertiser WHERE advertiserid=?";
            $values = array($id);
            $res = $db->qwv($sql, $values);

            return Advertiser::wrap($res);
        }

        public static function add($name)
        {
            $adv = new Advertiser(null, $name);
            $res = $adv->save();

            return $res;
        }

        public static function wrap($advs)
        {
            $advList = array();
            foreach( $advs as $adv )
            {
                array_push($advList, new Advertiser($adv['advertiserid'], $adv['name']));
            }

            return Advertiser::sendback($advList);
        }

        private $advertiserid;
        private $name;

        public function __construct($advertiserid, $name)
        {
            $this->advertiserid = $advertiserid;
            $this->name = $name;
        }

        public function __get($var)
        {
            return $this->$var;         
        }

        public function save()
        {
            global $db;
            if( !isset($this->advertiserid) )
            {
                $sql = "INSERT INTO advertisers (name) VALUES(?)";
                $values = array($this->name);
                $db->qwv($sql, $values);

                if( $db->stat() )
                {
                    $this->advertiserid = $db->last();
                    return $this;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                $sql = "UPDATE advertisers SET name=? WHERE advertiserid=?";
                $values = array ($this->name, $this->advertiserid);
                $db->qwv($sql, $values);

                return $db->stat();
            }
        }
    }
?>

如您所见,它具有相当标准的CRUD 函数(编辑:好的,在这个实现中只有 CRU)。有时,我会通过添加更多函数来扩展这样的类,这正是这些类的用途。例如,我可能会在这个类中添加以下函数(假设我在数据库中添加了一个列isBanned):

public static function getBanned()
{
    global $db;
    $sql = "SELECT * FROM advertiser WHERE isBanned=1";
    $res = $db->q($sql);

    return Advertiser::wrap($res);
}

问题:

我如何创建一个包罗万象的类,该类在存在和必要时还能加载自定义模型类?

例如,如果我写如下代码:

$model = new Catchall();
$banned = $model->Advertiser::getByID(4);

我希望我的包罗万象的类修改它的查询,以便所有对表/列的引用都是我选择的任何名称(Advertiser,在这种情况下),但要小写。

另外,如果我想创建一个像上面写的那样的自定义函数,我希望我的包罗万象的类能够确定一个文件存在于它的路径中(当然是以前定义的),其名称是我已经定义的指定(Advertisers.php,在这种情况下)并加载它。

Advertisers.php 将是 extends Catchall 并且只包含我的自定义函数。

通过这种方式,我可以拥有一个适用于所有 CRUD 函数的 Catchall 类,并且能够根据需要轻松扩展任意类。

  1. 我需要了解哪些想法/概念才能做到这一点?
  2. 我在哪里可以找到这样的示例,无需深入研究大量 CodeIgniter 或 Zend 源代码
  3. 我要做什么

【问题讨论】:

  • 请,停止使用全局变量
  • @teresko,请解释一种更好的方法来获得对在定义的类之外实例化的数据库对象的访问权限。我愿意接受建议,如果对您有帮助,我会很乐意向您展示我的代码的完整结构。我已经为此工作了很长时间,并且使用单个全局变量是合适的解决方案。请使用有效来源支持您的需求。我使用了一个全局变量,因为我需要数据库连接一次,并且在每个函数中创建一个新连接至少是 FOOLISH
  • 我使用静态类来封装数据库访问方法 - 比具有相似范围的全局更好
  • @rockerest ,您应该将相同的数据库连接对象实例传递给每个模型:pastie.org/2310815
  • @rockest:因为它无助于代码的可测试性和可读性。使用您的 API 的人不会通过查看方法头看到他需要建立数据库连接。相反,他们必须遇到异常或遍历您的代码。看看来自 Google Tech Talk 的“清洁代码”系列。 youtube.com/watch?v=-FRm3VPhseI

标签: php database oop database-design


【解决方案1】:

一般资料:我会查看 Doctrine2 以了解他们如何在 PHP 中创建 ORM 的示例。他们使用标记语言中的映射来表示:这个表有这些类型的列。此外,虽然不是在 PHP 中,但Django ORM 非常易于使用和理解,并且学习该教程 20 分钟左右将真正让您大开眼界,发现一些巧妙的可能性。 (对我有用)

快速搜索“php 活动记录轻量级”会返回几个有趣的示例,这些示例可能会让您走上正确的道路。

PHP 想法:我会研究 php 中的神奇 getter 和 setter,__GET 和 __SET,它们可以让您在对象上设置值,而无需为每个字段的每个字段创建 getter/setter桌子。您可以制作一个 __SET 来确保 set 字段是该表中的一个字段,并在下次保存该对象时将其添加到“要更新的字段”列表中。但是,从长远来看,这并不是一个好主意,因为它很快就会失控,而且很脆弱。

建议:最后,我在一家公司工作,该公司使用的系统看起来几乎完全一样,我可以毫不含糊地说,你不想尝试长期扩展。像这样的系统(活动记录模式)可以预先节省大量时间,因为不必编写查询和东西,但如果您想开始单元测试业务逻辑,则可能会花费大量时间 对象类。

例如,无法模拟/依赖注入静态 GetById 方法(它基本上是一个全局方法),因此每次在代码中调用时,代码都会转到真实数据库并返回一个真实对象.不需要太多这样的编码就可以制作一个几乎不可能测试、咆哮并与数据库紧密耦合的系统。

虽然它们的执行速度可能比您上面的代码慢一点,但如果您打算在相当长的时间内使用它,请尝试研究 ORM 工具。

编辑它被称为 Active Record。

【讨论】:

  • 我不清楚这个问题。由于在调用类时会创建数据库对象(使用定义的任何凭据/位置),因此可以将数据库连接重定向到任何需要的测试数据库。您能否详细说明一下:“由于对数据库的持续依赖,制作一个几乎无法测试的系统并不需要太多的咆哮。”?
  • 您能否详细说明如何改进(例如)getByID 函数?什么样的范式转变使这段代码可单元测试?每个函数都需要是实例函数,而不是静态函数吗?
【解决方案2】:

对于您正在尝试做的事情,有几种不同的设计模式。查看Data MapperActive Record

【讨论】:

  • 我想我正在使用这两个概念。整个抽象是“Data Mapper”的思想,每个单独的Class都是“Active Record”的概念。感谢这些链接。但是,我不确定这有多大帮助。这些页面上是否有编码示例或我错过的内容?我访问的链接只是描述为什么 / 如何您可能希望使用这些概念。
  • 为什么/如何的描述是重要的部分。您应该查看的另一种技术称为依赖注入。 en.wikipedia.org/wiki/Dependency_injection
【解决方案3】:

使用 PHP 的“魔术方法”__get,当您通过以下方式访问它时,您可以生成此功能:

$model = new Catchall();
$banned = $model->Advertiser->getByID(4);

...它将 a) 检查类 Advertiser 是否已经定义,b) 检查名为 Advertiser.php 的文件并包含它,或者 c) 返回一个泛型类的新实例。

您在示例中使用:: 的语法假定返回的类是静态的。我还没有编写这段代码来解决这个问题,但这样做应该是微不足道的。

查看文档:http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.methods

快速而肮脏的例子:

    public function __get($name) {

            $instance = false;
            // see if there is already a class with the requested name
            if (class_exists($name)) {
                $instance = new $name();
            }

            // check to see if there is an object def for the requested object
            if ($instance === false && file_exists(PATH_TO_OBJECTS.$name.'.php')) {
                require_once(PATH_TO_OBJECTS.$name.'.php');
                $instance = new $name();
            }

            // if instace is still not found, load up a generic
            if ($instance === false)
                $instance = new Catchall($name);

        return $instance;
    }

【讨论】:

  • 使用魔法虽然很诱人,但可能有害。尤其是在这种情况下,setter 和 getter 是可行的。而且,加载新类应该由自动加载器完成:-1
  • 呸。教条就是教条,编程就是编程。 “普遍有害”是一句老生常谈,毫无意义。我没有写复制/粘贴解决方案,我展示了一个可行的概念;要么接受,要么离开它。无论哪种方式,我都得到相同的报酬;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-14
  • 2011-10-18
  • 1970-01-01
  • 2023-04-10
  • 2023-03-14
  • 1970-01-01
相关资源
最近更新 更多