【问题标题】:Discovering the best approach to storing a specific object-oriented data structure发现存储特定面向对象数据结构的最佳方法
【发布时间】:2015-04-14 14:59:02
【问题描述】:

经过一些绝妙的建议,以及由于最终有可能解决我的问题而兴奋的不眠之夜,我意识到我仍然没有完全找到解决方案。所以,我在这里更详细地概述我的问题,希望有人知道实现这一目标的最佳方法。

回顾一下(如果你还没有读过the previous post):

  • 我正在从头构建一个PHP OOP框架(这件事我没有选择)
  • 需要该框架以尽可能最有效的方式处理面向对象的数据。它不需要闪电般快速,它只需要成为问题的最佳解决方案
  • 对象与严格编写的 oop 对象非常相似,因为它们是特定类的实例,其中包含一组严格的属性。
  • 对象属性可以是基本类型(字符串、数字、布尔值),但也可以是一个对象实例或对象数组(限制数组必须是相同类型的对象)

最终,一个支持面向文档的存储(类似于 XML 或 JSON)的存储引擎,其中对象本身具有严格的结构。

我将在这篇文章的其余部分详细解释我正在尝试做的事情,而不是概述我迄今为止所做的尝试(我在上一篇文章中对此进行了简要讨论)。这篇文章会很长(抱歉!)。


首先,我需要讨论一个必须引入的术语,以解决一组需求带来的最关键问题之一。我将这个术语命名为“持久性”。我知道这个术语在处理对象数据库时确实有不同的含义,因此我愿意接受关于不同术语的建议。但现在,让我们继续前进吧。

持久性

持久性是指对象的独立性。在考虑从 XML 生成的数据结构时,我发现有必要引入这个术语(这是我必须能够做到的)。在 XML 中,我们看到完全依赖于其父对象的对象,同时我们也看到可以独立于父对象的对象。

以下示例是符合特定结构的 XML 文档示例(例如,.wsdl 文件)。每个对象都类似于具有严格结构的类型。 每个对象都有一个“id”属性

在上面的例子中,我们看到了两个用户。两者在其“地址”属性下都有自己的地址对象。但是,如果我们查看它们的“favouriteBook”属性,我们可以看到它们都重用同一个 Book 对象实例。另请注意,这些书籍使用同一作者。

所以我们有一个非持久性的 Address 对象,因为它只与它的父对象(用户)相关,这意味着它的实例只需要在拥有的用户对象存在时存在。然后是 持久性 的 Book 对象,因为它可以在多个位置使用,并且它的实例保持持久性。

起初,我对想出这样一个术语感到有点疯狂,但是,我发现它非常易于理解和实际使用。它最终将“多对多、一对多、一对一、多对一”的公式浓缩为一个简单的想法,我觉得它更适合嵌套数据。

我在这里对上述数据进行了图像表示:

根据我对持久性的定义,有一套规则可以帮助理解它。这些规则如下:

  • 更新/创建
  • 正在存储的基础对象的持久子对象更新持久对象的属性,最终更新其实例。
  • 非持久对象总是创建对象的新实例以确保它们始终使用非持久实例(在任何给定时间,没有两个非持久实例存在于多个位置)
  • 正在删除
  • 基础对象的持久子对象不会被递归删除。这是因为持久对象可能存在于其他地方。您总是会直接删除持久对象。
  • 基础对象的非持久性子对象与基础对象一起被移除。如果它们没有被移除,它们就会被搁置,因为它们的设计要求它们有一个父节点。
  • 正在检索
  • 由于持久性主要定义了修改的工作方式,因此检索并不涉及很多持久性,除了您期望持久性如何影响模型的存储方式以及如何检索模型(持久性对象实例在任何地方都保持持久性)位于,非持久对象总是有自己的实例)

在我们继续之前要注意的最后一件事 - 数据模型的持久性是由模型本身定义的而不是关系。最初,持久性是关系的一部分,但是当系统希望您知道模型的结构以及如何使用它们时,这完全没有必要。最终,模型的每个模型实例要么是持久的,要么不是。


所以现在看一些代码,你可能会开始看到疯狂背后的方法。虽然看起来这个解决方案的原因是能够围绕符合一组条件的客观数据构建一个存储系统,但它的设计实际上来自希望能够存储类实例,和/或从一个客观的数据结构。

我已经编写了一些伪类作为我正在尝试生成的功能的示例。我已经注释了大多数方法,包括类型声明。

首先,这将是所有模型类都将扩展的基类。这个类的目的是在模型类/对象和数据库/存储引擎之间创建一个层:

<?php
/**
 * This is the base class that all models would extend. It contains the functionalities that are useful among all model
 * objects, such as crud actions, finding, and crud event management.
 *
 * @author Donny Sutherland <donny@pixelpug.co.uk>
 * @package Main
 * @subpackage Sub
 *
 * Class ORMModel
 */
class ORMModel {
    /**
     * In order to generate relationships between objects, every object MUST have an id. This functions as the object's
     * unique identifier. Each object in it's model type (collection) has it's own id.
     *
     * @var int
     */
    public $id;
    /**
     * Internal property assigned by the application. This is where the persistence of the model is defined.
     *
     * @var bool
     */
    protected $internal_isPersistent = true;
    /**
     * Internal property assigned by the application. This is an array of the model's properties, and their PHP type.
     *
     * For example, a User model might use something like this:
     * array(
        "id" => "integer",
     *  "username" => "string",
     *  "password" => "string",
     *  "address" => "object",
     *  "favouriteBook" => "object",
     *  "allBooks" => "array"
     * )
     *
     * @var array
     */
    protected $internal_propertyTypes = array();
    /**
     * Internal property assigned by the application. This is an array of the model's properties which are objects, and
     * the MODEL CLASS type of the object.
     *
     * For example, the User model example for the property types might use this:
     * array(
     *  "address" => "Address",
        "favouriteBook" => "Book",
     *  "allBooks" => "Book"
     * )
     *
     * @var array
     */
    protected $internal_objectTypes = array();
    /**
     * I am not 100% sure on the best way to use this yet, I have tried a few different ways and all seem to cause
     * performance problems. But ultimately, before we attempt to update an object, we cache it's currently stored
     * instance to this property, allowing us to compare old vs new. I find this really useful for detecting whether a
     * property has changed, I just need to work out the best way to do it.
     *
     * @var $this
     */
    protected $internal_old;

    /**
     * The lazy way to construct an empty model object (all NULL values)
     *
     * @return $this
     */
    final public static function constructEmpty() {

    }

    /**
     * This method is used by the other constructFromXXX methods once the data has been converted to a PHP array.
     * This method is what allows us to build a RESTful interface into the ORM system as it conforms to the following
     * rules:
     *
     * - if the id is set (not null), first pull the object from storage.
     * - For each key => value of the passed array, OVERWRITE the value
     * - For properties that are model objects/arrays, if the property is assiged to the array:
     *  - if the array value is NULL, we are clearing the object relationship
     *  - if the array valus is not null, construct recursively at this point
     *
     * Ultimately, if you assign a property in the array that you pass to this method, it will overwrite the value. If
     * you do not, it will use the property value in storage.
     *
     * @param array $array
     *
     * @return $this
     */
    final public static function constructFromArray(array $array) {

    }

    /**
     * This method attempts to decode the value of $json into a PHP array. It then calls constructFromArray if the string
     * could be decoded.
     *
     * @param $json
     *
     * @return $this
     */
    final public static function constructFromJson($json) {

    }

    /**
     * This method attempts to decode the value of $xml into a PHP array. It then calls constructFromArray if the xml
     * could be decoded.
     *
     * @param $xml
     *
     * @return $this
     */
    final public static function constructFromXml($xml) {

    }

    /**
     * Find one object, based on a set of options.
     *
     * @param ORMCrudOptions $options
     *
     * @return $this
     */
    final public static function findOne(ORMCrudOptions $options) {

    }

    /**
     * Find all objects, (optionally) based on a set of options
     *
     * @param ORMCrudOptions $options
     *
     * @return $this[]
     */
    final public static function findAll(ORMCrudOptions $options=null) {

    }

    /**
     * Find the count of objects, based on a set of optoins
     *
     * @param ORMCrudOptions $options
     *
     * @return integer
     */
    final public static function findCount(ORMCrudOptions $options) {

    }

    /**
     * Find one object, based on it's id, and (optionally) a set of options.
     *
     * @param ORMCrudOptions $options
     *
     * @return $this
     */
    final public static function findById($id,ORMCrudOptions $options=null) {

    }

    /**
     * Push this object to storage. This creates/updates all of the contained objects, based on their id's and
     * persistence.
     *
     * @param ORMCrudOptions $options
     *
     * @return bool
     */
    final public function pushThis(ORMCrudOptions $options) {

    }

    /**
     * Pull this object form storage. This retrieves all of the contained objects again, based on their id's and
     * persistence.
     *
     * @param ORMCrudOptions $options
     *
     * @return bool
     */
    final public function pullThis(ORMCrudOptions $options) {

    }

    /**
     * Remove this object from storage. This conditionally removes the contained objects (based on persistence) based
     * on their id's.
     *
     * @param ORMCrudOptions $options
     */
    final public function removeThis(ORMCrudOptions $options) {

    }

    /**
     * This is a crud event.
     */
    public function beforeCreate() {

    }

    /**
     * This is a crud event.
     */
    public function afterCreate() {

    }

    /**
     * This is a crud event.
     */
    public function beforeUpdate() {

    }

    /**
     * This is a crud event.
     */
    public function afterUpdate() {

    }

    /**
     * This is a crud event.
     */
    public function beforeRemove() {

    }

    /**
     * This is a crud event.
     */
    public function afterRemove() {

    }

    /**
     * This is a crud event.
     */
    public function beforeRetrieve() {

    }

    /**
     * This is a crud event.
     */
    public function afterRetrieve() {

    }
}

因此,最终,此类将被设计为提供构造、查找、保存、检索和删除模型对象的功能。内部属性是仅存在于类中(不在存储中)的属性。当您使用接口创建模型并向模型添加属性/字段时,框架本身会填充这些属性。

这个想法是,该框架带有一个用于管理数据模型的接口。使用此界面,您可以创建模型,并将属性/字段添加到模型中。这样做时,系统会自动为您创建类文件,并在您修改持久性和属性类型时更新这些内部属性。

为了让开发人员友好,系统会为每个模型创建两个类文件。一个基类(扩展 ORMModel)和另一个类(扩展基类)。基类由系统操作,因此不建议修改此文件。开发人员使用另一个类来为模型和 crud 事件添加额外的功能。

回到示例数据,这里是 User 基类:

    <?php
class User_Base extends ORMModel {
    public $name;
    public $pass;
    /**
     * @var Address
     */
    public $address;
    /**
     * @var Book
     */
    public $favouriteBook;

    protected $internal_isPersistent = true;
    protected $internal_propertyTypes = array(
        "id" => "integer",
        "name" => "string",
        "pass" => "string",
        "address" => "object",
        "favouriteBook" => "object"
    );
    protected $internal_objectTypes = array(
        "address" => "Address",
        "favouriteBook" => "Book"
    );
}

几乎不言自明。再次注意,内部属性是由系统生成的,因此这些数组将根据您在模型管理界面中创建/修改用户模型时指定的属性/字段生成。还要注意地址和favouriteBook 属性定义上的文档块。这些也是由系统生成的,使这些类对 IDE 非常友好。

这将是为 User 模型生成的另一个类:

    <?php
final class User extends User_Base {
    public function beforeCreate() {

    }

    public function afterCreate() {

    }

    public function beforeUpdate() {

    }

    public function afterUpdate() {

    }

    public function beforeRemove() {

    }

    public function afterRemove() {

    }

    public function beforeRetrieve() {

    }

    public function afterRetrieve() {

    }
}

再次,非常不言自明。我们扩展了基类以创建另一个类,开发人员可以在其中添加其他方法,并向 crud 事件添加功能。

我不会添加构成示例数据其余部分的其他对象。因为上面应该解释了它们的外观。

所以您可能/可能没有注意到,在 ORMModel 类中,CRUD 方法需要 ORMCrudOptions 类的实例。这个类对整个系统非常重要,所以让我们快速看一下:

    <?php

/**
 * Despite this object being some-what aggregate, it it quite possibly the most important part of the ORM, in that it
 * defines how CRUD actions are executed, and outline how the querying is done.
 *
 * Class ORMCrudOptions
 */
final class ORMCrudOptions {
    /**
     * This ultimately makes up the "where" part of the sql query. However, because we want to be able to make querying
     * possible at any depth within the hierarchy of a model, this gets quite complicated.
     *
     * Previously, I developed a system which allowed the user to do something like this:
     *
     * "this.customer.address.postcode LIKE ('%XXX%') OR this.customer.address.line1 LIKE ('%XXX%')
     *
     * he "this" and the "." are my extension to basic sql. The "this" refers to the base model that you are finding,
     * and each "." basically drills down into the hierarchy to make a comparison on a property somewhere within a
     * contained model object.
     *
     * I will explain more how I did this in my post, I am most definitely looking at how I could better achieve this
     * though.
     *
     * @var string
     */
    private $query;
    /**
     * This allows you to build up a list of order by definitions.
     *
     * Using the orderBy method, you can chain up the order by statements like:
     *
     * ->orderBy("this.name","asc")->orderBy("this.customer.address.line1","desc")
     *
     * Which would be similar to doing:
     *
     * ORDER BY this_name ASC, this_customer_address.line1 DESC
     *
     * @var array
     */
    private $orderBy;
    /**
     * This allows you to set the limit start and limit values by doing:
     *
     * ->limit(10,10)
     *
     * Which would be similar to doing:
     *
     * LIMIT 10, 10
     *
     * @var
     */
    private $limit;
    /**
     * Depth was added in my later en devours to try and help with performance. It allows you to specify the depth at
     * which to retrieve data. Although this helped with optimisation a lot, I really disliked having to use
     * implement this because it seems like a work-around. I would rather be able to increase performance elsewhere so
     * that objects are always retrieved at their full depth
     *
     * @var integer
     */
    private $depth;
    /**
     * This was another newly added feature. Whenever you execute a crud action on a model, the model instance is stored
     * in a local cache if this is true, and/or retrieved from this cached if this value is true.
     *
     * I did find this to make a significant increase on performance, although it did bring in complications that make
     * the system tricky to use at times. You really need to understand how and when to use the cache, otherwise it can
     * be infuriatingly obtuse.
     *
     * @var bool
     */
    private $useCache;
    /**
     * Built into the ORM system, and tied in with the application I set up a webhook system which fires out webhooks on
     * crud events. I discovered the need to be able to disable webhooks at times (when doing large amounts of crud
     * actions in one go) pretty early on. Setting this to false basically disables webhooks on the crud action
     *
     * @var bool
     */
    private $fireWebhooks;
    /**
     * Also build into the application, and tied into the ORM system is an access system. This works on a seperate
     * layer to the database, allowing me to use the same access system as I use for everything in the framework as I do
     * for defining crud action access. However, in some instances I found it useful to disable access checks.
     *
     * This is always on by default. In the api system that I built to access the data models, you were not able to
     * modify this property and therefore were always subject to access checks.
     *
     * @var
     */
    private $ignoreAccessChecks;

    /**
     * The lazy way to create a new instance of options.
     *
     * @return ORMCrudOptions
     */
    public static function n() {
        return new ORMCrudOptions();
    }

    /**
     * Set the query value
     *
     * @param $query
     *
     * @return $this
     */
    public function query($query) {
        $this->query = $query;

        return $this;
    }

    /**
     * Add an orderby field and direction
     *
     * @param $field
     * @param string $direction
     *
     * @return $this
     * @internal param array $orderBy
     *
     */
    public function orderBy($field,$direction="asc") {
        $this->orderBy[] = array($field,$direction);

        return $this;
    }

    /**
     * Set the limit start and limit.
     *
     * @param $limitResults
     * @param null $limitStart
     *
     * @return $this
     */
    public function limit($limitResults,$limitStart=null) {
        $this->limit = array($limitResults,$limitStart);

        return $this;
    }

    /**
     * Set the depth for retrieval
     *
     * @param $depth
     *
     * @return $this
     */
    public function depth($depth) {
        $this->depth = $depth;

        return $this;
    }

    /**
     * Set whether to use the model cache
     *
     * @param $useCache
     *
     * @return $this
     */
    public function useCache($useCache) {
        $this->useCache = $useCache;

        return $this;
    }

    /**
     * Set whether to fire webhooks on crud actions
     *
     * @param $fireWebhooks
     *
     * @return $this
     */
    public function fireWebhooks($fireWebhooks) {
        $this->fireWebhooks = $fireWebhooks;

        return $this;
    }

    /**
     * Set whether to ignore access checks
     *
     * @param $ignoreAccessChecks
     *
     * @return $this
     */
    public function ignoreAccessChecks($ignoreAccessChecks) {
        $this->ignoreAccessChecks = $ignoreAccessChecks;

        return $this;
    }
}

这个类背后的想法是消除在 crud 方法中包含大量参数的需要,因为这些参数中的大多数可以在所有 crud 方法中重用。记下查询属性上的 cmets,因为这很重要。


所以,这几乎涵盖了我正在尝试做的事情背后的基本伪代码和想法。所以最后,我将展示一些用户场景:

<?php
//the most simple way to store a user
$user = User::constructEmpty();
//we use auto incrementing on the id value at the database end. So by not specifying the id, we are not updaing, and
//the id will be auto generated. After the push has been made, the system will assign the id for me
$user->name = "bob";
$user->pass = "bobpass";
//the system automatically constructs child objects for you if they are not yet constructed, because
//it knows what type should be constructed. So I don't need to construct the address object, manually!
$user->address->line1 = "awesome drive";
$user->address->zip = "90051";
//save to storage, but don't fire webhooks and ignore access checks. Note that the ORMCrudOptions object
//is passed to child objects too when recursion happens, meaning that the same options are inherited by child objects
$user->pushThis(ORMCrudOptions::n()->fireWebhooks(false)->ignoreAccessChecks(true));
echo $user->id; //this will display the auto generated id
echo $user->address->id; //this will be the audo generated id of the address object.

//next lets update something within the object
$user->name = "bob updated";
//because we know now that the object has an id value, it will update the existing object. Remembering tha the User
//object is persistent!
$user->pushThis(ORMCrudOptions::n()->fireWebhooks(false)->ignoreAccessChecks(true));
echo $user->id; //this will be the exact same id as before
echo $user->address->id; //this will be a NEW ID! Remember, the address object is NOT persistent meaning that a new
//instance was created in order to ensure that is is infact non-persistent. The system does handle cleaning up of loose
//objects although this is one of the main perforance problems

//finding the above object by user->name
$user = User::findOne(ORMCrudOptions::n()->query("this.name = ('bob')"));
if($user) {
    echo $user->name; //provided that a user with name "bob" exsists, this would output "bob"
}

//finding the above user by address->zip
$user = User::findOne(ORMCrudOptions::n()->query("this.address.zip = ('90051')"));
if($user) {
    echo $user->address->zip; //provided that the user with address->zip "90051" exists, this would output "90051"
}

//removing the above user
$user = User::findById(1); //assuming that the id of the user id 1
//add a favourite book to the user
$user->favouriteBook->name = "awesome book!";
//update
$user->pushThis(ORMCrudOptions::n()->ignoreAccessChecks(true));
//remove
$user->removeThis(ORMCrudOptions::n()->ignoreAccessChecks(true));
//with how persistence works, this will delete the user, and the user's address (because the address is non-persistence)
//but will leave the created book un-deleted, because books are persistent and may exist as child objects to other objects

//finally, constructing from document-oriented
$user = User::constructFromArray(array(
    "user" => "bob",
    "pass" => "passbob",
    "address" => array(
        "line1" => "awesome drive",
        "zip" => "90051"
    )
));
//this will only CONSTRUCT the object based on the internal properties defined property types and object types.
//properties that don't exist in the model's defined properties, but exist in the array will be ignored, so having more
//properties in the array than should be there doesn't matter
$user->pushThis(ORMCrudOptions::n()->ignoreAccessChecks(true));

//update only one property of a user object using arrays (this is ultimately how the api system of the ORM was built)
$user = User::constructFromArray(array(
    "id" => 1,
    "user" => "bob updated"
));
echo $user->pass; //this would output passbob, because the pass was not specified in the array, it was pulled form storage

这里不太可能展示,但让这个系统使用起来很愉快的一件事是类文件的生成如何使它们对 IDE 非常友好(特别是对于自动完成)。是的,一些老派开发人员会反对这种新奇的现代技术,但归根结底,当您处理疯狂复杂的面向对象数据结构时,让 IDE 帮助您拼写您的财产正确的名称和正确的结构可以挽救生命!

如果你还在我身边,谢谢你的阅读。不过,您可能想知道,您又想要什么?

简而言之,我在文档/对象存储方面没有大量经验,而且在过去的几天里,我已经被证明有一些技术可以帮助我实现自己的目标试图做。我只是不能 100% 确定我找到了正确的。我是否要创建一个新的 ORM,是否可以有效地从现有的 ORM 中获取此功能,是否使用专用的对象/图形数据库?

我非常欢迎任何和所有的建议!

【问题讨论】:

  • 这是我旧帖子的扩展,这里:stackoverflow.com/questions/28482648/…
  • 请更简洁。或者至少在问题的顶部放置一个要点摘要。
  • 我最初的反应是“持久性”在这里是错误的术语,因为它已经被使用了——它本质上意味着存储一些东西。需要记住它意味着不同的东西可能会有点令人困惑,尤其是在存储系统的上下文中!不过不用担心,我想我可以解决这个问题。我对上一个问题的嵌套设置想法仍然有效,稍后我会添加一些额外的细节。
  • @SverriM.Olsen 我不禁觉得我已经这样做了?您能否详细说明您建议我如何改进这篇文章?非常感谢
  • @halfer - 可以理解。我真的在为 3k 的限制而苦苦挣扎!我必须实际提供链接而不是在内容中包含文件,但是我目前也仅限于每个帖子 2 个链接。我会仔细阅读帖子,看看我是否可以将 xml 文件内容放入其中。再次感谢

标签: php database oop object


【解决方案1】:

感觉这仍然是一种嵌套集算法,因为您的数据将始终适合层次结构。简单类型(字符串、整数等)具有深度为 1 的层次结构,而像 customer.address.postcode (来自您的相关帖子)这样的对象表达式将为每个组件(在本例中为 3,并存储相应的字符串值)具有层次结构在最外层节点)。

这个层次结构似乎可以存储不同的类型,因此您需要对嵌套集算法进行一些小改动。不是每个节点都携带特定于类的(地址、用户等)列,而是有一个对该类型的字符串引用和一个整数主键来引用它。这意味着您不能对数据库的这一部分使用外键约束,但这是一个很小的代价。 (这样做的原因是单个列不能服从多个约束之一,它必须服从所有约束。也就是说,您可能可以使用预插入/预更新触发器做一些聪明的事情)。

因此,如果您要使用 Doctrine 或 Propel NestedSet 行为,您可以这样定义表:

  • 节点
    • [嵌套集列,在 ORM 中为您完成]
    • name(varchar,记录元素名称如customer
    • is_persistent (bool)
    • table_name (varchar)
    • primary_key(整数)
  • 地址
    • (您常用的列,其他任何表格也一样)

现在,这里出现了一个有趣的属性:创建层次结构时,您会看到叶节点中的琐碎值可以通过我们的参考系统共享。事实上,我不完全确定 is_persistent 布尔值是必需的:由于共享外部表行,它是持久的(如果我理解正确的话),如果不是,它是非持久的。

所以,如果customer1.address.postcode 有一个特定的字符串值,你可以让customer2.address.postcode 指向同一个东西。当更新第一个表达式所指向的版本时,第二个将“自动”更新(因为它解析到相同的表行)。

这里的好处是,这将在没有太多工作的情况下固定在 Propel 和 Doctrine 上,而且根本不需要任何核心黑客攻击。您需要做一些工作来将对象/数组转换为层次结构,但这可能不是很多代码。


附录:让我解释一下我对嵌套元素存储的看法。您说您认为您需要在不同地方共享不同级别的层次结构,但我不太确定(目前我认为您需要一些鼓励,不要建立一个过于复杂的系统!)。让我们看一个例子,一个用户有一本喜欢的书。

为了存储它,我们创建了这些层次结构:

user
node level 1
points to user record containing id=1, name=bob, pass=bobpass
    favouriteBook
    node level 2
    points to book record containing id=1, name=awesome book
        author
        node level 3
        points to author record containing id=3, name=peter, pass=peterpass

现在,假设我们有另一个用户想要分享同一作者的另一本最喜欢的书(即我们正在分享user.favouriteBook.author)。

user
node level 1
points to different user record containing id=100, name=halfer, pass=halferpass
    favouriteBook
    node level 2
    points to different book record containing id=101, name=textbook
        author
        node level 3
        points to same author record (id = 3)

如果两个用户分享同一本最喜欢的书呢?没问题(我们另外分享user.favouriteBook):

user
node level 1
points to different user record containing id=101, name=donny, pass=donnypass
    favouriteBook
    node level 2
    points to previous book record (id=1)
        author
        node level 3
        points to previous author record (id = 3)

对这种方法的一个批评是,如果你将user.favouriteBook设为“持久”(即共享),那么它应该自动共享user.favouriteBook.author。这是因为如果两个或更多的人喜欢同一本书,那么他们所有人的作者都是同一作者。

但是,我在 cmets 中指出了为什么我认为我的显式方法更好:替代方法可能是嵌套集的嵌套集,这可能会变得过于复杂,但我认为您尚未向您展示过需要那个。权衡是我的方法需要更多的存储空间,但我认为这很好。您还可以设置更多对象,但如果您有一个单独的工厂,并对其进行可靠的单元测试,我认为您无需担心。

(我认为我的方法也可以更快,但如果不为两者开发原型并在真实数据集上测量性能,就很难说)。


附录 2,清理一些 cmets 讨论并将其保留为问题上下文中的答案。

要确定我在此处概述的建议是否可行,您需要创建一个原型。我建议使用现有的嵌套集解决方案,例如带有 NestedSetBehaviour 的 Propel,尽管 GitHub 会有许多其他库可供您尝试。在这个阶段不要尝试将此原型集成到您自己的 ORM 中,因为集成工作只会分散注意力。目前您想测试这个想法的可行性,仅此而已。

【讨论】:

  • 作为一个快速响应(在我深入研究之前),持久性更多地与对象与父对象的关系有关。因为它要么总是链接到一个父母(非持久),要么链接到无/多(持久)。理解为什么这很有用的最好方法是查看当对象被删除/更新(在持久性规则中定义)时会发生什么。现在,让我了解你的帖子! =]
  • 我不认为您可以在单个查询中执行此操作(或者,如果可以,这样做会太复杂或太慢)。
  • "一个更好的例子是这两个都指向一个地址对象:customer.shippingAddresscustomer.billingAddress"。他们当然可以。如果它们都作为单独的层次结构存在,它们都可以指向同一个表/pk。 (为了帮助其他人,我现在要花几天时间解决这个问题,所以如果你能自己破解一段时间,那就太好了!很高兴再次提供帮助,但可能一天不会几次)。
  • “我认为你的查询字符串字段需要指向一个实际属性而不是一个对象”——你可以这样做,但我看不出它有什么好处。尝试两种方式,看看。我对这种方法的担忧是您的层次结构表中的条目将比它需要的多得多,并且可能会变得太慢。不过,您是对的,总体上需要进行一些设计权衡——也许这就是其中之一。快乐的黑客,现在!
  • (我忘记了上面节点表中的name 组件,将添加它——这对设计很重要!)
【解决方案2】:

我正在从头构建一个 PHP OOP 框架(我别无选择 在这件事上)

你总是有选择的。

框架最需要处理面向对象的数据 可能的有效方式。它不需要闪电般快速,它只是 需要成为问题的最佳解决方案

我个人会选择序列化字符串或 ORM+MySQL (InnoDB)

对象非常类似于类实例,因为它们是 特定类的实例,其中包含一组严格的 属性。

听起来像是...对象的定义。因为对象是类的实例,所以它必须类似于类结构。此外,class' instanceobject 是一回事。所以你有点说Objects ... resemble objects

对象属性可以是基本类型(字符串、数字、布尔值),但也可以 也可以是一个对象实例,或一组对象(使用 限制数组必须是相同类型的对象)

是的,这就是面向对象编程的目的,也是它的强大功能之一。

【讨论】:

  • 感谢您的指点,我会相应地进行编辑。您正确地指出,我指的是 OOP 的一般概念。最终的想法是一个存储引擎,存储严格编写的 OOP 对象。肯定存在完全不同类型的对象,以完全松散和动态的对象形式存在,因此我觉得有必要指定
  • 我最初的目标是使用 mysql 创建一个 ORM。如果你能给出一些你建议我如何最好地使用这个设置的例子,那就太好了,就像我之前做过的那样,并且在性能方面遇到了困难(特别是对于非常复杂的对象)。很高兴知道您为什么指定 InnoDB
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-14
  • 1970-01-01
  • 2012-12-21
  • 2021-01-12
  • 2011-09-14
相关资源
最近更新 更多