問題描述

我一直試圖讓這個工作達一年多,並嘗試了許多方法,並沒有得到它的工作。如果有人可以幫助我,我真的很感激。這是我要做的事情

我需要為我的自定義帖子類型為幻燈片 (lom_slideshow) 創建 15 個自定義 metaboxes 用於寫入屏幕。除了滑塊的數量 (例如幻燈片 1,幻燈片 2 …),每個 metabox 將是相同的。我想讓 metaboxes 將每個字段保存到單獨的自定義字段中。

以下是每個 metabox 中需要的字段:

  • 數字/文本字段

    • 標題:幻燈片編號

    • 自定義字段鍵:slide1-number

  • 複選框

    • 標題:隱藏此幻燈片

    • 自定義字段鍵:slide1-hide

  • 無線電切換

    • 標題:幻燈片類型

    • 自定義字段鍵:slide1-slidesetype

    • 值:圖像幻燈片或視頻幻燈片

  • 文本域

    • 標題:幻燈片標題

    • 自定義字段鍵:slide1-title

  • 圖像 (這個可以工作幾種方式,我不太在乎它如何工作,只要它)

    • 標題:幻燈片圖片

    • 自定義字段鍵:slide1-image

    • 它將如何運作:只要它做以下一件事我會很開心…

    • A) 與該帖子相關聯的所有圖像附件的下拉列表。

    • B) 與該帖子相關聯的所有附加圖像的縮略圖列表。

    • C) 圖像上傳字段上傳圖像並將其附加到帖子。

  • 所見即所得

    • 標題:幻燈片説明

    • 自定義字段鍵:slide1-desc

    • 類型:wysiwyg

  • 多行文本

    • 標題:視頻嵌入代碼

    • 自定義字段鍵:slide1-embed

幻燈片 2 將是一樣的,但所有的自定義字段值將附加”slide2-“ 而不是”slide1-“

唯一的其他考慮是,我希望他們在主要內容列,只有在我的幻燈片自定義帖子類型 (lom_slideshow) 上可見。

就像我説的,我已經嘗試了這麼多時間,從來沒有這樣做。我過去已經很近了,並且已經得到了整個事情的工作,除非我看不到所見即所得的多個實例顯示在頁面上。我不再有代碼,因為我從那以後改變了很多次。我最終決定使用更多字段插件來做這個而不是我自己的代碼,並用 5 個元框進行開發測試,但是當我嘗試使用 15 個框來實現它時,我需要插件在我的數據庫表上造成嚴重破壞。我真的不想要另外一個插件。

我實際上正在尋找一個人來告訴我如何編寫代碼。我不需要鏈接到教程,因為我有信心我已經嘗試了所有,除非在最後一個小時左右發佈。

對於這裏的一些背景,我原本想做的是:Help Creating a Slideshow Custom Post Type with Custom Meta Boxes?

EAMann 為我提供了一個非常全面的答案,但這是我的技能之外,我無法實現。我已經簡化了我在這個問題上的請求,希望能夠最終完成這個。

最佳解決方案

解決一個複雜的問題 (理性地)

為瞭解決複雜的問題,有一種標準化/眾所周知的方法:將你的複雜問題分解成一套較小的問題。較小的問題更容易解決。如果您已經解決了每個較小的問題,那麼您最經常已經解決了您的複雜問題。

找到並分割成零件

到目前為止的理論。我們來檢查你的需要。我用自己的話説 line-up 你上面描述的內容:

  • 在多個實例 (Slide-1 到 slide-N) 中處理一組選項。

  • 在後期元值中存儲值。

  • 多個實例中的 Metabox(Slide-1 至 slide-N) 。

  • Metabox 內的幻燈片編輯器 (Form)(再次是多個實例)

  • 編輯數據的基本格式 (例如使用簡單的 HTML 表單元素)

  • 要解決的技術問題,有多個 WYSIWYG 編輯。

  • 您更復雜的圖像輸入。

那麼怎麼編碼呢?我認為最重要的部分是,在開始實際編碼之前,你會想起你真正想要實現的內容,以及如何將問題分成較小的部分。上面的列表可能不完整,這只是我從你的問題中可以看到的。而格式是非常特別的。它或多或少是從你寫的重複,但有點訂單到單點。

所以檢查這些是否是您的需求,並將該列表擴展到您認為合適的所有內容。

完成後,下一步是看看這些需要的東西可以用簡單的單詞和插件的功能列表來表達

  1. 一個插件:SlideshowPlugin 。

  2. 幻燈片由幻燈片組成。

  3. 編輯幻燈片的編輯器。

  4. 存儲和檢索幻燈片數據的地方。

看起來很簡單,對吧?我可能會錯過這裏的東西,所以在繼續之前先仔細檢查一下。正如寫的,開始編寫之前,只需用簡單的措辭和術語來彌補你的想法。甚至不要考慮哪些部分是有問題的,哪些部分不是或如何編寫一些細節,如 HTML Input 元素的命名。我知道如果你已經嘗試了很長時間,從頭開始重新啓動,這是很難的,因為你腦海裏有許多想法再次出現。

拿鉛筆和一些紙。這通常有助於彌補某人的心靈。

你可以看到我沒有在這裏指定 Metabox 或 Custom Post Type 的需要。首先了解您的問題的部分太具體了。 Metabox 或 Custom Post Type 非常具體,可能是如何編寫插件的。所以我暫時停下來,試圖縮短但正確地描述需求。 Metabox 或類似的東西可能在設計中發揮作用。讓我們來看看。

設計你的插件

在你知道你需要/想要實現的之後,你可以想出如何設計這個插件。這可以通過繪製一點圖片來完成。只需從功能列表中識別零件,並將它們相互關聯。你可以看到,實際上不需要做美術;):

不好意思,我希望這可以讀。無論如何,您可以創建自己的圖像,這只是一個例子。設計可以有所不同,所以如果你不會以同樣的方式繪製,那就是正常的。

我喜歡在紙上開始設計步驟,因為它有助於從上面更好地瞭解問題,並且在電腦上的紙張上快得多。

因此,現在您可以將您的列表與您的設計進行比較,並檢查列表中的所有功能是否涵蓋在設計中的部件。看起來好像我的清單和我的形象,所以我繼續,但不要跳過這一步。否則你不知道你是否錯過了一些東西。隨着你開始編寫代碼,改變一些已經被編碼的東西比圖像或列表要困難得多。

分離設計中的問題

現在這個插件變得更具體了。經過一些小的設計,可能是開始編碼的正確時機。由於我們有上面的列表,我可以通過設計瞭解每一個點,並且設計 cross-check,以便我知道這些零件是如何相互關聯的。

因為如果每一個部分都完成,那麼插件就可以準備就緒,不需要立即就整個事情進行整理,這是原來的問題。

我現在做的編碼有點評論風格和一些示例代碼。這是第一個實現方案,代碼未經測試。這只是為了讓我的手變髒,為了你可能有一個例子,可以寫下如何 – 但不是必須的 – 。有時候,我往往太具體了,請記住我。當我編寫代碼時,re-write 在創建時很常見,但是在創建示例代碼時我無法使其顯示。所以記住這一點。如果您看到要做的更簡單,請選擇您的路線。您需要稍後更改和擴展代碼,所以這只是一些示例代碼。

Plugin

只需一個類來處理基本操作,例如註冊鈎子,併為幻燈片提供幻燈片和編輯器的 Metaboxes 。這是一切開始的地方。插件以單個代碼開始。我稱之為 bootstrap:

<?php
/** Plugin Headers ... */

return SlideshowPlugin::bootstrap(); 

class SlideshowPlugin {
    /** @var Slideshow */
    private $slideshow;
    /** @var SlideshowMetaboxes */
    private $metaboxes;
    /** @var SlideshowPlugin */
    static $instance;
    static public function bootstrap() {
        $pluginNeeded = (is_admin() && /* more checks based your needs */ );
        if (!$pluginNeeded) 
            return;
        }
        if (NULL === self::$instance) {
            // only load the plugin code while it's really needed:
            require_once('library/Slideshow.php');
            require_once('library/SlideshowSlide.php');
            require_once('library/Store.php');
            require_once('library/Metaboxes.php');
            require_once('library/Metabox.php');
            require_once('library/Form.php');
            // ...
            self::$instance = new SlideshowPlugin();
        }
        return self::$instance;
    }
    private function addAction($action, $parameters = 0, $priority = 10) {
        $callback = array($this, $action);
        if (!is_callable($callback)) {
            throw new InvalidArgumentExeception(sprintf('Plugin %s does not supports the %s action.', __CLASS__, $action));
        }
        add_action($action, $callback, $parameters, $priority);
    }
    public function __construct() {
        $this->addAction('admin_init');
    }
    /**
     * @return bool
     */
    private function isEditorRequest() {
        // return if or not the request is the editor page
    }
    /**
     * @-wp-hook
     */
    public function admin_init() {
        // register anything based on custom post type and location in the admin.
        // I don't care about the details with CPT right now.
        // It's just that editorAction is called when we're on the slideshow
        // editor page:
        if ($this->isEditorRequest()) {
            $this->editorAction();
        }
    }
    private function getPostID() {
        // ... code goes here to get post id for request 
        return $postID;
    }
    private function getSlideshow() {
        is_null($this->slideshow)
            && ($postID = $this->getPostID())
            && $slideshow = new Slideshow($postID)
            ;
        return $slideshow;
    }
    private function getMetaboxes() {
        is_null($this->metaboxes) 
            && ($slideshow = $this->getSlideshow())
            && $this->metaboxes = new SlideshowMetaboxes($slideshow)
            ;
        return $this->metaboxes;
    }
    private function editorAction() {
        $metaboxes = $this->getMetaboxes();
    }
}

所以這個插件類已經很完整了。我沒有指定如何檢索 postID,但它已經被封裝在它自己的函數中。旁邊,我沒有代碼來檢查這是否是顯示編輯器的正確頁面,但是已經有一些存根代碼。

最後,如果請求是在實際的自定義帖子類型編輯器頁面上,則會調用 editorAction(),在那裏,Metaboxes 已經被收錄。而已。插件現在應該很完整。它有幻燈片和照顧 Metaboxes 。與設計相比,這些是與插件相關的部分。與圖像比較:

  1. 決定是否顯示編輯器。

  2. 插件和幻燈片之間的連接。該插件已經有幻燈片放映。

  3. 連接到 Metaboxes 。該插件已經有了 Metaboxes 。

看起來完整在這方面做的工作。

幻燈片和幻燈片

幻燈片映射為 1:1 映射到一個帖子。所以它需要有郵政編號。幻燈片可以保存數據,所以它基本上是一個數據類型。它存儲您需要的所有值。它是一個複合數據類型,它包含 0 到 N 個幻燈片。另一個幻燈片是另一個數據類型,其中包含每個幻燈片的信息。

幻燈片然後由 metabox 使用,可能是一種形式。

我還選擇實現幻燈片數據的存儲和檢索以及這些數據類型 (設計中標記為 「存儲」) 的框。這是混亂的數據類型和實際的對象。

但是當商店連接到幻燈片和幻燈片只在設計我連接他們彼此。對於未來可能太近了,但是為了首次實施設計,我認為這是一個有效的方法。由於這是第一種方法,因此即使有一些問題,我也相信這個方向是正確的:

class SlideshowSlide {
    private $slideshow;
    private $index;
    public $number, $hide, $type, $title, $image, $wysiwyg, $embed
    public function __construct($slideshow, $index) {
        $this->slideshow = $slideshow;
        $this->index = $index;
    }
    public function getSlideshow() { return $this->slideshow; }
    public function getIndex() { return $this->index; }
}

class Slideshow implements Countable, OuterIterator {
    private $postID;
    private $slides = array();
    private function loadSlidesCount() {
        $postID = $this->postID;
        // implement the loading of the count of slides here
    }
    private function loadSlide($index) {
        $postID = $this->postID;
        // implement the loading of slide data here
        $data = ... ;
        $slide = new SlideshowSlide($this, $index);
        $slide->setData($data); // however this is done.
        return $slide;            
    }
    private function loadSlides() {
        $count = $this->loadSlidesCount();
        $slides = array();
        $index = 0;
        while(($index < $count) && ($slide = $this->loadSlide($index++)))
            FALSE === $slide || $slides[] = $slide
            ;
        $this->slides = $slides;
    }
    public function __construct($postID) {
        $this->postID = $postID;
        $this->loadSlides();
    }
    public function count() {
        return count($this->slides);
    }
    public function getInnerIterator() {
        return new ArrayIterator($this->slides);
    }
    private function touchIndex($index) {
        $index = (int) $index;
        if ($index < 0 || $index >= count($this->slides) {
            throw new InvalidArgumentExpression(sprintf('Invalid index %d.', $index));
        }
        return $index;
    }
    public function getSlide($index) {
        $index = $this->touchIndex($index);
        return $this->slides[$index];
    }
}

幻燈片和幻燈片課程也相當完整,但也缺少實際的代碼。這只是為了展示我的想法,即擁有屬性/方法和一些處理內容以及如何實現數據檢索。

Metabox

Metabox 需要知道它代表哪個幻燈片。所以它需要知道幻燈片和具體幻燈片。幻燈片可以由插件提供,幻燈片可以由索引 (例如 0 … N 表示,其中 N 是幻燈片中的幻燈片數量-1) 。

class Metabox {
    public function __construct(SlideshowSlide $slide) {
    }
}

Metabox 類實際上是擴展插件類。它也可以通過插件類完成一些工作,但是當我想要在插件的上下文中可以顯示幻燈片的同時可以有多個實例時,我選擇了這種方式。

Metabox 現在需要處理請求邏輯:它代表一個實際上以某種方式輸出的 Metabox,但是它也是輸入,因為它需要處理表單輸入。

好的是,它實際上並沒有處理細節,因為表單輸出和輸入都是由表單對象完成的。

所以可能如果我將這個類編碼到最後,我會完全刪除它。我現在不知道目前,它代表了 Metabox 在編輯器頁面上的一個特定幻燈片。

Metaboxes

class Metaboxes
    private $slideshow;
    private $boxes;
    public function __construct(Slideshow $slideshow) {
        $this->slideshow = $slideshow;
        $this->editorAction();
    }
    private function createMetaboxes() {
        $slides = $this->slideshow;
        $boxes = array();
        foreach($slides as $slide) {
            $boxes[] = new Metabox($slide);
        }
        $this->boxes = $boxes;
    }
    private function editorAction() {
        $this->createMetaboxes();
    }

到目前為止,我只寫了一些小代碼。 Metaboxes 課堂是所有 metaboxes 的經理。幻燈片的代表 Metabox 代表幻燈片。

該存根代碼並不多,但實例化一個 Metabox 每個幻燈片。它可以而且必須做更多的到最後。

您可能想在這裏使用 Composite Pattern,因此對 Metaboxes 對象執行操作將對每個 Metabox 執行相同的操作。與實例化相比,它創建了新的 Metaboxes 。所以你以後不需要處理個別的 Metaboxes,只需要與 Metaboxes 對象。只是一個想法

Form

該表格可能是您處理代碼和代碼行最複雜的事情。它需要抽象您的數據以通過瀏覽器進行處理。所以它必須能夠處理多個實例。您可以通過使用 genreal 前綴 (例如”slide”) 將表單元素名稱 (如需要唯一) 前綴,然後是標識符 (幻燈片索引,我將此索引命名為您想要更改數字,例如具有排序鍵),然後是實際值標識符 (例如”number” 或”hide”) 。

我們來看看:一個表單知道它的前綴,幻燈片的數字和它包含的所有字段。這幾乎映射到上面介紹的幻燈片和幻燈片數據類型。有些小 stub-code:

/**
 * SlideForm
 *
 * Draw a Form based on Slide Data and a Form definition. Process it's Input.
 */
class SlideForm {
    /** @var Slide */
    private $slide;
    private $prefix = 'slide';
    public function __construct(Slide $slide) {
       $this->slide = $slide;
    }
    private function inputNamePrefix() {
       $index = $this->slide->getIndex();
       $prefix = $this->prefix;
       return sprintf('%s-%d-', $prefix, $index);
    }
    private function inputName($name) {
       return $this->inputNamePrefix().$name;
    }
    private function printInput(array $element) {
        list($type, $parameters) = $element;
        $function = 'printInput'.$type;
        $callback = array($this, $function)
        call_user_func_array($callback, $parameters);
    }
    private function printInputText($value, $name, $label, $size = 4) {
        $inputName = $this->inputName($name);
        ?>
        <label for="<?php echo $inputName ; ?>">
          <?php echo htmlspecialchars($label); ?>:
        </label>
        <input type="text" 
          name="<?php echo $inputName; ?>"
          size="<?php echo $size; ?>"  
          value="<?php echo htmlspecialchars($value); ?>">
        <?php
    }
    private function printInputCheckbox($value, $name, $label, ... ) { ... }
    private function printInputRadio($value, $name, $label, ... ) { ... }
    private function printInputImage($value, $name, $label, ... ) { ... }
    private function printInputWysiwyg($value, $name, $label, ... ) { ... }
    private function printInputTextarea($value, $name, $label, ... ) { ... }
    // ...
    private function mapSlideValueTo(array $element) {
        $slide = $this->slide;
        list($type, $parameters) = $element;
        list($name) = $parameters;
        $value = $slide->$name;
        array_unshift($parameters, $value);
        $element[1] = $parameters;
        return $element;
    }
    /**
     * @return array form definition
     */
    private function getForm() {
        // Form Definition
        $form = array(
           array(
               'Text', 
               array(
                   'number', 'Number'
               ),
           array(
               'Checkbox', 
               array(
                   'hide', 'Display', 'Hide This Slide'
               ),
           ),
           array(
               'Radio', 
               array(
                   'type', 'Type', array('Image', 'Video')
               ),
           ),
           array(
               'Text', 
               array(
                   'title', 'Title', 16
               ),
           ),
           // ...
        );
        return $form;
    }
    public function printFormHtml() {
        $form = $this->getForm();

        foreach($form as $element) {
            $element = $this->mapSlideValueTo($element);
            $this->printInput($element);
        }
    }
    public function processFormElement($element) {
        list($type, $parameters) = $element;
        list($name) = $parameters;
        $inputName = $this->inputName($name);

        $map = array(
            'Text' => 'String', 
            'Checkbox' => 'Checkbox', 
            'Radio' => 'Radio', 
        );

        // I would need to continue to code there.
        throw new Exception('Continue to code: Process Form Input based on Form Definition');
    }
    public function processForm() {
        $form = $this->getForm();

        foreach($form as $element) {
            $this->processFormElement($element);  // <- this function needs to be coded
        }
    }
    // ...
}

這個課程現在很大,因為它一次處理三件事情:

  1. 表格定義

  2. 表單輸出渲染

  3. 表單輸入處理

將它分解成它代表的三個部分是更明智的。我離開了你。它已經顯示瞭如何將表單功能封裝到較小的任務中,以便更容易地滿足您的需求。例如。如果 Image Input 元素輸出需要調整,則可以輕鬆地進行擴展/拋光。所見即所得您可以稍後更改實現,因為它不會影響您的幻燈片和幻燈片數據類型。

這個背後的原則也被稱為分離問題,那就是我如何開始我的答案:將問題分解成更小的問題。這些分離的問題更容易解決。

我希望這個時刻有所幫助。最後我甚至沒有回到自定義帖子類型。我知道他們必須進入插件,但使用新的設計,很容易找到寫入代碼的地方。

還剩下什麼?

  • 將代碼分割成多個文件。每個文件一個類。您可以輕鬆地將它們合併在一起,但是為了開發,將東西分開,以便您自己解決較小的問題/部分。

  • 測試:您需要自行測試零件的功能。幻燈片是否應該做什麼?很高興你可以使用 Unittests,如果你寫的類,對於 PHP 有 PHPUnit 。這有助於您開發代碼,同時知道它完全正確。這有助於解決每個人的單位中的簡單問題,您可以稍後更容易地更改代碼,因為您可以一直運行測試。

  • 測試#2:當然你需要測試你的插件作為一個整體,所以你知道它正在做你正在尋找的。你能想像如何自動完成這些工作,所以您可以一直以相同的質量重複測試。

參考文獻

注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。