[COMPONENT][Collection] Refactoring: Further work in abstracting collections

This commit is contained in:
Diogo Peralta Cordeiro 2022-01-02 20:04:52 +00:00
förälder def5f36c25
incheckning 5fa8056899
Signerad av: diogo
GPG-nyckel ID: 18D2D35001FBFAB0
38 ändrade filer med 119 tillägg och 77 borttagningar

Visa fil

@ -0,0 +1,11 @@
<?php
declare(strict_types = 1);
namespace Component\Collection;
use App\Core\Modules\Component;
class Collection extends Component
{
}

Visa fil

@ -30,15 +30,14 @@ declare(strict_types = 1);
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace App\Core\Controller;
namespace Component\Collection\Util;
use App\Core\DB\DB;
use function App\Core\I18n\_m;
use App\Core\Router\Router;
use App\Util\Exception\ClientException;
use Component\Feed\Util\FeedController;
abstract class ActorController extends FeedController
trait ActorControllerTrait
{
/**
* Generic function that handles getting a representation for an actor from id

Visa fil

@ -0,0 +1,9 @@
<?php
declare(strict_types = 1);
namespace Component\Collection\Util\Controller;
class CircleController extends OrderedCollection
{
}

Visa fil

@ -0,0 +1,11 @@
<?php
declare(strict_types = 1);
namespace Component\Collection\Util\Controller;
use App\Core\Controller;
class Collection extends Controller
{
}

Visa fil

@ -30,17 +30,15 @@ declare(strict_types = 1);
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\Feed\Util;
namespace Component\Collection\Util\Controller;
use App\Core\Controller;
use App\Core\Event;
use App\Entity\Actor;
use App\Entity\Note;
use App\Util\Common;
use Functional as F;
use function App\Core\I18n\_m;
abstract class FeedController extends Controller
abstract class FeedController extends OrderedCollection
{
/**
* Post-processing of the result of a feed controller, to remove any

Visa fil

@ -29,20 +29,19 @@ declare(strict_types = 1);
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace App\Core\Controller;
namespace Component\Collection\Util\Controller;
use App\Core\DB\DB;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Entity\LocalUser;
use App\Util\Common;
use App\Util\Exception\RedirectException;
use Component\Feed\Util\FeedController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use function App\Core\I18n\_m;
abstract class CollectionController extends FeedController
abstract class MetaCollectionController extends FeedController
{
protected string $slug = 'collectionsEntry';
protected string $plural_slug = 'collectionsList';
@ -50,19 +49,19 @@ abstract class CollectionController extends FeedController
abstract public function getCollectionUrl(int $owner_id, string $owner_nickname, int $collection_id): string;
abstract public function getCollectionItems(int $owner_id, $collection_id): array;
abstract public function getCollectionsBy(int $owner_id): array;
abstract public function getCollectionsByActorId(int $owner_id): array;
abstract public function getCollectionBy(int $owner_id, int $collection_id);
abstract public function createCollection(int $owner_id, string $name);
public function collectionsListViewByActorNickname(Request $request, string $nickname): array
public function collectionsViewByActorNickname(Request $request, string $nickname): array
{
$user = DB::findOneBy(LocalUser::class, ['nickname' => $nickname]);
return self::collectionsListView($request, $user->getId(), $nickname);
return self::collectionsView($request, $user->getId(), $nickname);
}
public function collectionsListViewByActorId(Request $request, int $id): array
public function collectionsViewByActorId(Request $request, int $id): array
{
return self::collectionsListView($request, $id, null);
return self::collectionsView($request, $id, null);
}
/**
@ -73,12 +72,12 @@ abstract class CollectionController extends FeedController
*
* @return array twig template options
*/
public function collectionsListView(Request $request, int $id, ?string $nickname): array
public function collectionsView(Request $request, int $id, ?string $nickname): array
{
$collections = $this->getCollectionsBy($id);
$collections = $this->getCollectionsByActorId($id);
$create_title = _m('Create a ' . mb_strtolower(preg_replace( '/([a-z0-9])([A-Z])/', "$1 $2", $this->slug)));
$collections_title = _m('Your ' . mb_strtolower(preg_replace( '/([a-z0-9])([A-Z])/', "$1 $2", $this->plural_slug)));
$create_title = _m('Create a ' . mb_strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1 $2', $this->slug)));
$collections_title = _m('Your ' . mb_strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1 $2', $this->plural_slug)));
// create collection form
$create = null;
if (Common::user()?->getId() === $id) {
@ -191,7 +190,7 @@ abstract class CollectionController extends FeedController
};
return [
'_template' => 'collections/collection_list_view.html.twig',
'_template' => 'collection/meta_collections.html.twig',
'page_title' => $this->page_title,
'list_title' => $collections_title,
'add_collection' => $create?->createView(),

Visa fil

@ -0,0 +1,9 @@
<?php
declare(strict_types = 1);
namespace Component\Collection\Util\Controller;
class OrderedCollection extends Collection
{
}

Visa fil

@ -29,12 +29,13 @@ declare(strict_types = 1);
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace App\Core\Modules;
namespace Component\Collection\Util;
use App\Core\DB\DB;
use App\Core\Event;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Core\Modules\Plugin;
use App\Entity\Actor;
use App\Util\Common;
use App\Util\Exception\RedirectException;
@ -44,7 +45,7 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
abstract class Collection extends Plugin
abstract class MetaCollectionPlugin extends Plugin
{
protected string $slug = 'collection';
protected string $plural_slug = 'collections';
@ -140,7 +141,7 @@ abstract class Collection extends Plugin
}
$res[] = Formatting::twigRenderFile(
'collections/widget.html.twig',
'collection/widget_add_to.html.twig',
[
'ctitle' => _m('Add to ' . $this->plural_slug),
'user' => $user,

Visa fil

@ -1,4 +1,4 @@
{% extends '/feed/feed.html.twig' %}
{% extends '/collection/notes.html.twig' %}
{% block title %}{{ page_title | trans }}{% endblock %}

Visa fil

@ -29,12 +29,12 @@ namespace Component\Conversation\Controller;
use App\Core\DB\DB;
use App\Core\Form;
use Component\Collection\Util\Controller\FeedController;
use function App\Core\I18n\_m;
use App\Util\Common;
use App\Util\Exception\RedirectException;
use Component\Conversation\Entity\ConversationBlock;
use Component\Feed\Feed;
use Component\Feed\Util\FeedController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
@ -50,7 +50,7 @@ class Conversation extends FeedController
$data = Feed::query(query: "note-conversation:{$conversation_id}", page: $this->int('p') ?? 1);
$notes = $data['notes'];
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'notes' => $notes,
'should_format' => false,
'page_title' => _m('Conversation'),

Visa fil

@ -32,8 +32,8 @@ use App\Util\Exception\ClientException;
use App\Util\Exception\NoLoggedInUser;
use App\Util\Exception\NoSuchNoteException;
use App\Util\Exception\ServerException;
use Component\Collection\Util\Controller\FeedController;
use Component\Feed\Feed;
use Component\Feed\Util\FeedController;
use Symfony\Component\HttpFoundation\Request;
use function App\Core\I18n\_m;
@ -62,7 +62,7 @@ class Reply extends FeedController
$data = Feed::query(query: "note-conversation:{$conversation_id}", page: $this->int('p') ?? 1);
$notes = $data['notes'];
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'notes' => $notes,
'should_format' => false,
'page_title' => _m('Conversation'),

Visa fil

@ -37,8 +37,8 @@ namespace Component\Feed\Controller;
use function App\Core\I18n\_m;
use App\Util\Common;
use Component\Collection\Util\Controller\FeedController;
use Component\Feed\Feed;
use Component\Feed\Util\FeedController;
use Symfony\Component\HttpFoundation\Request;
class Feeds extends FeedController
@ -54,7 +54,7 @@ class Feeds extends FeedController
language: Common::actor()?->getTopLanguage()?->getLocale(),
);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'page_title' => _m(\is_null(Common::user()) ? 'Feed' : 'Planet'),
'notes' => $data['notes'],
];
@ -74,7 +74,7 @@ class Feeds extends FeedController
actor: $actor,
);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Home'),
'notes' => $data['notes'],
];

Visa fil

@ -37,8 +37,8 @@ namespace Component\FreeNetwork\Controller;
use App\Core\DB\DB;
use function App\Core\I18n\_m;
use App\Util\Common;
use Component\Collection\Util\Controller\FeedController;
use Component\Feed\Feed;
use Component\Feed\Util\FeedController;
use Symfony\Component\HttpFoundation\Request;
class Feeds extends FeedController
@ -58,7 +58,7 @@ class Feeds extends FeedController
language: Common::actor()?->getTopLanguage()?->getLocale(),
);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Meteorites'),
'should_format' => true,
'notes' => $data['notes'],
@ -86,7 +86,7 @@ class Feeds extends FeedController
EOF,
);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Planetary System'),
'should_format' => true,
'notes' => $notes,
@ -107,7 +107,7 @@ class Feeds extends FeedController
language: Common::actor()?->getTopLanguage()?->getLocale(),
);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Galaxy'),
'should_format' => true,
'notes' => $data['notes'],

Visa fil

@ -24,7 +24,6 @@ declare(strict_types = 1);
namespace Component\Group\Controller;
use App\Core\Cache;
use App\Core\Controller\ActorController;
use App\Core\DB\DB;
use App\Core\Form;
use function App\Core\I18n\_m;
@ -36,13 +35,16 @@ use App\Util\Exception\ClientException;
use App\Util\Exception\RedirectException;
use App\Util\Form\ActorForms;
use App\Util\Nickname;
use Component\Collection\Util\ActorControllerTrait;
use Component\Collection\Util\Controller\FeedController;
use Component\Group\Entity\GroupMember;
use Component\Group\Entity\LocalGroup;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
class Group extends ActorController
class Group extends FeedController
{
use ActorControllerTrait;
public function groupViewId(Request $request, int $id)
{
return $this->handleActorById(

Visa fil

@ -94,7 +94,7 @@ class Group extends Component
$group = $this->getGroupFromContext($request);
if (!\is_null($group)) {
$nick = '!' . $group->getNickname();
$targets[$nick] = $nick;
$targets[$nick] = $group->getId();
}
return Event::next;
}

Visa fil

@ -33,7 +33,7 @@ use App\Entity\Feed;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\RedirectException;
use Component\Feed\Util\FeedController;
use Component\Collection\Util\Controller\FeedController;
use Functional as F;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

Visa fil

@ -57,7 +57,7 @@ class Feed extends Controller
)
EOF, ['id' => $user->getId()]);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Notifications'),
'should_format' => true,
'notes' => $notes,

Visa fil

@ -30,8 +30,8 @@ use App\Util\Exception\BugFoundException;
use App\Util\Exception\RedirectException;
use App\Util\Form\FormFields;
use App\Util\Formatting;
use Component\Collection\Util\Controller\FeedController;
use Component\Feed\Feed;
use Component\Feed\Util\FeedController;
use Component\Search as Comp;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

Visa fil

@ -1,4 +1,4 @@
{% extends 'feed/feed.html.twig' %}
{% extends 'collection/notes.html.twig' %}
{% block body %}

Visa fil

@ -165,7 +165,7 @@ class Inbox extends Controller
$ap_actor->getActorId(),
Discovery::normalize($actor->getNickname() . '@' . parse_url($ap_actor->getInboxUri(), PHP_URL_HOST)),
);
Event::handle('NewNotification', [$actor, $ap_act->getActivity(), [], _m('{nickname} mentioned you.', ['nickname' => $actor->getNickname()])]);
Event::handle('NewNotification', [$actor, $ap_act->getActivity(), [], _m('{nickname} mentioned you.', ['{nickname}' => $actor->getNickname()])]);
DB::flush();
dd($ap_act, $act = $ap_act->getActivity(), $act->getActor(), $act->getObject());

Visa fil

@ -34,18 +34,18 @@ namespace Plugin\ActorCircles;
use App\Core\DB\DB;
use App\Core\Event;
use function App\Core\I18n\_m;
use App\Core\Modules\Collection;
use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Actor;
use App\Entity\Feed;
use App\Entity\LocalUser;
use App\Util\Nickname;
use Component\Collection\Util\MetaCollectionPlugin;
use Plugin\ActorCircles\Controller as C;
use Plugin\ActorCircles\Entity as E;
use Symfony\Component\HttpFoundation\Request;
class ActorCircles extends Collection
class ActorCircles extends MetaCollectionPlugin
{
protected string $slug = 'circle';
protected string $plural_slug = 'circles';
@ -138,23 +138,23 @@ class ActorCircles extends Collection
$r->connect(
id: 'actor_circles_view_by_actor_id',
uri_path: '/actor/{id<\d+>}/circles',
target: [C\Controller::class, 'collectionsViewByActorId'],
target: [C\Circles::class, 'collectionsViewByActorId'],
);
$r->connect(
id: 'actor_circles_view_by_nickname',
uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/circles',
target: [C\Controller::class, 'collectionsByActorNickname'],
target: [C\Circles::class, 'collectionsViewByActorNickname'],
);
// View notes from a circle by actor id and nickname
$r->connect(
id: 'actor_circles_notes_view_by_actor_id',
uri_path: '/actor/{id<\d+>}/circles/{cid<\d+>}',
target: [C\Controller::class, 'collectionNotesViewByActorId'],
target: [C\Circles::class, 'collectionsEntryViewNotesByActorId'],
);
$r->connect(
id: 'actor_circles_notes_view_by_nickname',
uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/circles/{cid<\d+>}',
target: [C\Controller::class, 'collectionNotesByNickname'],
target: [C\Circles::class, 'collectionsEntryViewNotesByNickname'],
);
return Event::next;
}

Visa fil

@ -23,12 +23,12 @@ declare(strict_types = 1);
namespace Plugin\ActorCircles\Controller;
use App\Core\Controller\CollectionController;
use App\Core\DB\DB;
use App\Core\Router\Router;
use Component\Collection\Util\Controller\MetaCollectionController;
use Plugin\ActorCircles\Entity\ActorCircles;
class Controller extends CollectionController
class Circles extends MetaCollectionController
{
protected string $slug = 'circle';
protected string $plural_slug = 'circles';
@ -69,16 +69,16 @@ class Controller extends CollectionController
['circle_id' => $collection_id],
);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'notes' => array_values($notes),
];
}
public function getCollectionsBy(int $owner_id): array
public function getCollectionsByActorId(int $owner_id): array
{
return DB::findBy(ActorCircles::class, ['actor_id' => $owner_id], order_by: ['id' => 'desc']);
}
public function getCollectionBy(int $owner_id, int $collection_id): ActorCircles
{
return DB::findOneBy(ActorCircles::class, ['id' => $collection_id]);
return DB::findOneBy(ActorCircles::class, ['id' => $collection_id, 'actor_id' => $owner_id]);
}
}

Visa fil

@ -33,20 +33,20 @@ namespace Plugin\AttachmentCollections;
use App\Core\DB\DB;
use App\Core\Event;
use App\Core\Modules\Collection;
use function App\Core\I18n\_m;
use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Actor;
use App\Entity\Feed;
use App\Entity\LocalUser;
use App\Util\Nickname;
use Component\Collection\Util\MetaCollectionPlugin;
use Plugin\AttachmentCollections\Controller\AttachmentCollections as AttachmentCollectionsController;
use Plugin\AttachmentCollections\Entity\AttachmentCollection;
use Plugin\AttachmentCollections\Entity\AttachmentCollectionEntry;
use Symfony\Component\HttpFoundation\Request;
use function App\Core\I18n\_m;
class AttachmentCollections extends Collection
class AttachmentCollections extends MetaCollectionPlugin
{
protected function createCollection(Actor $owner, array $vars, string $name)
{
@ -128,12 +128,12 @@ class AttachmentCollections extends Collection
$r->connect(
id: 'collections_view_by_actor_id',
uri_path: '/actor/{id<\d+>}/collections',
target: [AttachmentCollectionsController::class, 'collectionsListViewByActorId'],
target: [AttachmentCollectionsController::class, 'collectionsViewByActorId'],
);
$r->connect(
id: 'collections_view_by_nickname',
uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/collections',
target: [AttachmentCollectionsController::class, 'collectionsListViewByActorNickname'],
target: [AttachmentCollectionsController::class, 'collectionsViewByActorNickname'],
);
// View notes from a collection by actor id and nickname
$r->connect(

Visa fil

@ -23,12 +23,12 @@ declare(strict_types = 1);
namespace Plugin\AttachmentCollections\Controller;
use App\Core\Controller\CollectionController;
use App\Core\DB\DB;
use App\Core\Router\Router;
use Component\Collection\Util\Controller\MetaCollectionController;
use Plugin\AttachmentCollections\Entity\AttachmentCollection;
class AttachmentCollections extends CollectionController
class AttachmentCollections extends MetaCollectionController
{
public function createCollection(int $owner_id, string $name)
{
@ -69,7 +69,7 @@ class AttachmentCollections extends CollectionController
'bare_notes' => array_values($notes),
];
}
public function getCollectionsBy(int $owner_id): array
public function getCollectionsByActorId(int $owner_id): array
{
return DB::findBy(AttachmentCollection::class, ['actor_id' => $owner_id], order_by: ['id' => 'desc']);
}

Visa fil

@ -1,4 +1,4 @@
{% extends 'collections/collection_entry_view.html.twig' %}
{% extends 'collection/collection_entry_view.html.twig' %}
{% block collection_items %}
{% for key, attachment in attachments %}

Visa fil

@ -28,7 +28,7 @@ use function App\Core\I18n\_m;
use App\Entity\Actor;
use App\Util\Exception\BugFoundException;
use App\Util\Exception\ClientException;
use Component\Feed\Util\FeedController;
use Component\Collection\Util\Controller\CircleController;
use Symfony\Component\HttpFoundation\Request;
class Directory extends FeedController
@ -144,7 +144,7 @@ class Directory extends FeedController
}
return [
'_template' => 'directory/actors.html.twig',
'_template' => 'collection/actors.html.twig',
'actors' => $query_fn($actor_type),
'title' => $title,
'empty_message' => $empty_message,

Visa fil

@ -35,7 +35,7 @@ use App\Util\Exception\NoLoggedInUser;
use App\Util\Exception\NoSuchNoteException;
use App\Util\Exception\RedirectException;
use App\Util\Exception\ServerException;
use Component\Feed\Util\FeedController;
use Component\Collection\Util\Controller\FeedController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
@ -174,7 +174,7 @@ class Favourite extends FeedController
);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'page_title' => 'Favourites feed.',
'notes' => $notes,
];
@ -207,7 +207,7 @@ class Favourite extends FeedController
);
return [
'_template' => 'feed/feed.html.twig',
'_template' => 'collection/notes.html.twig',
'page_title' => 'Reverse favourites feed.',
'notes' => $notes,
];

Visa fil

@ -43,6 +43,7 @@ use App\Util\Functional as GSF;
use Functional as F;
use Symfony\Component\HttpFoundation\Request;
// TODO: Migrate this to query filters
class NoteTypeFeedFilter extends Plugin
{
public const ALLOWED_TYPES = ['media', 'link', 'text', 'tag'];

Visa fil

@ -23,12 +23,14 @@ declare(strict_types = 1);
namespace App\Controller;
use App\Core\Controller\ActorController;
use App\Entity as E;
use Component\Collection\Util\ActorControllerTrait;
use Component\Collection\Util\Controller\FeedController;
use Symfony\Component\HttpFoundation\Request;
class Actor extends ActorController
class ActorFeed extends FeedController
{
use ActorControllerTrait;
public function actorViewId(Request $request, int $id)
{
return $this->handleActorById(

Visa fil

@ -163,7 +163,7 @@ class Security extends Controller
$actor,
$user,
function (int $id) use ($user) {
// Self subscription
// Self subscription for the Home feed and alike
DB::persist(Subscription::create(['subscriber_id' => $id, 'subscribed_id' => $id]));
Feed::createDefaultFeeds($id, $user);
},

Visa fil

@ -39,7 +39,7 @@ use App\Util\Exception\BugFoundException;
use App\Util\Exception\ClientException;
use App\Util\Exception\RedirectException;
use App\Util\Exception\ServerException;
use Component\Feed\Util\FeedController;
use Component\Collection\Util\Controller\FeedController;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

Visa fil

@ -45,7 +45,7 @@ abstract class Actor
public static function load(RouteLoader $r): void
{
$r->connect(id: 'actor_view_id', uri_path: '/actor/{id<\d+>}', target: [C\Actor::class, 'actorViewId']);
$r->connect(id: 'actor_view_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\Actor::class, 'actorViewNickname'], options: ['is_system_path' => false]);
$r->connect(id: 'actor_view_id', uri_path: '/actor/{id<\d+>}', target: [C\ActorFeed::class, 'actorViewId']);
$r->connect(id: 'actor_view_nickname', uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\ActorFeed::class, 'actorViewNickname'], options: ['is_system_path' => false]);
}
}

Visa fil

@ -1,4 +1,4 @@
{% extends 'feed/feed.html.twig' %}
{% extends 'collection/notes.html.twig' %}
{% block title %}{% trans %}%nickname%'s profile{% endtrans %}{% endblock %}

Visa fil

@ -78,7 +78,7 @@ class FeedsTest extends GNUsocialTestCase
static::assertThrows(ClientException::class, fn () => $feeds->home($req));
}
$result = $feeds->{$route}($req, ...$extra_args);
static::assertSame($result['_template'], 'feed/feed.html.twig');
static::assertSame($result['_template'], 'collection/notes.html.twig');
foreach ($result['notes'] as $n) {
static::assertIsArray($n['replies']);
}