在 PHP 应用程序和 ORM 中构建数据访问层

Posted

技术标签:

【中文标题】在 PHP 应用程序和 ORM 中构建数据访问层【英文标题】:Building data access layer in PHP application and ORM 【发布时间】:2016-07-16 03:00:23 【问题描述】:

我在弄清楚如何访问我的数据库表中的数据时遇到了一些麻烦。我知道有很多框架可以帮助实现这一点,例如 Yii,但由于它与团队中其他人的学习曲线和时间限制,我试图避免这条路线。

假设我有一个名为 File 的表,其中包含文件的名称、上传日期、上传用户等的列表。我还有另一个名为 Versions 的表,它基本上包含每个文件的版本:当用户上传相同的文件时文件,它认为它是一个修订。

文件

fid | name  | uploadDate 
1   | test  | 2017-01-01
2   | test2 | 2017-01-01

版本

vid | fid   | user
1   | 1     | a
2   | 1     | b

我最初的想法是为每个表创建一个类,我将在 Files 构造函数中放入 fid 并检索该单行并对 Versions 执行相同操作,其中 vid 将放入构造函数并检索特定版本。

但是,这意味着我将有两个单独的查询,每个类一个。所以我在考虑也许将它们合二为一。这意味着我将在 Files 类中的一个查询(使用 LEFT JOIN)下检索文件信息和版本信息,然后使用 Versions 类来处理数据,而不是查询数据库并处理数据。

由于某种原因,我觉得这具有更好的性能,因为我在这里使用 JOINS 来帮助我,但另一方面,如果我只需要文件的特定版本的信息,我需要创建File 的一个实例,这意味着使用 fid THEN 从内部检索我想要的版本。 (

也许有人可以给我一些关于这种方式以及在无法访问框架时该怎么做的见解。

【问题讨论】:

如果你和你的团队是php开发人员,没有时间学习一个框架,我建议使用redbeanredbeanphp.com/index.php快速简单的orm即用型 【参考方案1】:

ORM 不会解决你所有的问题

在许多情况下,使用模型访问数据库很方便,但并非总是如此。对您而言,让您的 ORM 模型免于需要复杂查询和高性能的任务可能会更好。

正确使用 ORM 模式

ORM 只是您可以在代码中使用的众多编码模式之一。

阅读此主题以了解更多信息What is an ORM and where can I learn more about it?

您不应将 ORM 视为隔离对数据库的访问的应用程序层。它只是一种以面向对象的方式操作数据的技术。

在每个流行的框架中,您都会看到 ORM 是一个选项,而不是一个强制层。

当您想要获取特定文件的所有版本时,请考虑这种情况。使用getAllVersions 方法创建类似FileVersionManager 的单例,它将通过fid 执行JOIN 查询。这里也可以是checkoutToVersiongetPreviousVersion。此方法可以返回一个或一组 Version ORM 模型。重要的是——这种方式在语义上更有意义,因此更容易被其他程序员阅读和理解。

或者,如果您需要在每次文件更改时生成版本,您可以将此逻辑委托给 Versionable 行为之类的东西。

我更愿意将 ORM 视为某种应用程序域定义层。

那么,回答你的问题

如果您想要自己的 ORM 实现,请不要让它太复杂。使用 CRUD 方法和模式定义为每个表创建单独的 ORM 模型。并使用其他模式来执行复杂的查询。

示例(应实施Query.selectWhere 以使示例正常工作):

<?php


/**
 * @property $name
 **/
class File extends ORMModel

    public function tableName()
    
        return 'files';
    

    public function properties()
    
        return array_merge(parent::properties(), ['name']);
    


/**
 * @property $number
 * @property $file_id
 **/
class Version extends ORMModel

    public function tableName()
    
        return 'versions';
    

    public function properties()
    
        return array_merge(parent::properties(), ['file_id', 'number']);
    

    public function getFile()
    
        return new File($this->file_id);
    


class FileVersionManager

    public static function getLatestVersion($file)
    
        $query = new Query();
        $rows = $query->selectWhere((new Version())->tableName(), ['file_id' => $file->id]);

        $max = 0;
        $maxId = null;
        foreach ($rows as $row) 
            if ($row['number'] > $max) 
                $maxId = $row['id'];
                $max = $row['number'];
            
        

        return new Version($maxId);
    

    public static function createNewVersion($file)
    
        $latestVersion = static::getLatestVersion($file);
        $newVersion = new Version();
        $newVersion->file_id = $file->id;
        $newVersion->number = $latestVersion->number + 1;
        $newVersion->insert();

        return $newVersion;
    


class Query


    private static $datasource = [
        'files' => [
            1 => [
                'name' => 'file1',
            ],
            2 => [
                'name' => 'file1',
            ]
        ],
        'versions' => [
            1 => [
                'file_id' => 1,
                'number' => 1
            ],
            2 => [
                'file_id' => 1,
                'number' => 2
            ],
            3 => [
                'file_id' => 2,
                'number' => 1
            ],
            4 => [
                'file_id' => 2,
                'number' => 2
            ],
        ]
    ];

    public function select($tablename) 
        return static::$datasource[$tablename];
    

    public function selectOne($tablename, $id) 
        return @static::$datasource[$tablename][$id];
    

    public function selectWhere($tableName, $condition) 
        //@todo: implement selection assuming that condition is ['propertyName1' => 'propertyValue1', 'propertyName2' => 'propertyValue2',  ]
        //should retun array of properties including id for above example
        return [];
    

    public function update($tableName, $id, $values) 
        if (empty(static::$datasource[$tableName][$id])) return false;
        static::$datasource[$tableName][$id] = $values;
        return true;
    

    public function insert($tableName,  $values) 
        $id = max(array_keys(static::$datasource[$tableName])) + 1;
        static::$datasource[$tableName][$id] = $values;
        return $id;
    

    public function delete($tableName, $id)
    
        unset(static::$datasource[$tableName][$id]);
        return true;
    



/**
 * @property $id
 **/
abstract class ORMModel

    private $properties;

    public abstract function tableName();
    public function properties() 
        return ['id'];
    

    public function __get($name) 
        $propertyNames = $this->properties();

        if (in_array($name, $propertyNames)) 
            return @$this->properties[$name];
        
    

    public function __set($name, $value) 
        $propertyNames = $this->properties();

        if (in_array($name, $propertyNames)) 
            $this->properties[$name] = $value;
        
    

    public function __construct($id = null)
    
        if (!empty($id)) 
            $query = new Query();
            if ($this->properties = $query->selectOne($this->tableName(), $id)) 
                $this->properties['id'] = $id;
            
        
    

    public function insert()
    
        $query = new Query();
        $id = $query->insert($this->tableName(), $this->properties);
        $this->properties['id'] = $id;
        return $this;
    

    public function update()
    
        if (empty($this->properties['id'])) return false;

        $query = new Query();
        return $query->update($this->tableName(), $this->id, array_diff($this->properties, ['id']));
    

    public function delete()
    
        if (empty($this->properties['id'])) return false;

        $query = new Query();
        return $query->delete($this->tableName(), $this->id);
    



$version = new Version(1);
$file = $version->getFile();
var_dump($file->name);

$file = new File(2);
$version = FileVersionManager::getLatestVersion($file);
var_dump($version->number);

FileVersionManager::createNewVersion($file);
$version = FileVersionManager::getLatestVersion($file);
var_dump($version->number);

P.S.在这里您可以定义自己的 Query 实现,这应该适用于您的(任何)数据源

P.P.S.我仍然建议你学习一些流行的框架(Yii2、Laravel、Symfony 或任何其他),因为这是学习构建应用程序架构的最佳实践的好方法

【讨论】:

好的。在这种情况下,查询结果将被传递到处理数据的类模型中? @Dimitri 如果您看一下框架,那么您会发现 ORM 通常不会封装对 DB 的直接查询。我通常在这里有一个额外的层(比如 Yii 中的 QueryBuilder),而 ORM 只是提供了一种使用 DB 模式的透明方式。你想将你的 ORM 构建为严格高于 SQL 查询的层,你很快就会陷入灵活性限制的陷阱 - ORM 将是你的“薄弱点”,你会讨厌它们并想要执行直接在各处构建 SQL 查询,而不是使用 ORM。 @Dimitri 我已经更新了我的答案。包含指向该主题的链接,其中包含 ORM 说明和建议的代码结构的一个小示例。希望这会有所帮助 太棒了!非常感谢您的意见!

以上是关于在 PHP 应用程序和 ORM 中构建数据访问层的主要内容,如果未能解决你的问题,请参考以下文章

什么是ORM,以及在php上的使用

使用 OOP PHP ORM 选择查询

如何在不使用Eloquent ORM时构建Laravel应用程序

如何为来自 ORM 系统(用于 PHP)的字段构建带有翻译的表单?

你在哪里“负载平衡”一个 PHP MVC 应用程序中的 ORM

Azure网络设置