问题 学习实施基本ORM的提示/资源/模式[已结束]


我已经看到了各种MVC框架以及PHP的独立ORM框架,以及其他ORM问题;但是,大多数问题要求现有框架开始使用,即  我正在寻找什么。 (我也读过 这个问题,但我不知道该怎么做,因为答案很模糊。)

相反,我认为通过弄脏手来实际编写自己的ORM,即使是简单的ORM,我也会学得最好。除了我真的不知道如何开始,特别是因为我在其他ORM中看到的代码是如此复杂。

和我的 PHP 5.2.x(这很重要) MVC框架我有一个基本的自定义数据库抽象层,它具有:

  • 非常简单的方法 connect($host, $user, $pass, $base)query($sql, $binds)等等
  • 它支持的每个DBMS的子类
  • 表示SQL结果集的类(和各自的子类)

才不是 有:

  • Active Record功能,我假设是一个ORM的东西(如果我错了,请纠正我)

编辑: 澄清一下,我只有一个数据库抽象层。我还没有模型,但是当我实现它们时,我希望它们是原生的ORM模型(可以这么说),因此这个问题。

我已经阅读了一些关于ORM的内容,根据我的理解,它们提供了一种方法,通过将数据表示为基于PHP的类/对象,从数据库本身进一步抽象数据模型;再次,纠正我,如果我错了或以任何方式错过了。

尽管如此,我还是喜欢其他任何使用ORM框架或多或少涉及的人的简单提示。还有什么我需要注意的,简单的学术样本供我参考,或者我可以阅读的资源?


9624
2018-05-22 09:08


起源



答案:


由于这个问题相当陈旧,我想你已经尝试过自己编写ORM了。尽管如此,正如我两年前写的一个自定义ORM,我仍然想分享我的经验和想法。

如上所述,我在两年前实施了一个定制的ORM,甚至在中小型项目中也取得了一些成功。我将它集成在一个相当流行的CMS中,当时(甚至现在)它缺乏这样的ORM功能。此外,那时候像Doctrine这样的流行框架并没有让我信服。从那时起,情况发生了很大变化 学说2 因此,如果我现在可以在实现我自己的ORM或使用Doctrine 2之类的流行框架进行生产使用之间做出选择,那么这将毫无疑问 - 使用现有的稳定解决方案。但是:实现这样一个框架(以简单的方式)是一个非常有价值的学习练习,它帮助我在使用更大的开源ORM方面做了很多工作,因为您可以更好地理解与对象关系映射相关的陷阱和困难。

实现基本的ORM功能并不太难,但只要对象之间的关系映射发挥作用,就会变得更加困难/有趣。


我是如何开始的? 

让我迷上的是Martin Fowlers的书 企业应用架构模式。如果您想编写自己的ORM,或者即使您只是使用某个ORM框架, 买这本书。 它是涵盖对象关系映射领域的许多基本和高级技术的最有价值的资源之一。阅读它,你会得到很多关于ORM背后模式的好主意。

基础架构

我决定是否愿意使用 活跃记录 接近或某种 数据映射器。此决定影响数据库中的数据如何映射到实体。我决定实现一个简单的Data Mapper,方法与之相同 学说2 要么 过冬 在Java中使用。 Active Record是ORM功能的方法(如果你可以这样称呼它) Zend框架。 Active Record比Data Mapper简单得多,但也更加有限。阅读这些模式并检查上述框架,您可以非常快速地获得差异。如果你决定使用Data Mapper,你也应该阅读 PHPs反射API

查询

我有一个雄心勃勃的目标,就是创建自己的查询语言 DQL 在学说或 HQL 在Hibernate中。我很快就放弃了,因为编写自定义SQL解析器/词法分析器似乎很复杂(而且它确实是!)。我所做的是实现一个 查询对象,为了封装查询中涉及哪些表的信息(这很重要,因为您需要将数据从数据库映射到每个表的相关类)。

在我的ORM中查询对象看起来像这样:

public function findCountryByUid($countryUid) {
    $queryObject = new QueryObject();
    $queryObject->addSelectFields(new SelectFields('countries', '*'))
            ->addTable(new Table('countries'))
            ->addWhere('countries.uid = "' . intval($countryUid) . '"');

    $res = $this->findByQuery($queryObject);
    return $res->getSingleResult();
}

组态 

通常,您还需要具有某种配置格式,Hibernate使用XML(以及其他),Doctrine 2使用PHP注释,EZComponents在其中使用PHP数组 持久对象组件 作为配置格式。这也是我使用的,它似乎是一个自然的选择,我使用的CMS也使用PHP配置格式。

使用该配置,您可以定义

  • 哪个表被映射到哪个类
  • 哪些字段应该映射到类实例
  • 表的字段有什么类型(int,string等)
  • 实体之间的关系(例如,User类具有对UserGroup类的引用)
  • 等等

这就是您在Data Mapper中使用的信息,用于将DB结果映射到对象。

履行

由于编写自定义ORM的复杂性,我决定采用强大的测试驱动方法。 TDD与否,编写许多单元测试对于这样的项目来说是一个非常好的主意。除此之外:弄脏你的手并保持福勒书的关闭。 ;-)


正如我所说,这确实值得付出努力,但我不想再这样做,因为现在存在成熟的框架。

我不再使用我的ORM,它工作,但缺少许多功能,其中包括:延迟加载,组件映射,事务支持,缓存,自定义类型,预处理语句/参数等。它的性能不够好在大型项目中使用它。

尽管如此,我希望我能在ORM领域给你一些起点,如果你还不知道的话。 ;-)


11
2017-10-27 14:54



很棒的概述。你有一个关于存在经过试验和测试的解决方案的有效观点,但是当我涉足设计我自己的ORM时,我还应该记住一些重要的想法。我很高兴这对你来说也是一次很棒的学习经历。一旦我外出,我一定会找到福勒的书。如果几天没有新的答案,这可能会让你另外得到+15。谢谢你的时间和解释! :) - BoltClock♦


答案:


由于这个问题相当陈旧,我想你已经尝试过自己编写ORM了。尽管如此,正如我两年前写的一个自定义ORM,我仍然想分享我的经验和想法。

如上所述,我在两年前实施了一个定制的ORM,甚至在中小型项目中也取得了一些成功。我将它集成在一个相当流行的CMS中,当时(甚至现在)它缺乏这样的ORM功能。此外,那时候像Doctrine这样的流行框架并没有让我信服。从那时起,情况发生了很大变化 学说2 因此,如果我现在可以在实现我自己的ORM或使用Doctrine 2之类的流行框架进行生产使用之间做出选择,那么这将毫无疑问 - 使用现有的稳定解决方案。但是:实现这样一个框架(以简单的方式)是一个非常有价值的学习练习,它帮助我在使用更大的开源ORM方面做了很多工作,因为您可以更好地理解与对象关系映射相关的陷阱和困难。

实现基本的ORM功能并不太难,但只要对象之间的关系映射发挥作用,就会变得更加困难/有趣。


我是如何开始的? 

让我迷上的是Martin Fowlers的书 企业应用架构模式。如果您想编写自己的ORM,或者即使您只是使用某个ORM框架, 买这本书。 它是涵盖对象关系映射领域的许多基本和高级技术的最有价值的资源之一。阅读它,你会得到很多关于ORM背后模式的好主意。

基础架构

我决定是否愿意使用 活跃记录 接近或某种 数据映射器。此决定影响数据库中的数据如何映射到实体。我决定实现一个简单的Data Mapper,方法与之相同 学说2 要么 过冬 在Java中使用。 Active Record是ORM功能的方法(如果你可以这样称呼它) Zend框架。 Active Record比Data Mapper简单得多,但也更加有限。阅读这些模式并检查上述框架,您可以非常快速地获得差异。如果你决定使用Data Mapper,你也应该阅读 PHPs反射API

查询

我有一个雄心勃勃的目标,就是创建自己的查询语言 DQL 在学说或 HQL 在Hibernate中。我很快就放弃了,因为编写自定义SQL解析器/词法分析器似乎很复杂(而且它确实是!)。我所做的是实现一个 查询对象,为了封装查询中涉及哪些表的信息(这很重要,因为您需要将数据从数据库映射到每个表的相关类)。

在我的ORM中查询对象看起来像这样:

public function findCountryByUid($countryUid) {
    $queryObject = new QueryObject();
    $queryObject->addSelectFields(new SelectFields('countries', '*'))
            ->addTable(new Table('countries'))
            ->addWhere('countries.uid = "' . intval($countryUid) . '"');

    $res = $this->findByQuery($queryObject);
    return $res->getSingleResult();
}

组态 

通常,您还需要具有某种配置格式,Hibernate使用XML(以及其他),Doctrine 2使用PHP注释,EZComponents在其中使用PHP数组 持久对象组件 作为配置格式。这也是我使用的,它似乎是一个自然的选择,我使用的CMS也使用PHP配置格式。

使用该配置,您可以定义

  • 哪个表被映射到哪个类
  • 哪些字段应该映射到类实例
  • 表的字段有什么类型(int,string等)
  • 实体之间的关系(例如,User类具有对UserGroup类的引用)
  • 等等

这就是您在Data Mapper中使用的信息,用于将DB结果映射到对象。

履行

由于编写自定义ORM的复杂性,我决定采用强大的测试驱动方法。 TDD与否,编写许多单元测试对于这样的项目来说是一个非常好的主意。除此之外:弄脏你的手并保持福勒书的关闭。 ;-)


正如我所说,这确实值得付出努力,但我不想再这样做,因为现在存在成熟的框架。

我不再使用我的ORM,它工作,但缺少许多功能,其中包括:延迟加载,组件映射,事务支持,缓存,自定义类型,预处理语句/参数等。它的性能不够好在大型项目中使用它。

尽管如此,我希望我能在ORM领域给你一些起点,如果你还不知道的话。 ;-)


11
2017-10-27 14:54



很棒的概述。你有一个关于存在经过试验和测试的解决方案的有效观点,但是当我涉足设计我自己的ORM时,我还应该记住一些重要的想法。我很高兴这对你来说也是一次很棒的学习经历。一旦我外出,我一定会找到福勒的书。如果几天没有新的答案,这可能会让你另外得到+15。谢谢你的时间和解释! :) - BoltClock♦


可以使用简单的ORM构建 __get() 和 __set() 以及一些自定义方法(可能使用 __call()),这是一个简单的伪代码:

class ORM
{
  private $table = null;
  private $fields = array();

  function __construct($table)
  {
    $this->table = $table;
  }

  function __get($key)
  {
    return isset($this->fields[$key]) ? $this->fields[$key] : false;
  }

  function __set($key, $value)
  {
    $this->fields[$key] = $value;
  }

  function load($id, $field = 'id')
  {
    // populate $this->fields with SELECT * FROM $this->table WHERE $field = $id;
  }

  function save()
  {
    if (isset($this->fields['id']))
    {
      // UPDATE $this->table SET $this->fields;
    }

    else
    {
      // INSERT INTO $this->table $this->fields;
    }
  }
}

$user = new ORM('user');

$user->name = 'name';
$user->pass = '1337';

$user->save();

这只是一个帮助您入门的基本示例。您可以使用。添加更多逻辑 __call() 通过其他字段获取结果的魔术方法 id 例如。

请记住,我给出的示例不处理关系,这是各种ORM实现真正不同的地方,但是我通常不相信任何ORM来处理我的关系,因为它们往往更慢并且不会产生有效的查询。


2
2018-05-22 18:38



谢谢!至于你的最后一点,你如何处理关系呢? - BoltClock♦
@BoltClock:我没有。我不是ORM的忠实粉丝所以我通常直接编写完整的SQL查询。阅读以下问题以获得灵感: stackoverflow.com/questions/1336661/simpler-orm-for-php。 - Alix Axel