Laminas Atom Feed with custom extension

Hello,

I’m trying to build my own Extension like the ones provided in tree.
My goal is to show inside Atom xml events so this is an example I will add more tags:

  • start datetime
  • finish datetime
  • availablity
  • thumbnail and big picture
  • address information

With this code and I’m able to register it:

$extensionManager = \Laminas\Feed\Writer\Writer::getExtensionManager();
$extensionManager->add('MyExtension\Renderer\Entry', \MyExtension\Renderer\Entry::class);
Laminas\Feed\Writer\Writer::registerExtension('MyExtension');

This is my class:

<?php


namespace MyExtension\Renderer;

use DOMDocument;
use DOMElement;
use Laminas\Feed\Writer\Extension;

/**
*/
class Entry extends Extension\AbstractRenderer
{
    /**
     * Set to TRUE if a rendering method actually renders something. This
     * is used to prevent premature appending of a XML namespace declaration
     * until an element which requires it is actually appended.
     *
     * @var bool
     */
    protected $called = false;

    /**
     * Render entry
     *
     * @return void
     */
    public function render()
    {
        if (strtolower($this->getType()) === 'rss') {
            return; // Atom 1.0 only
        }
        $this->_setImageLink($this->dom, $this->base);
        if ($this->called) {
            $this->_appendNamespaces();
        }
    }

    /**
     * Append namespaces to entry root
     *
     * @return void
     */
    // @codingStandardsIgnoreStart
    protected function _appendNamespaces()
    {
        // @codingStandardsIgnoreEnd
        $this->getRootElement()->setAttribute(
            'xmlns:solg',
            'http://example.it/atom/1.0'
        );
    }

    /**
     * Set image link
     *
     * @param  DOMDocument $dom
     * @param  DOMElement $root
     * @return void
     */
    // @codingStandardsIgnoreStart
    protected function _setImageLink(DOMDocument $dom, DOMElement $root)
    {
        // @codingStandardsIgnoreEnd
        $link = $this->getDataContainer()->getImageLink();
        if (! $link) {
            return;
        }
        $clink = $this->dom->createElement('image_link');
//         $clink->setAttribute('rel', 'replies');
//         $clink->setAttribute('type', 'text/html');
        $clink->setAttribute('href', $link);
//         $count = $this->getDataContainer()->getCommentCount();
//         if ($count !== null) {
//             $clink->setAttribute('thr:count', $count);
//         }
        $root->appendChild($clink);
        $this->called = true;
    }

}
?>

Unluckily it breaks on render:

[Fri Mar 06 17:14:45.214753 2020] [php7:notice] [pid 28374] [client 127.0.0.1:36146] PHP Fatal error:  Uncaught Laminas\\Feed\\Writer\\Exception\\BadMethodCallException: Method: getImageLink does not exist and could not be located on a registered Extension in /home/francesco/eclipse-workspace/project/vendor/laminas/laminas-feed/src/Writer/Entry.php:724
Stack trace:
#0 /home/francesco/eclipse-workspace/project/MyExtension/Renderer/Entry.php(70): Laminas\\Feed\\Writer\\Entry->__call('getImageLink', Array)
#1 /home/francesco/eclipse-workspace/project/MyExtension/Renderer/Entry.php(38): MyExtension\\Renderer\\Entry->_setImageLink(Object(DOMDocument), Object(DOMElement))
#2 /home/francesco/eclipse-workspace/project/vendor/laminas/laminas-feed/src/Writer/Renderer/Entry/Atom.php(59): MyExtension\\Renderer\\Entry->render()
#3 /home/francesco/eclipse-workspace/project/vendor/laminas/laminas-feed/src/Writer/Renderer/Feed/Atom.php(90): Laminas\\Feed\\Writer\\Renderer\\Entry\\Atom->render()
#4 /home/francesco/eclipse-workspace/project/vendor/laminas/laminas-feed/src/Writer/Feed.php(237): Laminas\\Feed\\Writer\\Renderer\\Feed\\Atom-> in /home/francesco/eclipse-workspace/project/vendor/laminas/laminas-feed/src/Writer/Entry.php on line 724

I’m not sure how to set the new property and how make it run without breaking on:

$feed->export('atom');

Any suggestion is welcome.
Many thanks
Cheers
Francesco

Hello and welcome to the Laminas forums!

One part is missing in your extension; the feed entry which holds the data of your image link. At the moment you have only created the renderer for your feed.

Some untested code to illustrate the entry class which holds the link:

namespace MyExtension;

use BadMethodCallException;
use Laminas\Stdlib\StringUtils;
use Laminas\Stdlib\StringWrapper\StringWrapperInterface;

class Entry
{
    /** @var array */
    protected $data = [];

    /** @var string */
    protected $encoding = 'UTF-8';

    /** @var StringWrapperInterface */
    protected $stringWrapper;

    public function __construct()
    {
        $this->stringWrapper = StringUtils::getWrapper($this->encoding);
    }

    public function setEncoding($enc)
    {
        $this->stringWrapper = StringUtils::getWrapper($enc);
        $this->encoding      = $enc;

        return $this;
    }

    public function getEncoding()
    {
        return $this->encoding;
    }

    public function setImageLink($value)
    {
        $this->data['imageLink'] = $value;
    }

    public function __call($method, array $params)
    {
        $point = lcfirst(substr($method, 3));
        if (! method_exists($this, 'setImageLink')) {
            throw new BadMethodCallException(
                'invalid method: ' . $method
            );
        }
        if (! array_key_exists($point, $this->data)
            || empty($this->data[$point])
        ) {
            return;
        }

        return $this->data[$point];
    }
}

Then add both to the extension manager and register the extension itself:

$extensionManager = Laminas\Feed\Writer\Writer::getExtensionManager();

// Add entry with image link
$extensionManager->add(
    MyExtension\Entry::class,
    MyExtension\Entry::class
);

// Add renderer for entry with image link 
$extensionManager->add(
    MyExtension\Renderer\Entry::class,
    MyExtension\Renderer\Entry::class
);

// Register extension
Laminas\Feed\Writer\Writer::registerExtension('MyExtension');

Compare also with the “Google Play Podcast” extension:

1 Like

Btw. your new element looks like the image link in “Google Merchant Center” for product feeds.

Thanks for the suggestions I’ll test it tomorrow…
As I stated in my post I’ll need more functions to show events data.

Cheers
Francesco

Please keep in mind that no feed reader understand your extensions and you might get an invalid feed.

Have a look at the RSS 2.0 specification:

RSS 2.0 adds that capability, following a simple rule. A RSS feed may contain elements and attributes not described on this page, only if those elements and attributes are defined in a namespace.

http://www.rssboard.org/rss-specification#extendingRss

An example can be found via W3C feed validator: http://static.userland.com/gems/backend/rssMarkPilgrimExample.xml

I hope this helps to find a correct solution.

Thanks for all the help.
I was thinking to an atom only extension because of this… I’ll provide all mandatory tags and add some extra one to give the ability to callers to implement filters, sort and extra information

I made it… I think you can start putting mine and your code together to update the documentation and close the thread.
Thanks
Francesco

This is already on my list.

You can mark the post that presents the solution for you. (See the buttons under every post.)