12

多くのコンテンツが含まれている場合は既にバージョン管理を使用していますが、リレーションDataObjectsにバージョン管理を適用できるかどうか疑問に思っていますか?many_many

私が次のものを持っていると仮定します:

class Page extends SiteTree
{
    private static $many_many = array(
        'Images' => 'Image' 
    );
}

次に、ORM がPage_Imagesリレーションを格納するためのテーブルを作成します。バージョン管理された関係を持つためには、より多くのテーブルが必要になります (例: Page_Images_Live)。

バージョン管理された関係を作成するよう ORM に指示する方法はありますか? Page * – * Images上記のリレーションの例を見ると、クラスをバージョン管理するのではなく、リレーションをバージョン管理する必要がありますImage例えば。このようなもの:

Version Stage:
---
    PageA
        Images ( ImageA, ImageB, ImageC )

Version Live:
---
    PageA
        Images ( ImageA, ImageC, ImageD, ImageE )

それは箱から出しても可能ですか?

4

2 に答える 2

5

私はこれを調べるのに多くの時間を費やしましたが、基本的に変更することなくManyManyList(拡張システムを介して必要なフックを公開しないため)、多くの選択肢はありません。

私はデザートファーストのタイプですが、どうすればそれができますか?

この偉業を達成するための私の唯一の提案は、本質的に多対多のブリッジ オブジェクト (つまり、PageImageを結合する別個のエンティティ)$has_manyですが、それでもかなりの変更が必要です。

これは、結合テーブルではなく実際のオブジェクトに対してバージョン管理されたアイテムを保存することによって、実際の関係を破壊することに関する解決策がフォーラムで部分的に議論されています。それはうまくいくでしょうが、私たちはまだそれよりもうまくやれると思います.

私は個人的に、関係のバージョンをそれ自体に結びつけることに傾倒してPageおり、以下の部分的な解決策がこれをカバーしています。への更新としてこれを試してみて、詳細についてはスクロールせずに読むManyManyList.

このようなものが始まりです:

class PageImageVersion extends DataObject
{
    private static $db = array(
        'Version' => 'Int'
    );

    private static $has_one = array(
        'Page' => 'Page',
        'Image' => 'Image'
    );
}

これには双方向の関係が含まれており、さらにバージョン番号が保存されています。必要な適切なフィールドを追加する機能を指定しgetCMSFieldsて、既存の画像に関連付けたり、新しい画像をアップロードしたりできます。実際のバージョン処理部分に比べて比較的簡単なはずなので、これをカバーすることは避けています。

これで、次のhas_manyようPageになります。

private static $has_many = array(
    'Images' => 'PageImageVersion' 
);

私のテストでは、次のようにImageマッチングを追加するための拡張機能も追加しました。$has_many

class ImageExtension extends DataExtension
{
    private static $has_many = array(
        'Pages' => 'PageImageVersion'
    );
}

正直なところ、関係の側にPages 機能を追加する以外にこれが必要かどうかはわかりません. Image私が見る限り、この特定のユースケースではそれほど重要ではありません。

残念ながら、このバージョン管理方法のために、 を呼び出す標準的な方法を使用することはできませんImages。少し工夫する必要があります。このようなもの:

public function getVersionedImages($Version = null)
{
    if ($Version == null)
    {
        $Version = $this->Version;
    }
    else if ($Version < 0)
    {
        $Version = max($this->Version - $Version, 1);
    }

    return $this->Images()->filter(array('Version' => $Version));
}

を呼び出すと、現在のページのバージョンに合わせて設定されたgetVersionedImages()すべての画像が返されます。また、最後のバージョンを介して以前のバージョンを取得したり、任意の位置番号を渡すことでページの特定のバージョンの画像を取得したりVersionすることもサポートしています。getVersionedImages(-1)

OK、ここまでは順調です。ここで、ページの書き込みごとに、この新しいバージョンのページの画像の重複リストを取得していることを確認する必要があります。

onAfterWrite関数 onを使用するとPage、次のことができます。

public function onAfterWrite()
{
    $lastVersionImages = $this->getVersionedImages(-1);
    foreach ($lastVersionImages as $image)
    {
        $duplicate = $image->duplicate(false);
        $duplicate->Version = $this->Version;
        $duplicate->write();
    }
}

家で遊んでいる人にとって、これは、以前のバージョンの復元がこれにどのように影響するかに関して、少し不安になる場所Pageです.

これを で編集するためGridField、いくつかの操作を行う必要があります。まず、コードがAdd New関数を処理できることを確認します。

私の考えはonAfterWriteオブジェクトPageImageVersionです:

public function onAfterWrite()
{
    //Make sure the version is actually saved
    if ($this->Version == 0)
    {
        $this->Version = $this->Page()->Version;
        $this->write();
    }
}

バージョン管理されたアイテムを に表示するにはGridField、次のように設定します。

$gridFieldConfig = GridFieldConfig_RecordEditor::create();
$gridField = new GridField("Images", "Images", $this->getVersionedImages(), $gridFieldConfig);
$fields->addFieldToTab("Root.Images", $gridField);

GridFieldビアから直接画像にリンクしたいかもしれませんが、GridFieldConfig_RelationEditorこれは事態が悪化するときです。

野菜の時間…

大きな困難の 1 つは、GridFieldこれらのエンティティのリンクとリンク解除の両方です。標準を使用するGridFieldDeleteActionと、適切なバージョンなしで関係が直接更新されます。

オブジェクトを書き込んで(別のバージョンをトリガーするために) を拡張GridFieldDeleteActionおよびオーバーライドし、バージョン管理されたイメージ オブジェクトのすべてのバージョンを最後のバージョンに複製し、新しいバージョンで不要なものをスキップする必要があります。handleActionPage

認めますが、この最後のビットは私の当て推量です。私の理解とデバッグから、それはうまくいくはずですが、それを正しくするために多くの手間がかかります。

の拡張子をGridFieldDeleteAction特定の に追加する必要がありますGridField

これは基本的に、このソリューションを機能させるための最後のステップです。追加、削除、複製、バージョン更新の部分がダウンしたら、実際に使用getVersionedImages()して適切なイメージを取得するだけです。

結論

避ける。あなたがこれをやりたい理由はわかりますmany_manyが、Silverstripe で関係を処理する方法を適切なサイズで更新しないと、これを処理できるクリーンな方法が本当にわかりません。


しかし、私は本当にそれが欲しいですManyManyList

必要と思われる変更はManyManyList、3 ウェイ キー (外部キー、ローカル キー、バージョン キー) と、追加/削除/取得などのさまざまな方法の更新です。

addand関数にフックがあれば、remove(Silverstripe の拡張システムを介して) 拡張機能として機能を忍び込ませ、必要なデータをmany_many関係が許可する余分なフィールドに追加できる可能性があります。

直接拡張してから を介して強制的にカスタム クラスに置き換えることでこれを実現できますが、それはさらに厄介な解決策になります。ManyManyListManyManyListObject::useCustomClass

この段階で純粋なソリューションに対して完全な答えを出すには、あまりにも長すぎて複雑すぎますManyManyList(ただし、後でこれに戻って試してみるかもしれません)。


免責事項: 私は Silverstripe Core 開発者ではありません。この問題全体に対するより適切な解決策があるかもしれませんが、その方法がわかりません。

于 2015-04-19T07:38:54.540 に答える
1

「_Live」サフィックスを使用して 2 番目のリレーションを定義し、ページが公開されたときにそれを更新できます。注: このソリューションには、2 つのバージョン (ライブとステージ) のみが保存されます。

以下は、多対多の関係がバージョン管理されているかどうかを自動的に検出する私の実装です。次に、公開とデータの取得を処理します。必要なのは、"_Live" 接尾辞を付けた余分な多対多の関係を 1 つ定義することだけです。

$page->Images() は、現在のステージ (ステージ/ライブ) に従ってアイテムを返します。

class Page extends SiteTree
{
    private static $many_many = array(
        'Images' => 'Image',
        'Images_Live' => 'Image'
    );

    public function publish($fromStage, $toStage, $createNewVersion = false)
    {
        if ($toStage == 'Live')
        {
            $this->publishManyToManyComponents();
        }

        parent::publish($fromStage, $toStage, $createNewVersion);
    }

    protected function publishManyToManyComponents()
    {
        foreach (static::getVersionedManyManyComponentNames() as $component_name)
        {
            $this->publishManyToManyComponent($component_name);
        }
    }

    protected function publishManyToManyComponent($component_name)
    {
        $stage = $this->getManyManyComponents($component_name);
        $live = $this->getManyManyComponents("{$component_name}_Live");

        $live_table = $live->getJoinTable();
        $live_fk = $live->getForeignKey();
        $live_lk = $live->getLocalKey();

        $stage_table = $stage->getJoinTable();
        $stage_fk = $live->getForeignKey();
        $stage_lk = $live->getLocalKey();

        // update or add items from stage to live
        foreach ($stage as $item)
        {
            $live->add($item, $stage->getExtraData(null, $item->ID));
        }

        // delete remaining items from live table
        DB::query("DELETE l FROM $live_table AS l LEFT JOIN $stage_table AS s ON l.$live_fk = s.$stage_fk AND l.$live_lk = s.$stage_lk WHERE s.ID IS NULL");

        // update new items IDs in live table (IDs are incremental so the new records can only have higher IDs than items in ID => should not cause duplicate IDs)
        DB::query("UPDATE $live_table AS l INNER JOIN $stage_table AS s ON l.$live_fk = s.$stage_fk AND l.$live_lk = s.$stage_lk SET l.ID = s.ID WHERE l.ID != s.ID;");
    }

    public function manyManyComponent($component_name)
    {
        if (Versioned::current_stage() == 'Live' && static::isVersionedManyManyComponent($component_name))
        {
            return parent::manyManyComponent("{$component_name}_Live");
        }
        else
        {
            return parent::manyManyComponent($component_name);
        }
    }

    protected static function isVersionedManyManyComponent($component_name)
    {
        $many_many_components = (array) Config::inst()->get(static::class, 'many_many', Config::INHERITED);
        return isset($many_many_components[$component_name]) && isset($many_many_components["{$component_name}_Live"]);
    }

    protected static function getVersionedManyManyComponentNames()
    {
        $many_many_components = (array) Config::inst()->get(static::class, 'many_many', Config::INHERITED);

        foreach ($many_many_components as $component_name => $dummy)
        {
            $is_live = 0;

            $stage_component_name = preg_replace('/_Live$/', '', $component_name, -1, $is_live);

            if ($is_live > 0 && isset($many_many_components[$stage_component_name]))
            {
                yield $stage_component_name;
            }
        }
    }
}
于 2016-11-23T15:09:46.877 に答える