Каков наилучший способ хранения данных в свободной форме? - PullRequest
0 голосов
/ 03 апреля 2011

Я использую Zend Framework и MySQL. Пользователи моего приложения будут иметь возможность хранить произвольные объемы данных, структурированные как таблицы. Проблема в том, что я не буду знать «столбцы» заранее. Поэтому я не могу создать готовую таблицу или готовую модель Zend Framework.

В предыдущей версии приложения схема данных была сгенерирована автоматически из данных. Это сработало хорошо. На этот раз проблема в том, что я не знаю, как создать Zend Framework Model для представления произвольного источника данных. Кто-нибудь имеет представление о том, какая стратегия лучше всего подходит для моего случая? И нет, переосмыслить требования к моим приложениям не вариант :).

Кстати: в еще более старой версии приложения данные сохранялись в формате JSON. Этот подход был очень медленным.

Ответы [ 2 ]

5 голосов
/ 03 апреля 2011

Если вы не можете использовать NoSQL, взгляните на модель Entity Attribute Value (EAV). Это позволяет использовать гибкие атрибуты с помощью модели метаданных.

Это может не работать хорошо, если у вас большой объем данных, и может быть немного болезненно поддерживать полностью реляционную схему. Я не рекомендовал бы это в качестве первого варианта, но если вы ДЕЙСТВИТЕЛЬНО не можете знать схему заранее, это один из способов, которым вы могли бы пойти.

http://en.wikipedia.org/wiki/Entity-attribute-value_model

0 голосов
/ 16 апреля 2011

Я чувствовал себя вдохновленным, так как я давал ответ .... Я собрал небольшой класс, который сделает это для вас ...

Таблица

kick_ass_data_t

Схема

CREATE TABLE `kick_ass_data_t` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `key_text` varchar(1024) NOT NULL,
  `value_text` mediumtext,
  `create_date` datetime NOT NULL,
  `lmod_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

Класс

<?php
/**
 * Property Bag
 */

class KickAssProps
{
    /**
     * gets a property from a database bag.
     * If $remove is true, item is deleted after retrieved.
     * @param string $key
     * @param mixed $defaultValue
     * @param bool $remove
     * @return mixed
     */
    public function getProperty( $key, $defaultValue = null, $remove = false )
    {
        $_result = $defaultValue;

        //  Lookup key
        if ( null === ( $_model = $this->_loadProperty( $key ) ) )
        {
            $_model = new KickAssData();
            $_model->key_text = $key;
            $_model->value_text = self::_serialize( $_result );
            $_model->create_date = date( 'Y-m-d H:i:s' );

            if ( ! $_model->save() )
            {
                //  bitch alot...
            }
        }
        else
            $_result = self::_unserialize( $_model->value_text );

        if ( $remove )
        {
            if ( ! $_model->delete() )
            {
                //  bitch alot...
                }
        }

        return $_result;
    }

    /**
     * sets a property in a database bag.
     * @param string $key
     * @param mixed $value
     * @return \KickAssProps $this
     */
    public function setProperty( $key, $value = null )
    {
        //  Lookup key
        if ( null === ( $_model = $this->_loadProperty( $key ) ) )
        {
            $_model = new KickAssData();
            $_model->key_text = $key;
            $_model->create_date = date( 'Y-m-d H:i:s' );
        }

        $_model->value_text = self::_serialize( $value );

        if ( ! $_model->save() )
        {
            //  bitch alot...
        }

        //  Allow chaining...
        return $this;
    }

    /**
     * Retrieves a property from the database bag
     * @param string $key
     * @return KickAssData
     */
    protected function _loadProperty( $key )
    {
        //  Example sql
        //  $_sql = 'select * from kick_ass_data_t where key_text = :key_text';

        //  read row from database however you want... this is how I'd do it in Yii (http://www.yiiframework.com)
        $_model = KickAssData::model()->find(
            'key_text = :key_text',
            array(
                 ':key_text' => $key
            )
        );

        return $_model;
    }

    /**
     * Serializer that can handle SimpleXmlElement objects
     * @param mixed $value
     * @return mixed
     */
    protected static function _serialize( $value )
    {
        try
        {
            if ( $value instanceof SimpleXMLElement )
                return $value->asXML();

            if ( is_object( $value ) )
                return serialize( $value );
        }
        catch ( Exception $_ex )
        {
        }

        return $value;
    }

    /**
     * Unserializer that can handle SimpleXmlElement objects
     * @param mixed $value
     * @return mixed
     */
    protected static function _unserialize( $value )
    {
        try
        {
                if ( self::_isSerialized( $value ) )
            {
                if ( $value instanceof SimpleXMLElement )
                    return simplexml_load_string( $value );

                return @unserialize( $value );
            }
        }
        catch ( Exception $_ex )
        {
        }

        return $value;
    }

    /**
     * Tests if a value needs unserialization by unserializing the value then 
     * re-serializing. If both are successful then it's cool. I know this is 
     * slower but it guarantees data integrity in my database.
     * @param mixed $value
     * @return boolean
     */
    protected static function _isSerialized( $value )
    {
        return !( false === @unserialize( $value ) && $value != @serialize( false ) );
    }
}
...