5

如何用纯 php 创建 ORM 框架。ORM 创建教程

 11 months ago
source link: https://blog.p2hp.com/archives/11646
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何用纯 php 创建 ORM 框架。ORM 创建教程

编写原始 SQL 查询很酷。当您不用数千行代码来管理项目时,这很酷。

当项目增长时,最好自动化构建 sql 查询。

然而,ORM这是非常复杂和沉重的结构,会减慢您的应用程序的速度,它也为您的应用程序带来了一些额外的功能:您可以添加自动安全检查、用户输入的清理和其他内容。

我们需要将表中的行映射到 php 代码中的对象。因此,至少我们需要为代码中使用的每个表提供类:

class Post {

   // ... another code

   public function save() { // this methods save obejct to databases table
     // ... code
   }

}

这是引用表的类post。相同的结构将在类User或中Comment。实际上我们应该在每个表类上都有 save 方法。这就是为什么我们为每个实体类创建抽象类:

abstract class Entity {

  public function save() {
    // this method construct sql using reflection 
  }
}

save我们在上节课中已经写好了方法,所以我们只需复制并修改其内容即可:

public function save() {

    $class = new \ReflectionClass($this);
    $tableName = strtolower($class->getShortName());

    $propsToImplode = [];

    foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { // consider only public properties of the providen 
      $propertyName = $property->getName();
      $propsToImplode[] = '`'.$propertyName.'` = "'.$this->{$propertyName}.'"';
    }

    $setClause = implode(',',$propsToImplode); // glue all key value pairs together
    $sqlQuery = '';

    if ($this->id > 0) {
      $sqlQuery = 'UPDATE `'.$tableName.'` SET '.$setClause.' WHERE id = '.$this->id;
    } else {
      $sqlQuery = 'INSERT INTO `'.$tableName.'` SET '.$setClause.', id = '.$this->id;
    }

    $result = self::$db->exec($sqlQuery);

    if (self::$db->errorCode()) {
        throw new \Exception(self::$db->errorInfo()[2]);
    }

    return $result;
}

在上面的示例中,我们使用类名作为表名。为了使其更加灵活,我们应该引入

abstract class Entity {

  protected $tableName; 

  // ...

并在save方法中检查其是否设置为某个值。如果已设置 - 则使用该变量的值作为表名:

public function save() {

    $class = new \ReflectionClass($this);
    $tableName = '';

    if ($this->tableName != '') {
      $tableName = $this->tableName;
    } else {
      $tableName = strtolower($class->getShortName());
    }

    // ... rest of the code

我们还需要实际的数据库连接。我们将一个PDO对象放入 protected$dbEntity

/**
*
* @var PDO
*/
protected $db;

public function __construct() {
    try {
        $this->db = new \PDO('mysql:host=localhost;dbname=blog','root', '');
    } catch (\Exception $e) {
        throw new \Exception('Error creating a database connection ');
    }
}

PDO我在 的构造函数中初始化了该对象,Entity只是为了向您展示这确实是PDO一个对象。在实际系统中你不应该这样做。在这种情况下,请使用依赖项注入或服务容器。我们将在本课程的后续课程中讨论它们。

将字段映射到表中的列

我们将使用最简单的方法将表的列映射到对象 - 公共属性。有人说在类中使用公共属性破坏了incapsulation面向对象编程的特性。它们在某些情况下是正确的,但使用公共属性可以降低 orm 的过度复杂性,并给出类的严格描述性视图。

让我们看一下使用ORM我们推广的功能的类:

class Post extends Entity {

  protected $tableName = 'posts'; // usually tables are named in plural when the object should be named singular

  public $id;

  public $title;

  public $body;

  public $author_id;

  public $date;

  public $views;

  public $finished;
}

看起来棒极了,不是吗?

现在是时候将数据库表中的字段映射到该对象中了。这可以通过将数组转换为实体对象的方法来完成morph。最简单的实现如下:

/**
*
* @return Entity
*/
public static function morph(array $object) {
  $class = new \ReflectionClass(get_called_class()); // this is static method that's why i use get_called_class

  $entity = $class->newInstance();

  foreach($class->getProperties(\ReflectionProperty::PUBLIC) as $prop) {
    if (isset($object[$prop->getName()])) {
      $prop->setValue($entity,$object[$prop->getName()]);
    }
  }

  $entity->initialize(); // soft magic

  return $entity;
}

正如我所说,这是将数组变形为对象的非常简单的实现,并且可以向其中添加许多内容:从数组中转换为正确的类型值、验证等。也许,我们会在下一课中接管类型的铸造。

在 i 的末尾调用实体的morph方法。initialize正如我在评论中提到的,它的魔力是:每个对象都可以实现方法initialize,并且会在创建的每个对象上调用该方法。当您的实体上有一些额外的逻辑时,这将有助于设置所有内容。

创建和保存对象

此时我们已经可以在 php 中创建一个对象并通过面向对象的 api 将其保存到数据库,而无需运行任何行SQL

$post = new Post(); // this creates post object

$post->title = 'How to cook pizza';
$post->date = time();
$post->finished = false;

$post->save(); // here we construct sql and execute it on the database

这里我们已经在propAUTO_INCREMENT中拥有了表的 id (因为它是表的主索引):$idPost

echo "new post id: ".$post->id;

搜索带有 orm 类的表

我们应该实现至少两种方法,这些方法能够在数据库中搜索一个元素并返回实体类的实例以及重新调整大量对象的版本。让我们定义方法 find ,它返回项目数组:

/**
*
* @return Entity[]
*/
public static function find ($options = []) {

    $result = [];
    $query = '';

    $whereClause = '';
    $whereConditions = [];

    if (!empty($options)) {
      foreach ($options as $key => $value) {
        $whereConditions[] = '`'.$key.'` = "'.$value.'"';
      }
      $whereClause = " WHERE ".implode(' AND ',$whereConditions);
    }

    $raw = self::$db->query($query);

    if (self::$db->errorCode()) {
        throw new \Exception(self::$db->errorInfo()[2]);
    }

    foreach ($raw as $rawRow) {
        $result[] = self::morph($rawRow);
    }

    return $result;
}

为了简化示例,我仅实现了简单的情况,当您可以传递字段名称和字段应等于的值时。例如,下一个代码将返回标题字段等于的项目Some title

$posts = Post::find([
  'title' => 'Some title'
]);

我们可以通过向我们的方法添加额外的功能来克服这个限制find:添加将原始 SQL 查询部分(where 子句)传递给 find 方法的可能性。因此,如果您需要执行一些非标准 WHERE,只需将条件find作为字符串参数传递给方法:

$posts = Post::find('id IN (1,5,10)');

我们应该更改 find 方法的代码以使其正常工作,如上例所示:

/**
*
* @return Entity[]
*/
public static function find ($options = []) {

    $result = [];
    $query = '';

    if (is_array($options)) {
      // ... old code
    } elseif (is_stirng($options)) { 
      $query = 'WHERE '.$options;
    } else {
      throw new \Exception('Wrong parameter type of options');
    }

    $raw =  $this->database->execute($query);
    foreach ($raw as $rawRow) {
      $result[] = self::morph($rawRow);
    }

    return $result;
}

实际上,我忘记在方法中实现限制和顺序,find这样你就可以将其作为家庭任务的一部分:)

给你一个提示:我们应该扩展参数$options,上面的例子看起来像:

[
  'condtions' => [
    'title' => 'Some title'
  ]
]

现在我们可以添加limit和/或order和其他选项find

$posts = Post::find([
  'conditions' => 'id IN (1,2,5)',
  'order' => 'id DESC' 
]);

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK