From d9aada4d6e4e2a68423f4dc47b237630b3c8611b Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Tue, 24 Dec 2024 09:18:41 -0500 Subject: [PATCH 1/3] getCollectionClass method and config support --- src/Discord/Discord.php | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/Discord/Discord.php b/src/Discord/Discord.php index 30782df04..7a6360cbc 100644 --- a/src/Discord/Discord.php +++ b/src/Discord/Discord.php @@ -15,6 +15,8 @@ use Discord\Factory\Factory; use Discord\Helpers\BigInt; use Discord\Helpers\CacheConfig; +use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Helpers\RegisteredCommand; use Discord\Http\Drivers\React; use Discord\Http\Endpoint; @@ -331,6 +333,13 @@ class Discord */ protected $cacheConfig; + /** + * The collection class. + * + * @var string + */ + protected $collectionClass; + /** * The Client class. * @@ -375,6 +384,10 @@ public function __construct(array $options = []) if ($cacheConfig = $this->getCacheConfig()) { $this->logger->warning('Attached experimental CacheInterface: '.get_class($cacheConfig->interface)); } + $this->collectionClass = $options['collection']; + if ($this->collectionClass !== Collection::class) { + $this->logger->warning("Attached experimental CollectionClass: {$this->collectionClass}"); + } $connector = new SocketConnector($options['socket_options'], $this->loop); $this->wsFactory = new Connector($this->loop, $connector); @@ -1409,6 +1422,7 @@ protected function resolveOptions(array $options = []): array 'socket_options', 'dnsConfig', 'cache', + 'collection', ]) ->setDefaults([ 'logger' => null, @@ -1419,6 +1433,7 @@ protected function resolveOptions(array $options = []): array 'intents' => Intents::getDefaultIntents(), 'socket_options' => [], 'cache' => [AbstractRepository::class => null], // use LegacyCacheWrapper + 'collection' => [Collection::class => null], ]) ->setAllowedTypes('token', 'string') ->setAllowedTypes('logger', ['null', LoggerInterface::class]) @@ -1431,17 +1446,9 @@ protected function resolveOptions(array $options = []): array ->setAllowedTypes('socket_options', 'array') ->setAllowedTypes('dnsConfig', ['string', \React\Dns\Config\Config::class]) ->setAllowedTypes('cache', ['array', CacheConfig::class, \React\Cache\CacheInterface::class, \Psr\SimpleCache\CacheInterface::class]) - ->setNormalizer('cache', function ($options, $value) { - if (! is_array($value)) { - if (! ($value instanceof CacheConfig)) { - $value = new CacheConfig($value); - } - - return [AbstractRepository::class => $value]; - } - - return $value; - }); + ->setNormalizer('cache', fn($options, $value) => is_array($value) ? $value : [AbstractRepository::class => $value instanceof CacheConfig ? $value : new CacheConfig($value)]) + ->setAllowedTypes('collection', ['string', 'null']) + ->setAllowedValues('collection', fn($value) => $value === null || (class_exists($value) && isset(class_implements($value)[CollectionInterface::class]))); $options = $resolver->resolve($options); @@ -1615,6 +1622,18 @@ public function getCacheConfig($repository_class = AbstractRepository::class) return $this->cacheConfig[$repository_class]; } + /** + * Gets the collection configuration. + * + * @param string $collection_class Collection class name. + * + * @return string The class name of the collection, which implements CollectionInterface + */ + public function getCollectionClass() + { + return $this->collectionClass; + } + /** * Handles dynamic get calls to the client. * From 51678a61440b15c44fea825c2fa15522cfbcfc6a Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Tue, 24 Dec 2024 09:42:03 -0500 Subject: [PATCH 2/3] Implement getCollectionClass --- src/Discord/Builders/CommandAttributes.php | 2 +- .../Builders/Components/SelectMenu.php | 4 +- src/Discord/Helpers/RegisteredCommand.php | 2 +- src/Discord/Parts/Channel/Channel.php | 31 ++--- src/Discord/Parts/Channel/Message.php | 117 +++++++++--------- src/Discord/Parts/Channel/Poll.php | 2 +- src/Discord/Parts/Channel/Poll/PollAnswer.php | 6 +- src/Discord/Parts/Channel/Reaction.php | 10 +- src/Discord/Parts/Embed/Embed.php | 10 +- src/Discord/Parts/Guild/AuditLog/AuditLog.php | 70 +++++------ src/Discord/Parts/Guild/AuditLog/Entry.php | 8 +- .../Parts/Guild/AutoModeration/Rule.php | 26 ++-- .../Parts/Guild/CommandPermissions.php | 10 +- src/Discord/Parts/Guild/Emoji.php | 11 +- src/Discord/Parts/Guild/Guild.php | 20 +-- src/Discord/Parts/Guild/ScheduledEvent.php | 6 +- src/Discord/Parts/Guild/WelcomeScreen.php | 10 +- .../Parts/Interactions/Command/Command.php | 8 +- .../Parts/Interactions/Command/Option.php | 18 +-- .../Parts/Interactions/Interaction.php | 4 +- .../Parts/Interactions/Request/Component.php | 10 +- .../Interactions/Request/InteractionData.php | 18 +-- .../Parts/Interactions/Request/Option.php | 10 +- .../Parts/Interactions/Request/Resolved.php | 50 ++++---- src/Discord/Parts/Thread/Thread.php | 14 +-- src/Discord/Parts/User/Member.php | 18 +-- .../Parts/WebSockets/PresenceUpdate.php | 18 +-- src/Discord/Repository/AbstractRepository.php | 14 ++- .../Repository/Channel/ThreadRepository.php | 12 +- src/Discord/Voice/VoiceClient.php | 6 +- .../WebSockets/Events/GuildEmojisUpdate.php | 6 +- .../WebSockets/Events/GuildStickersUpdate.php | 6 +- .../WebSockets/Events/MessageDeleteBulk.php | 4 +- .../WebSockets/Events/ThreadListSync.php | 4 +- tests/Parts/Channel/ChannelTest.php | 2 +- .../Channel/Message/EmptyMessageTest.php | 2 +- 36 files changed, 288 insertions(+), 281 deletions(-) diff --git a/src/Discord/Builders/CommandAttributes.php b/src/Discord/Builders/CommandAttributes.php index 877dca08f..fd33d17b4 100644 --- a/src/Discord/Builders/CommandAttributes.php +++ b/src/Discord/Builders/CommandAttributes.php @@ -29,7 +29,7 @@ * @property ?string[]|null $name_localizations Localization dictionary for the name field. Values follow the same restrictions as name. * @property ?string $description 1-100 character description for CHAT_INPUT commands, empty string for USER and MESSAGE commands. * @property ?string[]|null $description_localizations Localization dictionary for the description field. Values follow the same restrictions as description. - * @property \Discord\Helpers\Collection|Option[]|null $options The parameters for the command, max 25. Only for Slash command (CHAT_INPUT). + * @property \Discord\Helpers\CollectionInterface|Option[]|null $options The parameters for the command, max 25. Only for Slash command (CHAT_INPUT). * @property ?string $default_member_permissions Set of permissions represented as a bit set. * @property bool|null $dm_permission Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. * @property ?bool $default_permission Whether the command is enabled by default when the app is added to a guild. SOON DEPRECATED. diff --git a/src/Discord/Builders/Components/SelectMenu.php b/src/Discord/Builders/Components/SelectMenu.php index 54cb29098..70289930e 100644 --- a/src/Discord/Builders/Components/SelectMenu.php +++ b/src/Discord/Builders/Components/SelectMenu.php @@ -12,7 +12,7 @@ namespace Discord\Builders\Components; use Discord\Discord; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Interactions\Interaction; use Discord\WebSockets\Event; use React\Promise\PromiseInterface; @@ -350,7 +350,7 @@ public function setListener(?callable $callback, Discord $discord, bool $oneOff if (empty($this->options)) { $response = $callback($interaction); } else { - $options = Collection::for(Option::class, null); + $options = ($this->discord->getCollectionClass())::for(Option::class, null); foreach ($this->options as $option) { if (in_array($option->getValue(), $interaction->data->values)) { diff --git a/src/Discord/Helpers/RegisteredCommand.php b/src/Discord/Helpers/RegisteredCommand.php index 2378addfd..3207d9f10 100644 --- a/src/Discord/Helpers/RegisteredCommand.php +++ b/src/Discord/Helpers/RegisteredCommand.php @@ -90,7 +90,7 @@ public function __construct(Discord $discord, string $name, ?callable $callback */ public function execute(array $options, Interaction $interaction): bool { - $params = Collection::for(Option::class, 'name'); + $params = ($this->discord->getCollectionClass())::for(Option::class, 'name'); foreach ($options as $option) { if (isset($this->subCommands[$option->name])) { diff --git a/src/Discord/Parts/Channel/Channel.php b/src/Discord/Parts/Channel/Channel.php index 507db9a41..4e6792b03 100644 --- a/src/Discord/Parts/Channel/Channel.php +++ b/src/Discord/Parts/Channel/Channel.php @@ -14,7 +14,7 @@ use Carbon\Carbon; use Discord\Builders\MessageBuilder; use Discord\Exceptions\InvalidOverwriteException; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Embed\Embed; use Discord\Parts\Guild\Guild; use Discord\Parts\Guild\Role; @@ -70,7 +70,7 @@ * @property int|null $bitrate The bitrate of the channel. Only for voice channels. * @property int|null $user_limit The user limit of the channel. Max 99 for voice channels and 10000 for stage channels (0 refers to no limit). * @property int|null $rate_limit_per_user Amount of seconds a user has to wait before sending a new message (slow mode). - * @property Collection|User[] $recipients A collection of all the recipients in the channel. Only for DM or group channels. + * @property CollectionInterface|User[] $recipients A collection of all the recipients in the channel. Only for DM or group channels. * @property-read User|null $recipient The first recipient of the channel. Only for DM or group channels. * @property-read string|null $recipient_id The ID of the recipient of the channel, if it is a DM channel. * @property ?string|null $icon Icon hash. @@ -84,7 +84,7 @@ * @property int|null $default_auto_archive_duration Default duration for newly created threads, in minutes, to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080. * @property string|null $permissions Computed permissions for the invoking user in the channel, including overwrites, only included when part of the resolved data received on an application command interaction. * @property int|null $flags Channel flags combined as a bitfield. - * @property Collection|Tag[] $available_tags Set of tags that can be used in a forum channel, limited to 20. + * @property CollectionInterface|Tag[] $available_tags Set of tags that can be used in a forum channel, limited to 20. * @property ?Reaction|null $default_reaction_emoji Emoji to show in the add reaction button on a thread in a forum channel. * @property int|null $default_thread_rate_limit_per_user The initial rate_limit_per_user to set on newly created threads in a forum channel. this field is copied to the thread at creation time and does not live update. * @property ?int|null $default_sort_order The default sort order type used to order posts in forum channels. @@ -251,11 +251,11 @@ protected function getRecipientIdAttribute(): ?string /** * Gets the recipients attribute. * - * @return Collection A collection of recipients. + * @return CollectionInterface A collection of recipients. */ - protected function getRecipientsAttribute(): Collection + protected function getRecipientsAttribute(): CollectionInterface { - $recipients = Collection::for(User::class); + $recipients = ($this->discord->getCollectionClass())::for(User::class); foreach ($this->attributes['recipients'] ?? [] as $recipient) { $recipients->pushItem($this->discord->users->get('id', $recipient->id) ?: $this->factory->part(User::class, (array) $recipient, true)); @@ -295,13 +295,13 @@ protected function getLastPinTimestampAttribute(): ?Carbon * * @link https://discord.com/developers/docs/resources/channel#get-pinned-messages * - * @return PromiseInterface> + * @return PromiseInterface> */ public function getPinnedMessages(): PromiseInterface { return $this->http->get(Endpoint::bind(Endpoint::CHANNEL_PINS, $this->id)) ->then(function ($responses) { - $messages = Collection::for(Message::class); + $messages = ($this->discord->getCollectionClass())::for(Message::class); foreach ($responses as $response) { $messages->pushItem($this->messages->get('id', $response->id) ?: $this->messages->create($response, true)); @@ -736,7 +736,7 @@ public function limitDelete(int $value, ?string $reason = null): PromiseInterfac * Or also missing `connect` permission for text in voice. * @throws \RangeException * - * @return PromiseInterface> + * @return PromiseInterface> * @todo Make it in a trait along with Thread */ public function getMessageHistory(array $options = []): PromiseInterface @@ -781,7 +781,7 @@ public function getMessageHistory(array $options = []): PromiseInterface } return $this->http->get($endpoint)->then(function ($responses) { - $messages = Collection::for(Message::class); + $messages = ($this->discord->getCollectionClass())::for(Message::class); foreach ($responses as $response) { $messages->pushItem($this->messages->get('id', $response->id) ?: $this->messages->create($response, true)); @@ -925,13 +925,13 @@ protected function getPermissionOverwritesAttribute(): ?array /** * Gets the available tags attribute. * - * @return Collection|Tag[] Available forum tags. + * @return CollectionInterface|Tag[] Available forum tags. * * @since 7.4.0 */ - protected function getAvailableTagsAttribute(): Collection + protected function getAvailableTagsAttribute(): CollectionInterface { - $available_tags = Collection::for(Tag::class); + $available_tags = ($this->discord->getCollectionClass())::for(Tag::class); foreach ($this->attributes['available_tags'] ?? [] as $available_tag) { $available_tags->pushItem($this->createOf(Tag::class, $available_tag)); @@ -1267,12 +1267,13 @@ public function broadcastTyping(): PromiseInterface * @param int $options['time'] Time in milliseconds until the collector finishes or false. * @param int $options['limit'] The amount of messages allowed or false. * - * @return PromiseInterface> + * @return PromiseInterface> */ public function createMessageCollector(callable $filter, array $options = []): PromiseInterface { $deferred = new Deferred(); - $messages = new Collection([], null, null); + /** @var CollectionInterface $messages An instance of the collection class implementing CollectionInterface.*/ + $messages = new ($this->discord->getCollectionClass())([], null, null); $timer = null; $options = array_merge([ diff --git a/src/Discord/Parts/Channel/Message.php b/src/Discord/Parts/Channel/Message.php index ebe1485c7..a0883e0eb 100644 --- a/src/Discord/Parts/Channel/Message.php +++ b/src/Discord/Parts/Channel/Message.php @@ -13,7 +13,7 @@ use Carbon\Carbon; use Discord\Builders\MessageBuilder; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Poll; use Discord\Parts\Embed\Embed; use Discord\Parts\Guild\Emoji; @@ -45,39 +45,39 @@ * * @since 2.0.0 * - * @property string $id The unique identifier of the message. - * @property string $channel_id The unique identifier of the channel that the message was sent in. - * @property-read Channel|Thread $channel The channel that the message was sent in. - * @property User|null $author The author of the message. Will be a webhook if sent from one. - * @property-read string|null $user_id The user id of the author. - * @property string $content The content of the message if it is a normal message. - * @property Carbon $timestamp A timestamp of when the message was sent. - * @property Carbon|null $edited_timestamp A timestamp of when the message was edited, or null. - * @property bool $tts Whether the message was sent as a text-to-speech message. - * @property bool $mention_everyone Whether the message contained an @everyone mention. - * @property Collection|User[] $mentions A collection of the users mentioned in the message. - * @property Collection|Role[] $mention_roles A collection of roles that were mentioned in the message. - * @property Collection|Channel[] $mention_channels Collection of mentioned channels. - * @property Collection|Attachment[] $attachments Collection of attachment objects. - * @property Collection|Embed[] $embeds A collection of embed objects. - * @property ReactionRepository $reactions Collection of reactions on the message. - * @property string|null $nonce A randomly generated string that provides verification for the client. Not required. - * @property bool $pinned Whether the message is pinned to the channel. - * @property string|null $webhook_id ID of the webhook that made the message, if any. - * @property int $type The type of message. - * @property object|null $activity Current message activity. Requires rich presence. - * @property object|null $application Application of message. Requires rich presence. - * @property string|null $application_id If the message is a response to an Interaction, this is the id of the interaction's application. - * @property object|null $message_reference Message that is referenced by this message. - * @property int|null $flags Message flags. - * @property Message|null $referenced_message The message that is referenced in a reply. - * @property MessageInteraction|null $interaction Sent if the message is a response to an Interaction. - * @property Thread|null $thread The thread that was started from this message, includes thread member object. - * @property Collection|Component[]|null $components Sent if the message contains components like buttons, action rows, or other interactive components. - * @property Collection|Sticker[]|null $sticker_items Stickers attached to the message. - * @property int|null $position A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread, it can be used to estimate the relative position of the message in a thread in company with `total_message_sent` on parent thread. - * @property object|null $role_subscription_data Data of the role subscription purchase or renewal that prompted this `ROLE_SUBSCRIPTION_PURCHASE` message. - * @property Poll|null $poll The poll attached to the message. + * @property string $id The unique identifier of the message. + * @property string $channel_id The unique identifier of the channel that the message was sent in. + * @property-read Channel|Thread $channel The channel that the message was sent in. + * @property User|null $author The author of the message. Will be a webhook if sent from one. + * @property-read string|null $user_id The user id of the author. + * @property string $content The content of the message if it is a normal message. + * @property Carbon $timestamp A timestamp of when the message was sent. + * @property Carbon|null $edited_timestamp A timestamp of when the message was edited, or null. + * @property bool $tts Whether the message was sent as a text-to-speech message. + * @property bool $mention_everyone Whether the message contained an @everyone mention. + * @property CollectionInterface|User[] $mentions A collection of the users mentioned in the message. + * @property CollectionInterface|Role[] $mention_roles A collection of roles that were mentioned in the message. + * @property CollectionInterface|Channel[] $mention_channels Collection of mentioned channels. + * @property CollectionInterface|Attachment[] $attachments Collection of attachment objects. + * @property CollectionInterface|Embed[] $embeds A collection of embed objects. + * @property ReactionRepository $reactions Collection of reactions on the message. + * @property string|null $nonce A randomly generated string that provides verification for the client. Not required. + * @property bool $pinned Whether the message is pinned to the channel. + * @property string|null $webhook_id ID of the webhook that made the message, if any. + * @property int $type The type of message. + * @property object|null $activity Current message activity. Requires rich presence. + * @property object|null $application Application of message. Requires rich presence. + * @property string|null $application_id If the message is a response to an Interaction, this is the id of the interaction's application. + * @property object|null $message_reference Message that is referenced by this message. + * @property int|null $flags Message flags. + * @property Message|null $referenced_message The message that is referenced in a reply. + * @property MessageInteraction|null $interaction Sent if the message is a response to an Interaction. + * @property Thread|null $thread The thread that was started from this message, includes thread member object. + * @property CollectionInterface|Component[]|null $components Sent if the message contains components like buttons, action rows, or other interactive components. + * @property CollectionInterface|Sticker[]|null $sticker_items Stickers attached to the message. + * @property int|null $position A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread, it can be used to estimate the relative position of the message in a thread in company with `total_message_sent` on parent thread. + * @property object|null $role_subscription_data Data of the role subscription purchase or renewal that prompted this `ROLE_SUBSCRIPTION_PURCHASE` message. + * @property Poll|null $poll The poll attached to the message. * * @property-read bool $crossposted Message has been crossposted. * @property-read bool $is_crosspost Message is a crosspost from another channel. @@ -334,11 +334,11 @@ protected function getSuppressNotificationsAttribute(): bool /** * Gets the mention_channels attribute. * - * @return Collection|Channel[] + * @return CollectionInterface|Channel[] */ - protected function getMentionChannelsAttribute(): Collection + protected function getMentionChannelsAttribute(): CollectionInterface { - $collection = Collection::for(Channel::class); + $collection = ($this->discord->getCollectionClass())::for(Channel::class); if (preg_match_all('/<#([0-9]*)>/', $this->content, $matches)) { foreach ($matches[1] as $channelId) { @@ -358,11 +358,11 @@ protected function getMentionChannelsAttribute(): Collection /** * Returns any attached files. * - * @return Collection|Attachment[] Attachment objects. + * @return CollectionInterface|Attachment[] Attachment objects. */ - protected function getAttachmentsAttribute(): Collection + protected function getAttachmentsAttribute(): CollectionInterface { - $attachments = Collection::for(Attachment::class); + $attachments = ($this->discord->getCollectionClass())::for(Attachment::class); foreach ($this->attributes['attachments'] ?? [] as $attachment) { $attachments->pushItem($this->createOf(Attachment::class, $attachment)); @@ -490,11 +490,12 @@ protected function getGuildAttribute(): ?Guild /** * Returns the mention_roles attribute. * - * @return Collection The roles that were mentioned. null role only contains the ID in the collection. + * @return CollectionInterface The roles that were mentioned. null role only contains the ID in the collection. */ - protected function getMentionRolesAttribute(): Collection + protected function getMentionRolesAttribute(): CollectionInterface { - $roles = new Collection(); + /** @var CollectionInterface $roles An instance of the collection class implementing CollectionInterface.*/ + $roles = new ($this->discord->getCollectionClass())(); if (empty($this->attributes['mention_roles'])) { return $roles; @@ -514,11 +515,11 @@ protected function getMentionRolesAttribute(): Collection /** * Returns the mention attribute. * - * @return Collection|User[] The users that were mentioned. + * @return CollectionInterface|User[] The users that were mentioned. */ - protected function getMentionsAttribute(): Collection + protected function getMentionsAttribute(): CollectionInterface { - $users = Collection::for(User::class); + $users = ($this->discord->getCollectionClass())::for(User::class); foreach ($this->attributes['mentions'] ?? [] as $mention) { $users->pushItem($this->discord->users->get('id', $mention->id) ?: $this->factory->part(User::class, (array) $mention, true)); @@ -581,11 +582,12 @@ protected function getMemberAttribute(): ?Member /** * Returns the embed attribute. * - * @return Collection A collection of embeds. + * @return CollectionInterface A collection of embeds. */ - protected function getEmbedsAttribute(): Collection + protected function getEmbedsAttribute(): CollectionInterface { - $embeds = new Collection([], null); + /** @var CollectionInterface $embeds An instance of the collection class implementing CollectionInterface.*/ + $embeds = new ($this->discord->getCollectionClass())([], null); foreach ($this->attributes['embeds'] ?? [] as $embed) { $embeds->pushItem($this->createOf(Embed::class, $embed)); @@ -681,15 +683,15 @@ protected function getEditedTimestampAttribute(): ?Carbon /** * Returns the components attribute. * - * @return Collection|Component[]|null + * @return CollectionInterface|Component[]|null */ - protected function getComponentsAttribute(): ?Collection + protected function getComponentsAttribute(): ?CollectionInterface { if (! isset($this->attributes['components'])) { return null; } - $components = Collection::for(Component::class, null); + $components = ($this->discord->getCollectionClass())::for(Component::class, null); foreach ($this->attributes['components'] as $component) { $components->pushItem($this->createOf(Component::class, $component)); @@ -701,15 +703,15 @@ protected function getComponentsAttribute(): ?Collection /** * Returns the sticker_items attribute. * - * @return Collection|Sticker[]|null Partial stickers. + * @return CollectionInterface|Sticker[]|null Partial stickers. */ - protected function getStickerItemsAttribute(): ?Collection + protected function getStickerItemsAttribute(): ?CollectionInterface { if (! isset($this->attributes['sticker_items']) && ! in_array($this->type, [self::TYPE_DEFAULT, self::TYPE_REPLY])) { return null; } - $sticker_items = Collection::for(Sticker::class); + $sticker_items = ($this->discord->getCollectionClass())::for(Sticker::class); foreach ($this->attributes['sticker_items'] ?? [] as $sticker) { $sticker_items->pushItem($this->factory->part(Sticker::class, (array) $sticker, true)); @@ -1077,12 +1079,13 @@ public function delete(): PromiseInterface * @param int $options['time'] Time in milliseconds until the collector finishes or false. * @param int $options['limit'] The amount of reactions allowed or false. * - * @return PromiseInterface> + * @return PromiseInterface> */ public function createReactionCollector(callable $filter, array $options = []): PromiseInterface { $deferred = new Deferred(); - $reactions = new Collection([], null, null); + /** @var CollectionInterface $reactions An instance of the collection class implementing CollectionInterface.*/ + $reactions = new ($this->discord->getCollectionClass())([], null, null); $timer = null; $options = array_merge([ diff --git a/src/Discord/Parts/Channel/Poll.php b/src/Discord/Parts/Channel/Poll.php index 6420e5242..2f3e07b12 100644 --- a/src/Discord/Parts/Channel/Poll.php +++ b/src/Discord/Parts/Channel/Poll.php @@ -12,7 +12,7 @@ namespace Discord\Parts\Channel; use Carbon\Carbon; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Http\Endpoint; use Discord\Parts\Channel\Poll\PollAnswer; use Discord\Parts\Channel\Poll\PollMedia; diff --git a/src/Discord/Parts/Channel/Poll/PollAnswer.php b/src/Discord/Parts/Channel/Poll/PollAnswer.php index 2bfc021a5..b70f525c7 100644 --- a/src/Discord/Parts/Channel/Poll/PollAnswer.php +++ b/src/Discord/Parts/Channel/Poll/PollAnswer.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Channel\Poll; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Http\Endpoint; use Discord\Parts\Channel\Channel; use Discord\Parts\Channel\Message; @@ -140,7 +140,7 @@ protected function getGuildAttribute(): ?Guild * * @throws \OutOfRangeException * - * @return PromiseInterface + * @return PromiseInterface */ public function getVoters(array $options = []): PromiseInterface { @@ -162,7 +162,7 @@ public function getVoters(array $options = []): PromiseInterface return $this->http->get($query) ->then(function ($response) { - $users = Collection::for(User::class); + $users = ($this->discord->getCollectionClass())::for(User::class); foreach ($response->users ?? [] as $user) { if (! $part = $this->discord->users->get('id', $user->id)) { diff --git a/src/Discord/Parts/Channel/Reaction.php b/src/Discord/Parts/Channel/Reaction.php index e1a13ff5a..762a46bcc 100644 --- a/src/Discord/Parts/Channel/Reaction.php +++ b/src/Discord/Parts/Channel/Reaction.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Channel; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Http\Endpoint; use Discord\Parts\Guild\Emoji; use Discord\Parts\Guild\Guild; @@ -139,7 +139,7 @@ protected function getIdAttribute(): string * * @link https://discord.com/developers/docs/resources/channel#get-reactions * - * @return PromiseInterface + * @return PromiseInterface */ public function getUsers(array $options = []): PromiseInterface { @@ -161,7 +161,7 @@ public function getUsers(array $options = []): PromiseInterface return $this->http->get($query) ->then(function ($response) { - $users = Collection::for(User::class); + $users = ($this->discord->getCollectionClass())::for(User::class); foreach ((array) $response as $user) { if (! $part = $this->discord->users->get('id', $user->id)) { @@ -182,11 +182,11 @@ public function getUsers(array $options = []): PromiseInterface * * @see Message::getUsers() * - * @return PromiseInterface + * @return PromiseInterface */ public function getAllUsers(): PromiseInterface { - $response = Collection::for(User::class); + $response = ($this->discord->getCollectionClass())::for(User::class); $getUsers = function ($after = null) use (&$getUsers, $response) { $options = ['limit' => 100]; if ($after != null) { diff --git a/src/Discord/Parts/Embed/Embed.php b/src/Discord/Parts/Embed/Embed.php index 811635a39..5af6c67e7 100644 --- a/src/Discord/Parts/Embed/Embed.php +++ b/src/Discord/Parts/Embed/Embed.php @@ -12,7 +12,7 @@ namespace Discord\Parts\Embed; use Carbon\Carbon; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Attachment; use Discord\Parts\Part; use function Discord\poly_strlen; @@ -36,7 +36,7 @@ * @property-read Video|null $video The video of the embed. * @property-read object|null $provider The provider of the embed. * @property Author|null $author The author of the embed. - * @property Collection|Field[] $fields A collection of embed fields. + * @property CollectionInterface|Field[] $fields A collection of embed fields. */ class Embed extends Part { @@ -135,11 +135,11 @@ protected function getAuthorAttribute(): Author /** * Gets the fields attribute. * - * @return Collection|Field[] + * @return CollectionInterfaceInterface|Field[] */ - protected function getFieldsAttribute(): Collection + protected function getFieldsAttribute(): CollectionInterface { - $fields = Collection::for(Field::class, null); + $fields = ($this->discord->getCollectionClass())::for(Field::class, null); if (! array_key_exists('fields', $this->attributes)) { return $fields; diff --git a/src/Discord/Parts/Guild/AuditLog/AuditLog.php b/src/Discord/Parts/Guild/AuditLog/AuditLog.php index 1cba91e8a..f787eb954 100644 --- a/src/Discord/Parts/Guild/AuditLog/AuditLog.php +++ b/src/Discord/Parts/Guild/AuditLog/AuditLog.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Guild\AuditLog; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Webhook; use Discord\Parts\Guild\AutoModeration\Rule; use Discord\Parts\Guild\Guild; @@ -30,14 +30,14 @@ * * @since 5.1.0 * - * @property Collection|Command[] $application_commands List of application commands referenced in the audit log. - * @property Collection|Entry[] $audit_log_entries List of audit log entries. - * @property Collection|Rule[] $auto_moderation_rules List of auto moderation rules referenced in the audit log. - * @property Collection|ScheduledEvent[] $guild_scheduled_events List of guild scheduled events referenced in the audit log. - * @property Collection|Integration[] $integrations List of partial integration objects. - * @property Collection|Thread[] $threads List of threads referenced in the audit log. - * @property Collection|User[] $users List of users referenced in the audit log. - * @property Collection|Webhook[] $webhooks List of webhooks referenced in the audit log. + * @property CollectionInterface|Command[] $application_commands List of application commands referenced in the audit log. + * @property CollectionInterface|Entry[] $audit_log_entries List of audit log entries. + * @property CollectionInterface|Rule[] $auto_moderation_rules List of auto moderation rules referenced in the audit log. + * @property CollectionInterface|ScheduledEvent[] $guild_scheduled_events List of guild scheduled events referenced in the audit log. + * @property CollectionInterface|Integration[] $integrations List of partial integration objects. + * @property CollectionInterface|Thread[] $threads List of threads referenced in the audit log. + * @property CollectionInterface|User[] $users List of users referenced in the audit log. + * @property CollectionInterface|Webhook[] $webhooks List of webhooks referenced in the audit log. * * @property string $guild_id * @property-read Guild|null $guild @@ -74,11 +74,11 @@ protected function getGuildAttribute(): ?Guild /** * Returns a collection of application commands found in the audit log. * - * @return Collection|Command[] + * @return CollectionInterfaceInterface|Command[] */ - protected function getApplicationCommandsAttribute(): Collection + protected function getApplicationCommandsAttribute(): CollectionInterface { - $collection = Collection::for(Command::class); + $collection = ($this->discord->getCollectionClass())::for(Command::class); foreach ($this->attributes['application_commands'] ?? [] as $application_commands) { $collection->pushItem($this->factory->part(Command::class, (array) $application_commands, true)); @@ -90,11 +90,11 @@ protected function getApplicationCommandsAttribute(): Collection /** * Returns a collection of audit log entries. * - * @return Collection|Entry[] + * @return CollectionInterfaceInterface|Entry[] */ - protected function getAuditLogEntriesAttribute(): Collection + protected function getAuditLogEntriesAttribute(): CollectionInterface { - $collection = Collection::for(Entry::class); + $collection = ($this->discord->getCollectionClass())::for(Entry::class); foreach ($this->attributes['audit_log_entries'] ?? [] as $entry) { $collection->pushItem($this->createOf(Entry::class, $entry)); @@ -106,11 +106,11 @@ protected function getAuditLogEntriesAttribute(): Collection /** * Returns a collection of auto moderation rules found in the audit log. * - * @return Collection|Rule[] + * @return CollectionInterfaceInterface|Rule[] */ - protected function getAutoModerationRulesAttribute(): Collection + protected function getAutoModerationRulesAttribute(): CollectionInterface { - $collection = Collection::for(Rule::class); + $collection = ($this->discord->getCollectionClass())::for(Rule::class); foreach ($this->attributes['auto_moderation_rules'] ?? [] as $rule) { $collection->pushItem($this->factory->part(Rule::class, (array) $rule, true)); @@ -122,11 +122,11 @@ protected function getAutoModerationRulesAttribute(): Collection /** * Returns a collection of guild scheduled events found in the audit log. * - * @return Collection|ScheduledEvent[] + * @return CollectionInterfaceInterface|ScheduledEvent[] */ - protected function getGuildScheduledEventsAttribute(): Collection + protected function getGuildScheduledEventsAttribute(): CollectionInterface { - $collection = Collection::for(ScheduledEvent::class); + $collection = ($this->discord->getCollectionClass())::for(ScheduledEvent::class); foreach ($this->attributes['guild_scheduled_events'] ?? [] as $scheduled_event) { $collection->pushItem($this->factory->part(ScheduledEvent::class, (array) $scheduled_event, true)); @@ -140,11 +140,11 @@ protected function getGuildScheduledEventsAttribute(): Collection * * @link https://discord.com/developers/docs/resources/audit-log#audit-log-object-example-partial-integration-object * - * @return Collection|Integration[] + * @return CollectionInterfaceInterface|Integration[] */ - protected function getIntegrationsAttribute(): Collection + protected function getIntegrationsAttribute(): CollectionInterface { - $collection = Collection::for(Integration::class); + $collection = ($this->discord->getCollectionClass())::for(Integration::class); foreach ($this->attributes['integrations'] ?? [] as $integration) { $collection->pushItem($this->factory->part(Integration::class, (array) $integration, true)); @@ -156,11 +156,11 @@ protected function getIntegrationsAttribute(): Collection /** * Returns a collection of threads found in the audit log. * - * @return Collection|Thread[] + * @return CollectionInterfaceInterface|Thread[] */ - protected function getThreadsAttribute(): Collection + protected function getThreadsAttribute(): CollectionInterface { - $collection = Collection::for(Thread::class); + $collection = ($this->discord->getCollectionClass())::for(Thread::class); foreach ($this->attributes['threads'] ?? [] as $thread) { $collection->pushItem($this->factory->part(Thread::class, (array) $thread, true)); @@ -172,11 +172,11 @@ protected function getThreadsAttribute(): Collection /** * Returns a collection of users found in the audit log. * - * @return Collection|User[] + * @return CollectionInterfaceInterface|User[] */ - protected function getUsersAttribute(): Collection + protected function getUsersAttribute(): CollectionInterface { - $collection = Collection::for(User::class); + $collection = ($this->discord->getCollectionClass())::for(User::class); foreach ($this->attributes['users'] ?? [] as $user) { $collection->pushItem($this->discord->users->get('id', $user->id) ?: $this->factory->part(User::class, (array) $user, true)); @@ -188,11 +188,11 @@ protected function getUsersAttribute(): Collection /** * Returns a collection of webhooks found in the audit log. * - * @return Collection|Webhook[] + * @return CollectionInterfaceInterface|Webhook[] */ - protected function getWebhooksAttribute(): Collection + protected function getWebhooksAttribute(): CollectionInterface { - $collection = Collection::for(Webhook::class); + $collection = ($this->discord->getCollectionClass())::for(Webhook::class); foreach ($this->attributes['webhooks'] ?? [] as $webhook) { $collection->pushItem($this->factory->part(Webhook::class, (array) $webhook, true)); @@ -208,9 +208,9 @@ protected function getWebhooksAttribute(): Collection * * @throws \InvalidArgumentException * - * @return Collection|Entry[] + * @return CollectionInterfaceInterface|Entry[] */ - public function searchByType(int $action_type): Collection + public function searchByType(int $action_type): CollectionInterface { $types = array_values((new ReflectionClass(Entry::class))->getConstants()); diff --git a/src/Discord/Parts/Guild/AuditLog/Entry.php b/src/Discord/Parts/Guild/AuditLog/Entry.php index 70ccc9979..f2a483142 100644 --- a/src/Discord/Parts/Guild/AuditLog/Entry.php +++ b/src/Discord/Parts/Guild/AuditLog/Entry.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Guild\AuditLog; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Part; use Discord\Parts\User\User; @@ -121,11 +121,11 @@ protected function getUserAttribute(): ?User * * @link https://discord.com/developers/docs/resources/audit-log#audit-log-change-object * - * @return Collection + * @return CollectionInterface */ - protected function getChangesAttribute(): Collection + protected function getChangesAttribute(): CollectionInterface { - return new Collection($this->attributes['changes'] ?? [], 'key', null); + return new ($this->discord->getCollectionClass())($this->attributes['changes'] ?? [], 'key', null); } /** diff --git a/src/Discord/Parts/Guild/AutoModeration/Rule.php b/src/Discord/Parts/Guild/AutoModeration/Rule.php index 935f3e277..a79202490 100644 --- a/src/Discord/Parts/Guild/AutoModeration/Rule.php +++ b/src/Discord/Parts/Guild/AutoModeration/Rule.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Guild\AutoModeration; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Channel; use Discord\Parts\Guild\Guild; use Discord\Parts\Guild\Role; @@ -39,10 +39,10 @@ * @property int $event_type The rule event type. * @property int $trigger_type The rule trigger type. * @property object $trigger_metadata The rule trigger metadata (may contain `keyword_filter`, `regex_patterns`, `presets`, `allow_list`, `mention_total_limit` and `mention_raid_protection_enabled`). - * @property Collection|Action[] $actions The actions which will execute when the rule is triggered. + * @property CollectionInterface|Action[] $actions The actions which will execute when the rule is triggered. * @property bool $enabled Whether the rule is enabled. - * @property Collection|?Role[] $exempt_roles The role ids that should not be affected by the rule (Maximum of 20). - * @property Collection|?Channel[] $exempt_channels The channel ids that should not be affected by the rule (Maximum of 50). + * @property CollectionInterface|?Role[] $exempt_roles The role ids that should not be affected by the rule (Maximum of 20). + * @property CollectionInterface|?Channel[] $exempt_channels The channel ids that should not be affected by the rule (Maximum of 50). */ class Rule extends Part { @@ -98,11 +98,11 @@ protected function getCreatorAttribute(): ?User /** * Returns the actions attribute. * - * @return Collection|Action[] A collection of actions. + * @return CollectionInterfaceInterface|Action[] A collection of actions. */ - protected function getActionsAttribute(): Collection + protected function getActionsAttribute(): CollectionInterface { - $actions = Collection::for(Action::class, null); + $actions = ($this->discord->getCollectionClass())::for(Action::class, null); foreach ($this->attributes['actions'] as $action) { $actions->pushItem($this->createOf(Action::class, $action)); @@ -114,11 +114,11 @@ protected function getActionsAttribute(): Collection /** * Returns the exempt roles attribute. * - * @return Collection|?Role[] A collection of roles exempt from the rule. + * @return CollectionInterfaceInterface|?Role[] A collection of roles exempt from the rule. */ - protected function getExemptRolesAttribute(): Collection + protected function getExemptRolesAttribute(): CollectionInterface { - $roles = new Collection(); + $roles = new ($this->discord->getCollectionClass())(); if (empty($this->attributes['exempt_roles'])) { return $roles; @@ -138,11 +138,11 @@ protected function getExemptRolesAttribute(): Collection /** * Returns the exempt channels attribute. * - * @return Collection|?Channel[] A collection of channels exempt from the rule. + * @return CollectionInterfaceInterface|?Channel[] A collection of channels exempt from the rule. */ - protected function getExemptChannelsAttribute(): Collection + protected function getExemptChannelsAttribute(): CollectionInterface { - $channels = new Collection(); + $channels = new ($this->discord->getCollectionClass())(); if (empty($this->attributes['exempt_channels'])) { return $channels; diff --git a/src/Discord/Parts/Guild/CommandPermissions.php b/src/Discord/Parts/Guild/CommandPermissions.php index 7d297419b..4b8405c36 100644 --- a/src/Discord/Parts/Guild/CommandPermissions.php +++ b/src/Discord/Parts/Guild/CommandPermissions.php @@ -12,7 +12,7 @@ namespace Discord\Parts\Guild; use Discord\Helpers\BigInt; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Interactions\Command\Permission; use Discord\Parts\Part; @@ -28,7 +28,7 @@ * @property string $application_id The id of the application the command belongs to. * @property string $guild_id The id of the guild. * @property-read Guild|null $guild - * @property Collection|Permission[] $permissions The permissions for the command in the guild. + * @property CollectionInterface|Permission[] $permissions The permissions for the command in the guild. */ class CommandPermissions extends Part { @@ -55,11 +55,11 @@ protected function getGuildAttribute(): ?Guild /** * Gets the permissions attribute. * - * @return Collection|Permission[] A collection of permissions. + * @return CollectionInterfaceInterface|Permission[] A collection of permissions. */ - protected function getPermissionsAttribute(): Collection + protected function getPermissionsAttribute(): CollectionInterface { - $permissions = Collection::for(Permission::class); + $permissions = ($this->discord->getCollectionClass())::for(Permission::class); foreach ($this->attributes['permissions'] ?? [] as $permission) { $permissions->pushItem($this->factory->part(Permission::class, (array) $permission, true)); diff --git a/src/Discord/Parts/Guild/Emoji.php b/src/Discord/Parts/Guild/Emoji.php index b353ee11b..20ce6d6af 100644 --- a/src/Discord/Parts/Guild/Emoji.php +++ b/src/Discord/Parts/Guild/Emoji.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Guild; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Part; use Discord\Parts\User\User; use Stringable; @@ -25,7 +25,7 @@ * * @property ?string $id The identifier for the emoji. * @property string $name The name of the emoji. - * @property Collection|Role[] $roles The roles that are allowed to use the emoji. + * @property CollectionInterface|Role[] $roles The roles that are allowed to use the emoji. * @property User|null $user User that created this emoji. * @property bool|null $require_colons Whether the emoji requires colons to be triggered. * @property bool|null $managed Whether this emoji is managed by a role. @@ -67,11 +67,12 @@ protected function getGuildAttribute(): ?Guild /** * Returns the roles attribute. * - * @return Collection A collection of roles for the emoji. + * @return CollectionInterface A collection of roles for the emoji. */ - protected function getRolesAttribute(): Collection + protected function getRolesAttribute(): CollectionInterface { - $roles = new Collection(); + /** @var CollectionInterface $roles An instance of the collection class implementing CollectionInterface.*/ + $roles = new ($this->discord->getCollectionClass())(); if (empty($this->attributes['roles'])) { return $roles; diff --git a/src/Discord/Parts/Guild/Guild.php b/src/Discord/Parts/Guild/Guild.php index 215bd8a76..f39c7740e 100644 --- a/src/Discord/Parts/Guild/Guild.php +++ b/src/Discord/Parts/Guild/Guild.php @@ -13,7 +13,7 @@ use Carbon\Carbon; use Discord\Exceptions\FileNotFoundException; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Helpers\Multipart; use Discord\Http\Endpoint; use Discord\Http\Exceptions\NoPermissionsException; @@ -305,7 +305,7 @@ class Guild extends Part /** * An array of valid regions. * - * @var Collection|null + * @var CollectionInterface|null */ protected $regions; @@ -423,7 +423,7 @@ protected function setStickersAttribute(?array $stickers): void * * @throws NoPermissionsException Missing manage_guild permission. * - * @return PromiseInterface + * @return PromiseInterface */ public function getInvites(): PromiseInterface { @@ -433,7 +433,7 @@ public function getInvites(): PromiseInterface } return $this->http->get(Endpoint::bind(Endpoint::GUILD_INVITES, $this->id))->then(function ($response) { - $invites = Collection::for(Invite::class, 'code'); + $invites = ($this->discord->getCollectionClass())::for(Invite::class, 'code'); foreach ($response as $invite) { $invite = $this->factory->part(Invite::class, (array) $invite, true); @@ -568,11 +568,11 @@ protected function getSplashHashAttribute(): ?string * * @deprecated 10.0.0 Use `$channel->stage_instances` * - * @return Collection|StageInstance[] + * @return CollectionInterfaceInterface|StageInstance[] */ - protected function getStageInstancesAttribute(): Collection + protected function getStageInstancesAttribute(): CollectionInterface { - $stage_instances = Collection::for(StageInstance::class); + $stage_instances = ($this->discord->getCollectionClass())::for(StageInstance::class); if ($channels = $this->channels) { /** @var Channel */ @@ -753,7 +753,7 @@ public function getVoiceRegions(): PromiseInterface } return $this->http->get('voice/regions')->then(function ($regions) { - $regions = new Collection($regions); + $regions = new ($this->discord->getCollectionClass())($regions); $this->regions = $regions; @@ -1155,7 +1155,7 @@ public function updateRolePositions(array $roles): PromiseInterface * @param string|null $options['query'] Query string to match username(s) and nickname(s) against * @param int|null $options['limit'] How many entries are returned (default 1, minimum 1, maximum 1000) * - * @return PromiseInterface + * @return PromiseInterface */ public function searchMembers(array $options): PromiseInterface { @@ -1176,7 +1176,7 @@ public function searchMembers(array $options): PromiseInterface $endpoint->addQuery('limit', $options['limit']); return $this->http->get($endpoint)->then(function ($responses) { - $members = Collection::for(Member::class); + $members = ($this->discord->getCollectionClass())::for(Member::class); foreach ($responses as $response) { if (! $member = $this->members->get('id', $response->user->id)) { diff --git a/src/Discord/Parts/Guild/ScheduledEvent.php b/src/Discord/Parts/Guild/ScheduledEvent.php index 49b571776..e4e3811ff 100644 --- a/src/Discord/Parts/Guild/ScheduledEvent.php +++ b/src/Discord/Parts/Guild/ScheduledEvent.php @@ -12,7 +12,7 @@ namespace Discord\Parts\Guild; use Carbon\Carbon; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Http\Endpoint; use Discord\Parts\Channel\Channel; use Discord\Parts\Part; @@ -97,7 +97,7 @@ class ScheduledEvent extends Part * * @throws \RangeException * - * @return PromiseInterface> + * @return PromiseInterface> */ public function getUsers(array $options): PromiseInterface { @@ -126,7 +126,7 @@ public function getUsers(array $options): PromiseInterface } return $this->http->get($endpoint)->then(function ($responses) { - $users = new Collection(); + $users = new ($this->discord->getCollectionClass())(); $guild = $this->guild; diff --git a/src/Discord/Parts/Guild/WelcomeScreen.php b/src/Discord/Parts/Guild/WelcomeScreen.php index 3ea37c850..b53938b14 100644 --- a/src/Discord/Parts/Guild/WelcomeScreen.php +++ b/src/Discord/Parts/Guild/WelcomeScreen.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Guild; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Part; /** @@ -22,7 +22,7 @@ * @since 7.0.0 * * @property ?string $description The server description shown in the welcome screen. - * @property Collection|WelcomeChannel[] $welcome_channels The channels shown in the welcome screen, up to 5. + * @property CollectionInterface|WelcomeChannel[] $welcome_channels The channels shown in the welcome screen, up to 5. */ class WelcomeScreen extends Part { @@ -37,11 +37,11 @@ class WelcomeScreen extends Part /** * Returns the Welcome Channels of the Welcome Screen. * - * @return Collection|WelcomeChannel[] The channels of welcome screen. + * @return CollectionInterfaceInterface|WelcomeChannel[] The channels of welcome screen. */ - protected function getWelcomeChannelsAttribute(): Collection + protected function getWelcomeChannelsAttribute(): CollectionInterface { - $collection = Collection::for(WelcomeChannel::class, null); + $collection = ($this->discord->getCollectionClass())::for(WelcomeChannel::class, null); foreach ($this->attributes['welcome_channels'] ?? [] as $welcome_channel) { $collection->pushItem($this->createOf(WelcomeChannel::class, $welcome_channel)); diff --git a/src/Discord/Parts/Interactions/Command/Command.php b/src/Discord/Parts/Interactions/Command/Command.php index 139bc83b8..c50591862 100644 --- a/src/Discord/Parts/Interactions/Command/Command.php +++ b/src/Discord/Parts/Interactions/Command/Command.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Interactions\Command; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Guild\Guild; use Discord\Parts\Part; use Stringable; @@ -79,15 +79,15 @@ protected function getApplicationIdAttribute(): string /** * Gets the options attribute. * - * @return Collection|Option[]|null A collection of options. + * @return CollectionInterfaceInterface|Option[]|null A collection of options. */ - protected function getOptionsAttribute(): ?Collection + protected function getOptionsAttribute(): ?CollectionInterface { if (! isset($this->attributes['options']) && (isset($this->type) && $this->type != self::CHAT_INPUT)) { return null; } - $options = Collection::for(Option::class, null); + $options = ($this->discord->getCollectionClass())::for(Option::class, null); foreach ($this->attributes['options'] ?? [] as $option) { $options->pushItem($this->createOf(Option::class, $option)); diff --git a/src/Discord/Parts/Interactions/Command/Option.php b/src/Discord/Parts/Interactions/Command/Option.php index 6900ff6be..6648365e7 100644 --- a/src/Discord/Parts/Interactions/Command/Option.php +++ b/src/Discord/Parts/Interactions/Command/Option.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Interactions\Command; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Part; use function Discord\poly_strlen; @@ -29,8 +29,8 @@ * @property string $description 1-100 character description. * @property ?string[]|null $description_localizations Localization dictionary for the description field. Values follow the same restrictions as description. * @property bool|null $required If the parameter is required or optional--default false. - * @property Collection|Choice[]|null $choices Choices for STRING, INTEGER, and NUMBER types for the user to pick from, max 25. Only for slash commands. - * @property Collection|Option[] $options Sub-options if applicable. + * @property CollectionInterface|Choice[]|null $choices Choices for STRING, INTEGER, and NUMBER types for the user to pick from, max 25. Only for slash commands. + * @property CollectionInterface|Option[] $options Sub-options if applicable. * @property array|null $channel_types If the option is a channel type, the channels shown will be restricted to these types. * @property int|float|null $min_value If the option is an INTEGER or NUMBER type, the minimum value permitted. * @property int|float|null $max_value If the option is an INTEGER or NUMBER type, the maximum value permitted. @@ -75,15 +75,15 @@ class Option extends Part /** * Gets the choices attribute. * - * @return Collection|Choice[]|null A collection of choices. + * @return CollectionInterfaceInterface|Choice[]|null A collection of choices. */ - protected function getChoicesAttribute(): ?Collection + protected function getChoicesAttribute(): ?CollectionInterface { if (! isset($this->attributes['choices']) && ! in_array($this->type, [self::STRING, self::INTEGER, self::NUMBER])) { return null; } - $choices = Collection::for(Choice::class, null); + $choices = ($this->discord->getCollectionClass())::for(Choice::class, null); foreach ($this->attributes['choices'] ?? [] as $choice) { $choices->pushItem($this->createOf(Choice::class, $choice)); @@ -95,11 +95,11 @@ protected function getChoicesAttribute(): ?Collection /** * Gets the options attribute. * - * @return Collection|Option[] A collection of options. + * @return CollectionInterfaceInterface|Option[] A collection of options. */ - protected function getOptionsAttribute(): Collection + protected function getOptionsAttribute(): CollectionInterface { - $options = Collection::for(Option::class, null); + $options = ($this->discord->getCollectionClass())::for(Option::class, null); foreach ($this->attributes['options'] ?? [] as $option) { $options->pushItem($this->createOf(Option::class, $option)); diff --git a/src/Discord/Parts/Interactions/Interaction.php b/src/Discord/Parts/Interactions/Interaction.php index 0def5a361..132b9b1e4 100644 --- a/src/Discord/Parts/Interactions/Interaction.php +++ b/src/Discord/Parts/Interactions/Interaction.php @@ -13,7 +13,7 @@ use Discord\Builders\Components\Component; use Discord\Builders\MessageBuilder; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Helpers\Multipart; use Discord\Http\Endpoint; use Discord\Parts\Channel\Channel; @@ -610,7 +610,7 @@ public function showModal(string $title, string $custom_id, array $components, ? if ($submit) { $listener = function (Interaction $interaction) use ($custom_id, $submit, &$listener) { if ($interaction->type == self::TYPE_MODAL_SUBMIT && $interaction->data->custom_id == $custom_id) { - $components = Collection::for(RequestComponent::class, 'custom_id'); + $components = ($this->discord->getCollectionClass())::for(RequestComponent::class, 'custom_id'); foreach ($interaction->data->components as $actionrow) { if ($actionrow->type == Component::TYPE_ACTION_ROW) { foreach ($actionrow->components as $component) { diff --git a/src/Discord/Parts/Interactions/Request/Component.php b/src/Discord/Parts/Interactions/Request/Component.php index a6a7a8a8d..9150f12ce 100644 --- a/src/Discord/Parts/Interactions/Request/Component.php +++ b/src/Discord/Parts/Interactions/Request/Component.php @@ -12,7 +12,7 @@ namespace Discord\Parts\Interactions\Request; use Discord\Builders\Components\Component as ComponentBuilder; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Guild\Emoji; use Discord\Parts\Part; @@ -36,7 +36,7 @@ * @property string|null $placeholder Custom placeholder text if nothing is selected; max 150 characters. (Select Menus, Text Inputs) * @property int|null $min_values The minimum number of items that must be chosen; default 1, min 0, max 25. (Select Menus) * @property int|null $max_values The maximum number of items that can be chosen; default 1, max 25. (Select Menus) - * @property Collection|Component[]|null $components A list of child components. (Action Rows) + * @property CollectionInterface|Component[]|null $components A list of child components. (Action Rows) * @property int|null $min_length Minimum input length for a text input. (Text Inputs) * @property int|null $max_length Maximum input length for a text input. (Text Inputs) * @property bool|null $required Whether this component is required to be filled; defaults to `true` (Text Inputs) @@ -69,15 +69,15 @@ class Component extends Part /** * Gets the sub-components of the component. * - * @return Collection|Component[]|null $components + * @return CollectionInterfaceInterface|Component[]|null $components */ - protected function getComponentsAttribute(): ?Collection + protected function getComponentsAttribute(): ?CollectionInterface { if (! isset($this->attributes['components']) && $this->type != ComponentBuilder::TYPE_ACTION_ROW) { return null; } - $components = Collection::for(Component::class, null); + $components = ($this->discord->getCollectionClass())::for(Component::class, null); foreach ($this->attributes['components'] ?? [] as $component) { $components->pushItem($this->createOf(Component::class, $component)); diff --git a/src/Discord/Parts/Interactions/Request/InteractionData.php b/src/Discord/Parts/Interactions/Request/InteractionData.php index b18a2e367..de6aee0a9 100644 --- a/src/Discord/Parts/Interactions/Request/InteractionData.php +++ b/src/Discord/Parts/Interactions/Request/InteractionData.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Interactions\Request; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Interactions\Command\Command; use Discord\Parts\Part; @@ -26,13 +26,13 @@ * @property string $name Name of the invoked command. * @property int $type The type of the invoked command. * @property Resolved|null $resolved Resolved users, members, roles and channels that are relevant. - * @property Collection|Option[]|null $options Parameters and values from the user. + * @property CollectionInterface|Option[]|null $options Parameters and values from the user. * @property string|null $guild_id ID of the guild internally passed from Interaction or ID of the guild the command belongs to. * @property string|null $target_id ID the of user or message targeted by a user or message command. * @property string|null $custom_id Custom ID the component was created for. (Only for Message Component & Modal) * @property int|null $component_type Type of the component. (Only for Message Component) * @property string[]|null $values Values selected in a select menu. (Only for Message Component) - * @property Collection|Component[]|null $components The values submitted by the user. (Only for Modal) + * @property CollectionInterface|Component[]|null $components The values submitted by the user. (Only for Modal) */ class InteractionData extends Part { @@ -58,15 +58,15 @@ class InteractionData extends Part /** * Gets the options of the interaction. * - * @return Collection|Option[]|null $options + * @return CollectionInterfaceInterface|Option[]|null $options */ - protected function getOptionsAttribute(): ?Collection + protected function getOptionsAttribute(): ?CollectionInterface { if (! isset($this->attributes['options']) && $this->type != Command::CHAT_INPUT) { return null; } - $options = Collection::for(Option::class, 'name'); + $options = ($this->discord->getCollectionClass())::for(Option::class, 'name'); foreach ($this->attributes['options'] ?? [] as $option) { $options->pushItem($this->createOf(Option::class, $option)); @@ -78,15 +78,15 @@ protected function getOptionsAttribute(): ?Collection /** * Gets the components of the interaction. * - * @return Collection|Component[]|null $components + * @return CollectionInterfaceInterface|Component[]|null $components */ - protected function getComponentsAttribute(): ?Collection + protected function getComponentsAttribute(): ?CollectionInterface { if (! isset($this->attributes['components'])) { return null; } - $components = Collection::for(Component::class, null); + $components = ($this->discord->getCollectionClass())::for(Component::class, null); foreach ($this->attributes['components'] as $component) { $components->pushItem($this->createOf(Component::class, $component)); diff --git a/src/Discord/Parts/Interactions/Request/Option.php b/src/Discord/Parts/Interactions/Request/Option.php index 05e561280..9a23d936e 100644 --- a/src/Discord/Parts/Interactions/Request/Option.php +++ b/src/Discord/Parts/Interactions/Request/Option.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Interactions\Request; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Interactions\Command\Option as CommandOption; use Discord\Parts\Part; @@ -25,7 +25,7 @@ * @property string $name Name of the parameter. * @property int $type Type of the option. * @property string|int|float|bool|null $value Value of the option resulting from user input. - * @property Collection|Option[]|null $options Present if this option is a group or subcommand. + * @property CollectionInterface|Option[]|null $options Present if this option is a group or subcommand. * @property bool|null $focused `true` if this option is the currently focused option for autocomplete. */ class Option extends Part @@ -44,15 +44,15 @@ class Option extends Part /** * Gets the options of the interaction. * - * @return Collection|Option[]|null $options + * @return CollectionInterfaceInterface|Option[]|null $options */ - protected function getOptionsAttribute(): ?Collection + protected function getOptionsAttribute(): ?CollectionInterface { if (! isset($this->attributes['options']) && ! in_array($this->type, [CommandOption::SUB_COMMAND, CommandOption::SUB_COMMAND_GROUP])) { return null; } - $options = Collection::for(Option::class, 'name'); + $options = ($this->discord->getCollectionClass())::for(Option::class, 'name'); foreach ($this->attributes['options'] ?? [] as $option) { $options->pushItem($this->createOf(Option::class, $option)); diff --git a/src/Discord/Parts/Interactions/Request/Resolved.php b/src/Discord/Parts/Interactions/Request/Resolved.php index 12f7ce7fb..e9cde45eb 100644 --- a/src/Discord/Parts/Interactions/Request/Resolved.php +++ b/src/Discord/Parts/Interactions/Request/Resolved.php @@ -11,7 +11,7 @@ namespace Discord\Parts\Interactions\Request; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Attachment; use Discord\Parts\Channel\Channel; use Discord\Parts\Channel\Message; @@ -28,12 +28,12 @@ * * @since 7.0.0 * - * @property Collection|User[]|null $users The ids and User objects. - * @property Collection|Member[]|null $members The ids and partial Member objects. - * @property Collection|Role[]|null $roles The ids and Role objects. - * @property Collection|Channel[]|Thread[]|null $channels The ids and partial Channel objects. - * @property Collection|Message[]|null $messages The ids and partial Message objects. - * @property Collection|Attachment[]|null $attachments The ids and partial Attachment objects. + * @property CollectionInterface|User[]|null $users The ids and User objects. + * @property CollectionInterface|Member[]|null $members The ids and partial Member objects. + * @property CollectionInterface|Role[]|null $roles The ids and Role objects. + * @property CollectionInterface|Channel[]|Thread[]|null $channels The ids and partial Channel objects. + * @property CollectionInterface|Message[]|null $messages The ids and partial Message objects. + * @property CollectionInterface|Attachment[]|null $attachments The ids and partial Attachment objects. * * @property string|null $guild_id ID of the guild internally passed from Interaction. */ @@ -62,15 +62,15 @@ class Resolved extends Part /** * Returns a collection of resolved users. * - * @return Collection|User[]|null Map of Snowflakes to user objects + * @return CollectionInterfaceInterface|User[]|null Map of Snowflakes to user objects */ - protected function getUsersAttribute(): ?Collection + protected function getUsersAttribute(): ?CollectionInterface { if (! isset($this->attributes['users'])) { return null; } - $collection = Collection::for(User::class); + $collection = ($this->discord->getCollectionClass())::for(User::class); foreach ($this->attributes['users'] as $snowflake => $user) { $collection->pushItem($this->discord->users->get('id', $snowflake) ?: $this->factory->part(User::class, (array) $user, true)); @@ -84,15 +84,15 @@ protected function getUsersAttribute(): ?Collection * * Partial Member objects are missing user, deaf and mute fields * - * @return Collection|Member[]|null Map of Snowflakes to partial member objects + * @return CollectionInterfaceInterface|Member[]|null Map of Snowflakes to partial member objects */ - protected function getMembersAttribute(): ?Collection + protected function getMembersAttribute(): ?CollectionInterface { if (! isset($this->attributes['members'])) { return null; } - $collection = Collection::for(Member::class); + $collection = ($this->discord->getCollectionClass())::for(Member::class); foreach ($this->attributes['members'] as $snowflake => $member) { if ($guild = $this->discord->guilds->get('id', $this->guild_id)) { @@ -113,15 +113,15 @@ protected function getMembersAttribute(): ?Collection /** * Returns a collection of resolved roles. * - * @return Collection|Role[]|null Map of Snowflakes to role objects + * @return CollectionInterfaceInterface|Role[]|null Map of Snowflakes to role objects */ - protected function getRolesAttribute(): ?Collection + protected function getRolesAttribute(): ?CollectionInterface { if (! isset($this->attributes['roles'])) { return null; } - $collection = Collection::for(Role::class); + $collection = ($this->discord->getCollectionClass())::for(Role::class); foreach ($this->attributes['roles'] as $snowflake => $role) { if ($guild = $this->discord->guilds->get('id', $this->guild_id)) { @@ -143,15 +143,15 @@ protected function getRolesAttribute(): ?Collection * * Partial Channel objects only have id, name, type and permissions fields. Threads will also have thread_metadata and parent_id fields. * - * @return Collection|Channel[]|Thread[]|null Map of Snowflakes to partial channel objects + * @return CollectionInterfaceInterface|Channel[]|Thread[]|null Map of Snowflakes to partial channel objects */ - protected function getChannelsAttribute(): ?Collection + protected function getChannelsAttribute(): ?CollectionInterface { if (! isset($this->attributes['channels'])) { return null; } - $collection = new Collection(); + $collection = new ($this->discord->getCollectionClass())(); foreach ($this->attributes['channels'] as $snowflake => $channel) { if ($guild = $this->discord->guilds->get('id', $this->guild_id)) { @@ -175,15 +175,15 @@ protected function getChannelsAttribute(): ?Collection /** * Returns a collection of resolved messages. * - * @return Collection|Message[]|null Map of Snowflakes to partial messages objects + * @return CollectionInterfaceInterface|Message[]|null Map of Snowflakes to partial messages objects */ - protected function getMessagesAttribute(): ?Collection + protected function getMessagesAttribute(): ?CollectionInterface { if (! isset($this->attributes['messages'])) { return null; } - $collection = Collection::for(Message::class); + $collection = ($this->discord->getCollectionClass())::for(Message::class); foreach ($this->attributes['messages'] as $snowflake => $message) { if ($guild = $this->discord->guilds->get('id', $this->guild_id)) { @@ -201,15 +201,15 @@ protected function getMessagesAttribute(): ?Collection /** * Returns a collection of resolved attachments. * - * @return Collection|Attachment[]|null Map of Snowflakes to attachments objects + * @return CollectionInterfaceInterface|Attachment[]|null Map of Snowflakes to attachments objects */ - protected function getAttachmentsAttribute(): ?Collection + protected function getAttachmentsAttribute(): ?CollectionInterface { if (! isset($this->attributes['attachments'])) { return null; } - $attachments = Collection::for(Attachment::class); + $attachments = ($this->discord->getCollectionClass())::for(Attachment::class); foreach ($this->attributes['attachments'] as $attachment) { $attachments->pushItem($this->factory->part(Attachment::class, (array) $attachment, true)); diff --git a/src/Discord/Parts/Thread/Thread.php b/src/Discord/Parts/Thread/Thread.php index 044d5e605..2580940ae 100644 --- a/src/Discord/Parts/Thread/Thread.php +++ b/src/Discord/Parts/Thread/Thread.php @@ -13,7 +13,7 @@ use Carbon\Carbon; use Discord\Builders\MessageBuilder; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Http\Endpoint; use Discord\Http\Exceptions\NoPermissionsException; use Discord\Parts\Channel\Channel; @@ -462,7 +462,7 @@ public function setAutoArchiveDuration(int $duration, ?string $reason = null): P * * @link https://discord.com/developers/docs/resources/channel#get-pinned-messages * - * @return PromiseInterface> + * @return PromiseInterface> * * @todo Make it in a trait along with Channel */ @@ -470,7 +470,7 @@ public function getPinnedMessages(): PromiseInterface { return $this->http->get(Endpoint::bind(Endpoint::CHANNEL_PINS, $this->id)) ->then(function ($responses) { - $messages = Collection::for(Message::class); + $messages = ($this->discord->getCollectionClass())::for(Message::class); foreach ($responses as $response) { $messages->pushItem($this->messages->get('id', $response->id) ?: $this->messages->create($response, true)); @@ -546,7 +546,7 @@ public function deleteMessages($messages, ?string $reason = null): PromiseInterf * @param string|Message|null $options['after'] Get messages after this message ID. * @param int|null $options['limit'] Max number of messages to return (1-100). Defaults to 50. * - * @return PromiseInterface> + * @return PromiseInterface> * * @todo Make it in a trait along with Channel */ @@ -586,7 +586,7 @@ public function getMessageHistory(array $options = []): PromiseInterface } return $this->http->get($endpoint)->then(function ($responses) { - $messages = Collection::for(Message::class); + $messages = ($this->discord->getCollectionClass())::for(Message::class); foreach ($responses as $response) { if (! $message = $this->messages->get('id', $response->id)) { @@ -770,14 +770,14 @@ public function broadcastTyping(): PromiseInterface * @param int $options ['time'] Time in milliseconds until the collector finishes or false. * @param int $options ['limit'] The amount of messages allowed or false. * - * @return PromiseInterface> + * @return PromiseInterface> * * @todo Make it in a trait along with Channel */ public function createMessageCollector(callable $filter, array $options = []): PromiseInterface { $deferred = new Deferred(); - $messages = new Collection([], null, null); + $messages = new ($this->discord->getCollectionClass())([], null, null); $timer = null; $options = array_merge([ diff --git a/src/Discord/Parts/User/Member.php b/src/Discord/Parts/User/Member.php index cdf2088e7..59507da22 100644 --- a/src/Discord/Parts/User/Member.php +++ b/src/Discord/Parts/User/Member.php @@ -14,7 +14,7 @@ use Carbon\Carbon; use Discord\Builders\MessageBuilder; use Discord\Helpers\BigInt; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Http\Endpoint; use Discord\Http\Exceptions\NoPermissionsException; use Discord\Parts\Channel\Channel; @@ -46,7 +46,7 @@ * @property-read string $displayname The nickname or display name with optional discriminator of the member. * @property ?string|null $avatar The avatar URL of the member or null if member has no guild avatar. * @property ?string|null $avatar_hash The avatar hash of the member or null if member has no guild avatar. - * @property Collection|Role[] $roles A collection of Roles that the member has. + * @property CollectionInterface|Role[] $roles A collection of Roles that the member has. * @property Carbon|null $joined_at A timestamp of when the member joined the guild. * @property Carbon|null $premium_since When the user started boosting the server. * @property bool $deaf Whether the member is deaf. @@ -61,7 +61,7 @@ * @property string $id The unique identifier of the member. * @property string $status The status of the member. * @property-read Activity $game The game the member is playing. - * @property Collection|Activity[] $activities User's current activities. + * @property CollectionInterface|Activity[] $activities User's current activities. * @property object $client_status Current client status. * * @method PromiseInterface sendMessage(MessageBuilder $builder) @@ -609,11 +609,11 @@ protected function getGameAttribute(): ?Activity /** * Gets the activities attribute. * - * @return Collection|Activity[] + * @return CollectionInterfaceInterface|Activity[] */ - protected function getActivitiesAttribute(): Collection + protected function getActivitiesAttribute(): CollectionInterface { - $activities = Collection::for(Activity::class, null); + $activities = ($this->discord->getCollectionClass())::for(Activity::class, null); foreach ($this->attributes['activities'] ?? [] as $activity) { $activities->pushItem($this->createOf(Activity::class, $activity)); @@ -685,11 +685,11 @@ protected function getGuildAttribute(): ?Guild /** * Returns the roles attribute. * - * @return Collection A collection of roles the member is in. null role only contains ID in the collection. + * @return CollectionInterfaceInterface A collection of roles the member is in. null role only contains ID in the collection. */ - protected function getRolesAttribute(): Collection + protected function getRolesAttribute(): CollectionInterface { - $roles = new Collection(); + $roles = new ($this->discord->getCollectionClass())(); if (empty($this->attributes['roles'])) { return $roles; diff --git a/src/Discord/Parts/WebSockets/PresenceUpdate.php b/src/Discord/Parts/WebSockets/PresenceUpdate.php index 911e9306b..8c1635824 100644 --- a/src/Discord/Parts/WebSockets/PresenceUpdate.php +++ b/src/Discord/Parts/WebSockets/PresenceUpdate.php @@ -11,7 +11,7 @@ namespace Discord\Parts\WebSockets; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Guild\Guild; use Discord\Parts\Guild\Role; use Discord\Parts\Part; @@ -32,7 +32,7 @@ * @property string $guild_id The unique identifier of the guild that the presence update affects. * @property-read Guild|null $guild The guild that the presence update affects. * @property string $status The updated status of the user. - * @property Collection|Activity[] $activities The activities of the user. + * @property CollectionInterface|Activity[] $activities The activities of the user. * @property-read Activity $game The updated game of the user. * @property object $client_status Status of the client. * @property string|null $desktop_status Status of the user on their desktop client. Null if they are not active on desktop. @@ -40,7 +40,7 @@ * @property string|null $web_status Status of the user on their web client. Null if they are not active on web. * * @property-read Member $member The member that the presence update affects. - * @property-read Collection|Role[] $roles Roles that the user has in the guild. + * @property-read CollectionInterface|Role[] $roles Roles that the user has in the guild. */ class PresenceUpdate extends Part { @@ -93,11 +93,11 @@ protected function getGuildAttribute(): ?Guild /** * Gets the activities attribute. * - * @return Collection|Activity[] + * @return CollectionInterfaceInterface|Activity[] */ - protected function getActivitiesAttribute(): Collection + protected function getActivitiesAttribute(): CollectionInterface { - $collection = Collection::for(Activity::class, null); + $collection = ($this->discord->getCollectionClass())::for(Activity::class, null); foreach ($this->attributes['activities'] ?? [] as $activity) { $collection->pushItem($this->factory->part(Activity::class, (array) $activity, true)); @@ -163,14 +163,14 @@ protected function getMemberAttribute(): ?Member /** * Returns the users roles. * - * @return Collection|Role[] + * @return CollectionInterfaceInterface|Role[] */ - protected function getRolesAttribute(): Collection + protected function getRolesAttribute(): CollectionInterface { if ($member = $this->member) { return $member->roles; } - return Collection::for(Role::class); + return ($this->discord->getCollectionClass())::for(Role::class); } } diff --git a/src/Discord/Repository/AbstractRepository.php b/src/Discord/Repository/AbstractRepository.php index bf526514c..58c6eb837 100755 --- a/src/Discord/Repository/AbstractRepository.php +++ b/src/Discord/Repository/AbstractRepository.php @@ -14,7 +14,8 @@ use Discord\Discord; use Discord\Factory\Factory; use Discord\Helpers\CacheWrapper; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; +use Discord\Helpers\CollectionTrait; use Discord\Helpers\LegacyCacheWrapper; use Discord\Http\Endpoint; use Discord\Http\Http; @@ -38,8 +39,9 @@ * @property string $discrim The discriminator. * @property-read CacheWrapper $cache The react/cache wrapper. */ -abstract class AbstractRepository extends Collection +abstract class AbstractRepository implements CollectionInterface { + use CollectionTrait; /** * The discriminator. * @@ -553,16 +555,16 @@ public function has(...$keys): bool } /** - * Runs a filter callback over the repository and returns a new collection + * Runs a filter callback over the repository and returns a new ($this->discord->getCollectionClass()) * based on the response of the callback. * * @param callable $callback * - * @return Collection + * @return CollectionInterface */ - public function filter(callable $callback): Collection + public function filter(callable $callback): CollectionInterface { - $collection = new Collection([], $this->discrim, $this->class); + $collection = new ($this->discord->getCollectionClass())([], $this->discrim, $this->class); foreach ($this->items as $offset => $item) { if ($item instanceof WeakReference) { diff --git a/src/Discord/Repository/Channel/ThreadRepository.php b/src/Discord/Repository/Channel/ThreadRepository.php index e4dabada2..1b85402bf 100644 --- a/src/Discord/Repository/Channel/ThreadRepository.php +++ b/src/Discord/Repository/Channel/ThreadRepository.php @@ -11,7 +11,7 @@ namespace Discord\Repository\Channel; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Http\Endpoint; use Discord\Parts\Thread\Thread; use Discord\Repository\AbstractRepository; @@ -87,7 +87,7 @@ protected function cacheFreshen($response): PromiseInterface * * @link https://discord.com/developers/docs/resources/channel#list-active-threads * - * @return PromiseInterface> + * @return PromiseInterface> */ public function active(): PromiseInterface { @@ -109,7 +109,7 @@ public function active(): PromiseInterface * * @throws \InvalidArgumentException * - * @return PromiseInterface> + * @return PromiseInterface> */ public function archived(bool $private = false, bool $joined = false, ?int $limit = null, $before = null): PromiseInterface { @@ -150,11 +150,11 @@ public function archived(bool $private = false, bool $joined = false, ?int $limi * * @param object $response * - * @return Collection|Thread[] + * @return CollectionInterfaceInterface|Thread[] */ - private function handleThreadPaginationResponse(object $response): Collection + private function handleThreadPaginationResponse(object $response): CollectionInterface { - $collection = Collection::for(Thread::class); + $collection = ($this->discord->getCollectionClass())::for(Thread::class); foreach ($response->threads as $thread) { /** @var Thread */ diff --git a/src/Discord/Voice/VoiceClient.php b/src/Discord/Voice/VoiceClient.php index 398c30d0f..f01d28fa2 100644 --- a/src/Discord/Voice/VoiceClient.php +++ b/src/Discord/Voice/VoiceClient.php @@ -16,7 +16,7 @@ use Discord\Exceptions\LibSodiumNotFoundException; use Discord\Exceptions\OutdatedDCAException; use Discord\Helpers\Buffer as RealBuffer; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Channel; use Discord\WebSockets\Op; use Evenement\EventEmitter; @@ -371,7 +371,7 @@ public function __construct(WebSocket $websocket, LoopInterface $loop, Channel $ $this->deaf = $data['deaf']; $this->mute = $data['mute']; $this->endpoint = str_replace([':80', ':443'], '', $data['endpoint']); - $this->speakingStatus = new Collection([], 'ssrc'); + $this->speakingStatus = new ($this->discord->getCollectionClass())([], 'ssrc'); $this->dnsConfig = $data['dnsConfig']; } @@ -1341,7 +1341,7 @@ public function close(): void $this->sentLoginFrame = false; $this->startTime = null; $this->streamTime = 0; - $this->speakingStatus = new Collection([], 'ssrc'); + $this->speakingStatus = new ($this->discord->getCollectionClass())([], 'ssrc'); $this->emit('close'); } diff --git a/src/Discord/WebSockets/Events/GuildEmojisUpdate.php b/src/Discord/WebSockets/Events/GuildEmojisUpdate.php index 77dcf0692..ad830ec47 100644 --- a/src/Discord/WebSockets/Events/GuildEmojisUpdate.php +++ b/src/Discord/WebSockets/Events/GuildEmojisUpdate.php @@ -11,7 +11,7 @@ namespace Discord\WebSockets\Events; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\WebSockets\Event; use Discord\Parts\Guild\Emoji; use Discord\Parts\Guild\Guild; @@ -28,8 +28,8 @@ class GuildEmojisUpdate extends Event */ public function handle($data) { - $oldEmojis = Collection::for(Emoji::class); - $emojiParts = Collection::for(Emoji::class); + $oldEmojis = ($this->discord->getCollectionClass())::for(Emoji::class); + $emojiParts = ($this->discord->getCollectionClass())::for(Emoji::class); /** @var ?Guild */ if ($guild = yield $this->discord->guilds->cacheGet($data->guild_id)) { diff --git a/src/Discord/WebSockets/Events/GuildStickersUpdate.php b/src/Discord/WebSockets/Events/GuildStickersUpdate.php index 966bf7455..2e9c9c40a 100644 --- a/src/Discord/WebSockets/Events/GuildStickersUpdate.php +++ b/src/Discord/WebSockets/Events/GuildStickersUpdate.php @@ -11,7 +11,7 @@ namespace Discord\WebSockets\Events; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\WebSockets\Event; use Discord\Parts\Guild\Guild; use Discord\Parts\Guild\Sticker; @@ -28,8 +28,8 @@ class GuildStickersUpdate extends Event */ public function handle($data) { - $oldStickers = Collection::for(Sticker::class); - $stickerParts = Collection::for(Sticker::class); + $oldStickers = ($this->discord->getCollectionClass())::for(Sticker::class); + $stickerParts = ($this->discord->getCollectionClass())::for(Sticker::class); /** @var ?Guild */ if ($guild = yield $this->discord->guilds->cacheGet($data->guild_id)) { diff --git a/src/Discord/WebSockets/Events/MessageDeleteBulk.php b/src/Discord/WebSockets/Events/MessageDeleteBulk.php index 99b91972e..c56d3a2c3 100644 --- a/src/Discord/WebSockets/Events/MessageDeleteBulk.php +++ b/src/Discord/WebSockets/Events/MessageDeleteBulk.php @@ -11,7 +11,7 @@ namespace Discord\WebSockets\Events; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\WebSockets\Event; /** @@ -26,7 +26,7 @@ class MessageDeleteBulk extends Event */ public function handle($data) { - $resolved = new Collection(); + $resolved = new ($this->discord->getCollectionClass())(); foreach ($data->ids as $id) { $event = new MessageDelete($this->discord); diff --git a/src/Discord/WebSockets/Events/ThreadListSync.php b/src/Discord/WebSockets/Events/ThreadListSync.php index 4333274e4..5650277c2 100644 --- a/src/Discord/WebSockets/Events/ThreadListSync.php +++ b/src/Discord/WebSockets/Events/ThreadListSync.php @@ -11,7 +11,7 @@ namespace Discord\WebSockets\Events; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Channel; use Discord\Parts\Guild\Guild; use Discord\Parts\Thread\Thread; @@ -26,7 +26,7 @@ class ThreadListSync extends Event { public function handle($data) { - $threadParts = Collection::for(Thread::class); + $threadParts = ($this->discord->getCollectionClass())::for(Thread::class); /** @var ?Guild */ if ($guild = yield $this->discord->guilds->cacheGet($data->guild_id)) { diff --git a/tests/Parts/Channel/ChannelTest.php b/tests/Parts/Channel/ChannelTest.php index 24d195b38..eb618aad7 100644 --- a/tests/Parts/Channel/ChannelTest.php +++ b/tests/Parts/Channel/ChannelTest.php @@ -13,7 +13,7 @@ use Discord\Builders\MessageBuilder; use Discord\Discord; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Channel; use Discord\Parts\Channel\Message; use Discord\Parts\Channel\Invite; diff --git a/tests/Parts/Channel/Message/EmptyMessageTest.php b/tests/Parts/Channel/Message/EmptyMessageTest.php index 2eb476a8f..944d2a7ba 100644 --- a/tests/Parts/Channel/Message/EmptyMessageTest.php +++ b/tests/Parts/Channel/Message/EmptyMessageTest.php @@ -13,7 +13,7 @@ use Carbon\Carbon; use Discord\Discord; -use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; use Discord\Parts\Channel\Channel; use Discord\Parts\Channel\Message; use Discord\Parts\Embed\Embed; From 62cb15e8810d90d2675bcaf4933c57fa1014da37 Mon Sep 17 00:00:00 2001 From: Valithor Obsidion Date: Tue, 24 Dec 2024 11:10:57 -0500 Subject: [PATCH 3/3] (BC) Separation of AbstractRepository into Trait and Interface (#1269) --- src/Discord/Repository/AbstractRepository.php | 719 +--------------- .../AbstractRepositoryInterface.php | 64 ++ .../Repository/AbstractRepositoryTrait.php | 802 ++++++++++++++++++ 3 files changed, 868 insertions(+), 717 deletions(-) create mode 100644 src/Discord/Repository/AbstractRepositoryInterface.php create mode 100644 src/Discord/Repository/AbstractRepositoryTrait.php diff --git a/src/Discord/Repository/AbstractRepository.php b/src/Discord/Repository/AbstractRepository.php index bf526514c..e6dd14dd9 100755 --- a/src/Discord/Repository/AbstractRepository.php +++ b/src/Discord/Repository/AbstractRepository.php @@ -11,21 +11,8 @@ namespace Discord\Repository; -use Discord\Discord; -use Discord\Factory\Factory; use Discord\Helpers\CacheWrapper; use Discord\Helpers\Collection; -use Discord\Helpers\LegacyCacheWrapper; -use Discord\Http\Endpoint; -use Discord\Http\Http; -use Discord\Parts\Part; -use React\Promise\PromiseInterface; -use Traversable; -use WeakReference; - -use function Discord\nowait; -use function React\Promise\reject; -use function React\Promise\resolve; /** * Repositories provide a way to store and update parts on the Discord server. @@ -40,707 +27,5 @@ */ abstract class AbstractRepository extends Collection { - /** - * The discriminator. - * - * @var string Discriminator. - */ - protected $discrim = 'id'; - - /** - * The HTTP client. - * - * @var Http Client. - */ - protected $http; - - /** - * The parts factory. - * - * @var Factory Parts factory. - */ - protected $factory; - - /** - * Endpoints for interacting with the Discord servers. - * - * @var array Endpoints. - */ - protected $endpoints = []; - - /** - * Variables that are related to the repository. - * - * @var array Variables. - */ - protected $vars = []; - - /** - * @var CacheWrapper - */ - protected $cache; - - /** - * AbstractRepository constructor. - * - * @param Discord $discord - * @param array $vars An array of variables used for the endpoint. - */ - public function __construct(Discord $discord, array $vars = []) - { - $this->http = $discord->getHttpClient(); - $this->factory = $discord->getFactory(); - $this->vars = $vars; - if ($cacheConfig = $discord->getCacheConfig(static::class)) { - $this->cache = new CacheWrapper($discord, $cacheConfig, $this->items, $this->class, $this->vars); - } else { - $this->cache = new LegacyCacheWrapper($discord, $this->items, $this->class); - } - - parent::__construct([], $this->discrim, $this->class); - } - - /** - * Freshens the repository cache. - * - * @param array $queryparams Query string params to add to the request (no validation) - * - * @return PromiseInterface - * - * @throws \Exception - */ - public function freshen(array $queryparams = []): PromiseInterface - { - if (! isset($this->endpoints['all'])) { - return reject(new \Exception('You cannot freshen this repository.')); - } - - $endpoint = new Endpoint($this->endpoints['all']); - $endpoint->bindAssoc($this->vars); - - foreach ($queryparams as $query => $param) { - $endpoint->addQuery($query, $param); - } - - return $this->http->get($endpoint)->then(function ($response) { - foreach ($this->items as $offset => $value) { - if ($value === null) { - unset($this->items[$offset]); - } elseif (! ($this->items[$offset] instanceof WeakReference)) { - $this->items[$offset] = WeakReference::create($value); - } - $this->cache->delete($offset); - } - - return $this->cacheFreshen($response); - }); - } - - /** - * @param object $response - * - * @return PromiseInterface - */ - protected function cacheFreshen($response): PromiseInterface - { - foreach ($response as $value) { - $value = array_merge($this->vars, (array) $value); - $part = $this->factory->create($this->class, $value, true); - $items[$part->{$this->discrim}] = $part; - } - - if (empty($items)) { - return resolve($this); - } - - return $this->cache->setMultiple($items)->then(fn ($success) => $this); - } - - /** - * Builds a new, empty part. - * - * @param array|object $attributes The attributes for the new part. - * @param bool $created - * - * @return Part The new part. - * - * @throws \Exception - */ - public function create(array|object $attributes = [], bool $created = false): Part - { - $attributes = array_merge((array) $attributes, $this->vars); - - return $this->factory->part($this->class, $attributes, $created); - } - - /** - * Attempts to save a part to the Discord servers. - * - * @param Part $part The part to save. - * @param string|null $reason Reason for Audit Log (if supported). - * - * @return PromiseInterface - * - * @throws \Exception - */ - public function save(Part $part, ?string $reason = null): PromiseInterface - { - if ($part->created) { - if (! isset($this->endpoints['update'])) { - return reject(new \Exception('You cannot update this part.')); - } - - $method = 'patch'; - $endpoint = new Endpoint($this->endpoints['update']); - $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); - $attributes = $part->getUpdatableAttributes(); - } else { - if (! isset($this->endpoints['create'])) { - return reject(new \Exception('You cannot create this part.')); - } - - $method = 'post'; - $endpoint = new Endpoint($this->endpoints['create']); - $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); - $attributes = $part->getCreatableAttributes(); - } - - $headers = []; - if (isset($reason)) { - $headers['X-Audit-Log-Reason'] = $reason; - } - - return $this->http->{$method}($endpoint, $attributes, $headers)->then(function ($response) use ($method, $part) { - switch ($method) { - case 'patch': // Update old part - $part->fill((array) $response); - $part->created = true; - return $this->cache->set($part->{$this->discrim}, $part)->then(fn ($success) => $part); - default: // Create new part - $newPart = $this->factory->create($this->class, (array) $response, true); - $newPart->created = true; - return $this->cache->set($newPart->{$this->discrim}, $this->factory->create($this->class, (array) $response, true))->then(fn ($success) => $newPart); - } - }); - } - - /** - * Attempts to delete a part on the Discord servers. - * - * @param Part|string $part The part to delete. - * @param string|null $reason Reason for Audit Log (if supported). - * - * @return PromiseInterface - * - * @throws \Exception - */ - public function delete($part, ?string $reason = null): PromiseInterface - { - if (! isset($part)) { - return reject(new \Exception('You cannot delete a non-existent part.')); - } - - if (! ($part instanceof Part)) { - $part = $this->factory->part($this->class, [$this->discrim => $part], true); - } - - if (! $part->created) { - return reject(new \Exception('You cannot delete a non-existent part.')); - } - - if (! isset($this->endpoints['delete'])) { - return reject(new \Exception('You cannot delete this part.')); - } - - $endpoint = new Endpoint($this->endpoints['delete']); - $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); - - $headers = []; - if (isset($reason)) { - $headers['X-Audit-Log-Reason'] = $reason; - } - - return $this->http->delete($endpoint, null, $headers)->then(function ($response) use (&$part) { - if ($response) { - $part->fill((array) $response); - } - $part->created = false; - - return $this->cache->delete($part->{$this->discrim})->then(fn ($success) => $part); - }); - } - - /** - * Returns a part with fresh values. - * - * @param Part $part The part to get fresh values. - * @param array $queryparams Query string params to add to the request (no validation) - * - * @return PromiseInterface - * - * @throws \Exception - */ - public function fresh(Part $part, array $queryparams = []): PromiseInterface - { - if (! $part->created) { - return reject(new \Exception('You cannot get a non-existent part.')); - } - - if (! isset($this->endpoints['get'])) { - return reject(new \Exception('You cannot get this part.')); - } - - $endpoint = new Endpoint($this->endpoints['get']); - $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); - - foreach ($queryparams as $query => $param) { - $endpoint->addQuery($query, $param); - } - - return $this->http->get($endpoint)->then(function ($response) use (&$part) { - $part->fill((array) $response); - - return $this->cache->set($part->{$this->discrim}, $part)->then(fn ($success) => $part); - }); - } - - /** - * Gets a part from the repository or Discord servers. - * - * @param string $id The ID to search for. - * @param bool $fresh Whether we should skip checking the cache. - * - * @throws \Exception - * - * @return PromiseInterface - */ - public function fetch(string $id, bool $fresh = false): PromiseInterface - { - if (! $fresh) { - if (isset($this->items[$id])) { - $part = $this->items[$id]; - if ($part instanceof WeakReference) { - $part = $part->get(); - } - - if ($part) { - $this->items[$id] = $part; - - return resolve($part); - } - } else { - return $this->cache->get($id)->then(function ($part) use ($id) { - if ($part === null) { - return $this->fetch($id, true); - } - - return $part; - }); - } - } - - if (! isset($this->endpoints['get'])) { - return reject(new \Exception('You cannot get this part.')); - } - - $part = $this->factory->part($this->class, [$this->discrim => $id]); - $endpoint = new Endpoint($this->endpoints['get']); - $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); - - return $this->http->get($endpoint)->then(function ($response) use ($part, $id) { - $part->created = true; - $part->fill(array_merge($this->vars, (array) $response)); - - return $this->cache->set($id, $part)->then(fn ($success) => $part); - }); - } - - /** - * Gets a part from the repository. - * - * @param string $discrim - * @param mixed $key - * - * @return Part|null - */ - public function get(string $discrim, $key) - { - if ($key === null) { - return null; - } - - if ($discrim == $this->discrim) { - if ($item = $this->offsetGet($key)) { - return $item; - } - - // Attempt to get resolved value if promise is resolved without waiting - return nowait($this->cache->get($key)); - } - - foreach ($this->items as $offset => $item) { - if ($item = $this->offsetGet($offset)) { - if ($item->{$discrim} == $key) { - return $item; - } - continue; - } - - $resolved = nowait($this->cache->get($offset)); - if ($resolved !== null) { - return $resolved; - } - break; - } - - return null; - } - - /** - * Attempts to get from memory first otherwise load from cache. - * - * @internal - * - * @param string|int $offset - * - * @return PromiseInterface - */ - public function cacheGet($offset): PromiseInterface - { - return resolve($this->offsetGet($offset) ?? $this->cache->get($offset)); - } - - /** - * Sets a part in the repository. - * - * @param string|int $offset - * @param Part $value - */ - public function set($offset, $value) - { - // Don't insert elements that are not of type class. - if (! is_a($value, $this->class)) { - return; - } - - $this->cache->set($offset, $value); - $this->items[$offset] = $value; - } - - /** - * Pulls a part from the repository. - * - * @deprecated 10.0.0 Use async `$repository->cachePull()` - * - * @param string|int $key - * @param mixed $default - * - * @return Part|mixed - */ - public function pull($key, $default = null) - { - if ($item = $this->offsetGet($key)) { - $default = $item; - unset($this->items[$key]); - $this->cache->delete($key); - } - - return $default; - } - - /** - * Pulls an item from cache. - * - * @internal - * - * @param string|int $key - * @param ?Part $default - * - * @return PromiseInterface - */ - public function cachePull($key, $default = null): PromiseInterface - { - return $this->cacheGet($key)->then(fn ($item) => ($item === null) ? $default : $this->cache->delete($key)->then(fn ($success) => $item)); - } - - /** - * Pushes a single item to the repository. - * - * @deprecated 10.0.0 Use async `$repository->cache->set()` - * This method is deprecated for userland code but can still be used internally within the library. - * - * @param Part $item - * - * @return $this - */ - public function pushItem($item): self - { - if (is_a($item, $this->class)) { - $key = $item->{$this->discrim}; - $this->items[$key] = $item; - $this->cache->set($key, $item); - } - - return $this; - } - - /** - * Returns the first cached part. - * - * @return Part|null - */ - public function first() - { - foreach ($this->items as $offset => $item) { - if ($item instanceof WeakReference) { - if (! $item = $item->get()) { - // Attempt to get resolved value if promise is resolved without waiting - $item = nowait($this->cache->get($offset)); - } - } - - if ($item) { - return $item; - } - } - - return null; - } - - /** - * Returns the last cached part. - * - * @return Part|null - */ - public function last() - { - $items = array_reverse($this->items, true); - - foreach ($items as $offset => $item) { - if ($item instanceof WeakReference) { - if (! $item = $item->get()) { - // Attempt to get resolved value if promise is resolved without waiting - $item = nowait($this->cache->get($offset)); - } - } - - if ($item) { - return $item; - } - } - - return null; - } - - /** - * Checks if the array has an object. - * - * @deprecated 10.0.0 Use async `$repository->cache->has()` - * - * @param string|int ...$keys - * - * @return bool - */ - public function has(...$keys): bool - { - foreach ($keys as $key) { - if (! isset($this->items[$key]) || nowait($this->cache->has($key)) === false) { - return false; - } - } - - return true; - } - - /** - * Runs a filter callback over the repository and returns a new collection - * based on the response of the callback. - * - * @param callable $callback - * - * @return Collection - */ - public function filter(callable $callback): Collection - { - $collection = new Collection([], $this->discrim, $this->class); - - foreach ($this->items as $offset => $item) { - if ($item instanceof WeakReference) { - if (! $item = $item->get()) { - // Attempt to get resolved value if promise is resolved without waiting - $item = nowait($this->cache->get($offset)); - } - } - - if ($item === null) { - continue; - } - - if ($callback($item)) { - $collection->push($item); - } - } - - return $collection; - } - - /** - * Runs a filter callback over the repository and returns the first part - * where the callback returns `true` when given the part. - * - * @param callable $callback - * - * @return Part|null `null` if no items returns `true` when called in the callback. - */ - public function find(callable $callback) - { - foreach ($this->getIterator() as $item) { - if ($item === null) { - continue; - } - - if ($callback($item)) { - return $item; - } - } - - return null; - } - - /** - * Clears the repository. - * - * @deprecated 10.0.0 Use async `$repository->cache->clear()` - */ - public function clear(): void - { - // Set items null but keep the keys to be removed on prune - $this->items = array_fill_keys(array_keys($this->items), null); - } - - /** - * Converts the weak caches to array. - * - * @return array - */ - public function toArray(): array - { - $items = []; - - foreach ($this->items as $offset => $item) { - if ($item instanceof WeakReference) { - $item = $item->get(); - } - $items[$offset] = $item; - } - - return $items; - } - - /** - * Get the keys of the items. - * - * @return int[]|string[] - */ - public function keys(): array - { - return array_keys($this->items); - } - - /** - * If the repository has an offset. - * - * @deprecated 10.0.0 Use async `$repository->cache->has()` - * This method is deprecated for userland code but can still be used internally within the library. - * - * @param string|int $offset - * - * @return bool - */ - public function offsetExists($offset): bool - { - return parent::offsetExists($offset); - } - - /** - * Gets a part from the repository. - * - * @deprecated 10.0.0 Use async `$repository->cacheGet()` or sync `$repository->get()` - * This method is deprecated for userland code but can still be used internally within the library. - * - * @param string|int $offset - * - * @return Part|null - */ - public function offsetGet($offset) - { - $item = parent::offsetGet($offset); - - if ($item instanceof WeakReference) { - $item = $item->get(); - } - - if ($item) { - return $this->items[$offset] = $item; - } - - return null; - } - - /** - * Sets a part into the repository. - * - * @deprecated 10.0.0 Use async `$repository->cache->set()` - * - * @param string|int $offset - * @param ?Part $value - */ - public function offsetSet($offset, $value): void - { - parent::offsetSet($offset, $value); - } - - /** - * Unsets an index from the repository. - * - * @deprecated 10.0.0 Use async `$repository->cache->delete()` - * - * @param string|int $offset - */ - public function offsetUnset($offset): void - { - parent::offsetUnset($offset); - } - - /** - * {@inheritDoc} - */ - public function jsonSerialize(): array - { - return $this->toArray(); - } - - /** - * Returns an iterator for the cache. - * - * @return Traversable - */ - public function &getIterator(): Traversable - { - foreach ($this->items as $offset => &$item) { - if ($item instanceof WeakReference) { - // Attempt to get resolved value if promise is resolved without waiting - $item = $item->get() ?? nowait($this->cache->get($offset)); - } - - yield $offset => $item; - } - } - - public function __get(string $key) - { - if (in_array($key, ['discrim', 'cache'])) { - return $this->$key; - } - } -} + use AbstractRepositoryTrait; +} \ No newline at end of file diff --git a/src/Discord/Repository/AbstractRepositoryInterface.php b/src/Discord/Repository/AbstractRepositoryInterface.php new file mode 100644 index 000000000..b821a64de --- /dev/null +++ b/src/Discord/Repository/AbstractRepositoryInterface.php @@ -0,0 +1,64 @@ + + * + * This file is subject to the MIT license that is bundled + * with this source code in the LICENSE.md file. + */ + +namespace Discord\Repository; + +use Discord\Helpers\CollectionInterface; +use Discord\Discord; +use Discord\Helpers\Collection; +use Discord\Parts\Part; +use React\Promise\PromiseInterface; +use Traversable; + +interface AbstractRepositoryInterface extends CollectionInterface +{ + public function __construct(Discord $discord, array $vars = []); + public function freshen(array $queryparams = []): PromiseInterface; + public function create(array|object $attributes = [], bool $created = false): Part; + public function save(Part $part, ?string $reason = null): PromiseInterface; + public function delete($part, ?string $reason = null): PromiseInterface; + public function fresh(Part $part, array $queryparams = []): PromiseInterface; + public function fetch(string $id, bool $fresh = false): PromiseInterface; + public function get(string $discrim, $key); + public function cacheGet($offset): PromiseInterface; + public function set($offset, $value); + public function pull($key, $default = null); + public function cachePull($key, $default = null): PromiseInterface; + public function pushItem($item): self; + public function first(); + public function last(); + public function has(...$keys): bool; + public function filter(callable $callback): Collection; + public function find(callable $callback); + public function clear(): void; + public function toArray(): array; + public function keys(): array; + public function offsetExists($offset): bool; + #[\ReturnTypeWillChange] + public function offsetGet($offset); + public function offsetSet($offset, $value): void; + public function offsetUnset($offset): void; + public function jsonSerialize(): array; + public function &getIterator(): Traversable; + public function __get(string $key); + + // Methods imported from CollectionTrait + public function fill(array $items): static; + public function push(...$items): static; + public function isset($offset): bool; + public function map(callable $callback): static; + public function merge($collection): static; + public function serialize(): string; + public function __serialize(): array; + public function unserialize(string $serialized): void; + public function __unserialize(array $data): void; + public function __debugInfo(): array; +} diff --git a/src/Discord/Repository/AbstractRepositoryTrait.php b/src/Discord/Repository/AbstractRepositoryTrait.php new file mode 100644 index 000000000..679e5cc35 --- /dev/null +++ b/src/Discord/Repository/AbstractRepositoryTrait.php @@ -0,0 +1,802 @@ + + * + * This file is subject to the MIT license that is bundled + * with this source code in the LICENSE.md file. + */ + +namespace Discord\Repository; + +use Discord\Discord; +use Discord\Factory\Factory; +use Discord\Helpers\CacheWrapper; +use Discord\Helpers\Collection; +use Discord\Helpers\CollectionInterface; +use Discord\Helpers\CollectionTrait; +use Discord\Helpers\LegacyCacheWrapper; +use Discord\Http\Endpoint; +use Discord\Http\Http; +use Discord\Parts\Part; +use React\Promise\PromiseInterface; +use Traversable; +use WeakReference; + +use function Discord\nowait; +use function React\Promise\reject; +use function React\Promise\resolve; + +trait AbstractRepositoryTrait +{ + use CollectionTrait + { + get as get; + fill as fill; + push as push; + isset as isset; + map as map; + merge as merge; + serialize as serialize; + __serialize as __serialize; + unserialize as unserialize; + __unserialize as __unserialize; + __debugInfo as __debugInfo; + + // 'Parent' methods + __construct as __Collection____construct; + freshen as __Collection__freshen; + create as __Collection__create; + save as __Collection__save; + delete as __Collection__delete; + fresh as __Collection__fresh; + fetch as __Collection__fetch; + get as __Collection__get; + cacheGet as __Collection__cacheGet; + set as __Collection__set; + pull as __Collection__pull; + cachePull as __Collection__cachePull; + pushItem as __Collection__pushItem; + first as __Collection__first; + last as __Collection__last; + has as __Collection__has; + filter as __Collection__filter; + find as __Collection__find; + clear as __Collection__clear; + toArray as __Collection__toArray; + keys as __Collection__keys; + offsetExists as __Collection__offsetExists; + offsetGet as __Collection__offsetGet; + offsetSet as __Collection__offsetSet; + offsetUnset as __Collection__offsetUnset; + jsonSerialize as __Collection__jsonSerialize; + getIterator as __Collection__getIterator; + __get as __Collection____get; + } + /** + * The collection discriminator. + * + * @var string Discriminator. + */ + protected $discrim = 'id'; + + /** + * The items contained in the collection. + * + * @var array + */ + protected $items = []; + + /** + * Class type allowed into the collection. + * + * @var string + */ + protected $class; + + /** + * The HTTP client. + * + * @var Http Client. + */ + protected $http; + + /** + * The parts factory. + * + * @var Factory Parts factory. + */ + protected $factory; + + /** + * Endpoints for interacting with the Discord servers. + * + * @var array Endpoints. + */ + protected $endpoints = []; + + /** + * Variables that are related to the repository. + * + * @var array Variables. + */ + protected $vars = []; + + /** + * @var CacheWrapper + */ + protected $cache; + + /** + * AbstractRepository constructor. + * + * @param Discord $discord + * @param array $vars An array of variables used for the endpoint. + */ + public function __construct(Discord $discord, array $vars = []) + { + $this->http = $discord->getHttpClient(); + $this->factory = $discord->getFactory(); + $this->vars = $vars; + if ($cacheConfig = $discord->getCacheConfig(static::class)) { + $this->cache = new CacheWrapper($discord, $cacheConfig, $this->items, $this->class, $this->vars); + } else { + $this->cache = new LegacyCacheWrapper($discord, $this->items, $this->class); + } + } + + /** + * Freshens the repository cache. + * + * @param array $queryparams Query string params to add to the request (no validation) + * + * @return PromiseInterface + * + * @throws \Exception + */ + public function freshen(array $queryparams = []): PromiseInterface + { + if (! isset($this->endpoints['all'])) { + return reject(new \Exception('You cannot freshen this repository.')); + } + + $endpoint = new Endpoint($this->endpoints['all']); + $endpoint->bindAssoc($this->vars); + + foreach ($queryparams as $query => $param) { + $endpoint->addQuery($query, $param); + } + + return $this->http->get($endpoint)->then(function ($response) { + foreach ($this->items as $offset => $value) { + if ($value === null) { + unset($this->items[$offset]); + } elseif (! ($this->items[$offset] instanceof WeakReference)) { + $this->items[$offset] = WeakReference::create($value); + } + $this->cache->delete($offset); + } + + return $this->cacheFreshen($response); + }); + } + + /** + * @param object $response + * + * @return PromiseInterface + */ + protected function cacheFreshen($response): PromiseInterface + { + foreach ($response as $value) { + $value = array_merge($this->vars, (array) $value); + $part = $this->factory->create($this->class, $value, true); + $items[$part->{$this->discrim}] = $part; + } + + if (empty($items)) { + return resolve($this); + } + + return $this->cache->setMultiple($items)->then(fn ($success) => $this); + } + + /** + * Builds a new, empty part. + * + * @param array|object $attributes The attributes for the new part. + * @param bool $created + * + * @return Part The new part. + * + * @throws \Exception + */ + public function create(array|object $attributes = [], bool $created = false): Part + { + $attributes = array_merge((array) $attributes, $this->vars); + + return $this->factory->part($this->class, $attributes, $created); + } + + /** + * Attempts to save a part to the Discord servers. + * + * @param Part $part The part to save. + * @param string|null $reason Reason for Audit Log (if supported). + * + * @return PromiseInterface + * + * @throws \Exception + */ + public function save(Part $part, ?string $reason = null): PromiseInterface + { + if ($part->created) { + if (! isset($this->endpoints['update'])) { + return reject(new \Exception('You cannot update this part.')); + } + + $method = 'patch'; + $endpoint = new Endpoint($this->endpoints['update']); + $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); + $attributes = $part->getUpdatableAttributes(); + } else { + if (! isset($this->endpoints['create'])) { + return reject(new \Exception('You cannot create this part.')); + } + + $method = 'post'; + $endpoint = new Endpoint($this->endpoints['create']); + $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); + $attributes = $part->getCreatableAttributes(); + } + + $headers = []; + if (isset($reason)) { + $headers['X-Audit-Log-Reason'] = $reason; + } + + return $this->http->{$method}($endpoint, $attributes, $headers)->then(function ($response) use ($method, $part) { + switch ($method) { + case 'patch': // Update old part + $part->fill((array) $response); + $part->created = true; + return $this->cache->set($part->{$this->discrim}, $part)->then(fn ($success) => $part); + default: // Create new part + $newPart = $this->factory->create($this->class, (array) $response, true); + $newPart->created = true; + return $this->cache->set($newPart->{$this->discrim}, $this->factory->create($this->class, (array) $response, true))->then(fn ($success) => $newPart); + } + }); + } + + /** + * Attempts to delete a part on the Discord servers. + * + * @param Part|string $part The part to delete. + * @param string|null $reason Reason for Audit Log (if supported). + * + * @return PromiseInterface + * + * @throws \Exception + */ + public function delete($part, ?string $reason = null): PromiseInterface + { + if (! isset($part)) { + return reject(new \Exception('You cannot delete a non-existent part.')); + } + + if (! ($part instanceof Part)) { + $part = $this->factory->part($this->class, [$this->discrim => $part], true); + } + + if (! $part->created) { + return reject(new \Exception('You cannot delete a non-existent part.')); + } + + if (! isset($this->endpoints['delete'])) { + return reject(new \Exception('You cannot delete this part.')); + } + + $endpoint = new Endpoint($this->endpoints['delete']); + $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); + + $headers = []; + if (isset($reason)) { + $headers['X-Audit-Log-Reason'] = $reason; + } + + return $this->http->delete($endpoint, null, $headers)->then(function ($response) use (&$part) { + if ($response) { + $part->fill((array) $response); + } + $part->created = false; + + return $this->cache->delete($part->{$this->discrim})->then(fn ($success) => $part); + }); + } + + /** + * Returns a part with fresh values. + * + * @param Part $part The part to get fresh values. + * @param array $queryparams Query string params to add to the request (no validation) + * + * @return PromiseInterface + * + * @throws \Exception + */ + public function fresh(Part $part, array $queryparams = []): PromiseInterface + { + if (! $part->created) { + return reject(new \Exception('You cannot get a non-existent part.')); + } + + if (! isset($this->endpoints['get'])) { + return reject(new \Exception('You cannot get this part.')); + } + + $endpoint = new Endpoint($this->endpoints['get']); + $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); + + foreach ($queryparams as $query => $param) { + $endpoint->addQuery($query, $param); + } + + return $this->http->get($endpoint)->then(function ($response) use (&$part) { + $part->fill((array) $response); + + return $this->cache->set($part->{$this->discrim}, $part)->then(fn ($success) => $part); + }); + } + + /** + * Gets a part from the repository or Discord servers. + * + * @param string $id The ID to search for. + * @param bool $fresh Whether we should skip checking the cache. + * + * @throws \Exception + * + * @return PromiseInterface + */ + public function fetch(string $id, bool $fresh = false): PromiseInterface + { + if (! $fresh) { + if (isset($this->items[$id])) { + $part = $this->items[$id]; + if ($part instanceof WeakReference) { + $part = $part->get(); + } + + if ($part) { + $this->items[$id] = $part; + + return resolve($part); + } + } else { + return $this->cache->get($id)->then(function ($part) use ($id) { + if ($part === null) { + return $this->fetch($id, true); + } + + return $part; + }); + } + } + + if (! isset($this->endpoints['get'])) { + return reject(new \Exception('You cannot get this part.')); + } + + $part = $this->factory->part($this->class, [$this->discrim => $id]); + $endpoint = new Endpoint($this->endpoints['get']); + $endpoint->bindAssoc(array_merge($part->getRepositoryAttributes(), $this->vars)); + + return $this->http->get($endpoint)->then(function ($response) use ($part, $id) { + $part->created = true; + $part->fill(array_merge($this->vars, (array) $response)); + + return $this->cache->set($id, $part)->then(fn ($success) => $part); + }); + } + + /** + * Gets a part from the repository. + * + * @param string $discrim + * @param mixed $key + * + * @return Part|null + */ + public function get(string $discrim, $key) + { + if ($key === null) { + return null; + } + + if ($discrim == $this->discrim) { + if ($item = $this->offsetGet($key)) { + return $item; + } + + // Attempt to get resolved value if promise is resolved without waiting + return nowait($this->cache->get($key)); + } + + foreach ($this->items as $offset => $item) { + if ($item = $this->offsetGet($offset)) { + if ($item->{$discrim} == $key) { + return $item; + } + continue; + } + + $resolved = nowait($this->cache->get($offset)); + if ($resolved !== null) { + return $resolved; + } + break; + } + + return null; + } + + /** + * Attempts to get from memory first otherwise load from cache. + * + * @internal + * + * @param string|int $offset + * + * @return PromiseInterface + */ + public function cacheGet($offset): PromiseInterface + { + return resolve($this->offsetGet($offset) ?? $this->cache->get($offset)); + } + + /** + * Sets a part in the repository. + * + * @param string|int $offset + * @param Part $value + */ + public function set($offset, $value) + { + // Don't insert elements that are not of type class. + if (! is_a($value, $this->class)) { + return; + } + + $this->cache->set($offset, $value); + $this->items[$offset] = $value; + } + + /** + * Pulls a part from the repository. + * + * @deprecated 10.0.0 Use async `$repository->cachePull()` + * + * @param string|int $key + * @param mixed $default + * + * @return Part|mixed + */ + public function pull($key, $default = null) + { + if ($item = $this->offsetGet($key)) { + $default = $item; + unset($this->items[$key]); + $this->cache->delete($key); + } + + return $default; + } + + /** + * Pulls an item from cache. + * + * @internal + * + * @param string|int $key + * @param ?Part $default + * + * @return PromiseInterface + */ + public function cachePull($key, $default = null): PromiseInterface + { + return $this->cacheGet($key)->then(fn ($item) => ($item === null) ? $default : $this->cache->delete($key)->then(fn ($success) => $item)); + } + + /** + * Pushes a single item to the repository. + * + * @deprecated 10.0.0 Use async `$repository->cache->set()` + * This method is deprecated for userland code but can still be used internally within the library. + * + * @param Part $item + * + * @return $this + */ + public function pushItem($item): self + { + if (is_a($item, $this->class)) { + $key = $item->{$this->discrim}; + $this->items[$key] = $item; + $this->cache->set($key, $item); + } + + return $this; + } + + /** + * Returns the first cached part. + * + * @return Part|null + */ + public function first() + { + foreach ($this->items as $offset => $item) { + if ($item instanceof WeakReference) { + if (! $item = $item->get()) { + // Attempt to get resolved value if promise is resolved without waiting + $item = nowait($this->cache->get($offset)); + } + } + + if ($item) { + return $item; + } + } + + return null; + } + + /** + * Returns the last cached part. + * + * @return Part|null + */ + public function last() + { + $items = array_reverse($this->items, true); + + foreach ($items as $offset => $item) { + if ($item instanceof WeakReference) { + if (! $item = $item->get()) { + // Attempt to get resolved value if promise is resolved without waiting + $item = nowait($this->cache->get($offset)); + } + } + + if ($item) { + return $item; + } + } + + return null; + } + + /** + * Checks if the array has an object. + * + * @deprecated 10.0.0 Use async `$repository->cache->has()` + * + * @param string|int ...$keys + * + * @return bool + */ + public function has(...$keys): bool + { + foreach ($keys as $key) { + if (! isset($this->items[$key]) || nowait($this->cache->has($key)) === false) { + return false; + } + } + + return true; + } + + /** + * Runs a filter callback over the repository and returns a new collection + * based on the response of the callback. + * + * @param callable $callback + * + * @return CollectionInterface + */ + public function filter(callable $callback): CollectionInterface + { + $collection = new Collection([], $this->discrim, $this->class); + + foreach ($this->items as $offset => $item) { + if ($item instanceof WeakReference) { + if (! $item = $item->get()) { + // Attempt to get resolved value if promise is resolved without waiting + $item = nowait($this->cache->get($offset)); + } + } + + if ($item === null) { + continue; + } + + if ($callback($item)) { + $collection->push($item); + } + } + + return $collection; + } + + /** + * Runs a filter callback over the repository and returns the first part + * where the callback returns `true` when given the part. + * + * @param callable $callback + * + * @return Part|null `null` if no items returns `true` when called in the callback. + */ + public function find(callable $callback) + { + foreach ($this->getIterator() as $item) { + if ($item === null) { + continue; + } + + if ($callback($item)) { + return $item; + } + } + + return null; + } + + /** + * Clears the repository. + * + * @deprecated 10.0.0 Use async `$repository->cache->clear()` + */ + public function clear(): void + { + // Set items null but keep the keys to be removed on prune + $this->items = array_fill_keys(array_keys($this->items), null); + } + + /** + * Converts the weak caches to array. + * + * @return array + */ + public function toArray(): array + { + $items = []; + + foreach ($this->items as $offset => $item) { + if ($item instanceof WeakReference) { + $item = $item->get(); + } + $items[$offset] = $item; + } + + return $items; + } + + /** + * Get the keys of the items. + * + * @return int[]|string[] + */ + public function keys(): array + { + return array_keys($this->items); + } + + /** + * If the repository has an offset. + * + * @deprecated 10.0.0 Use async `$repository->cache->has()` + * This method is deprecated for userland code but can still be used internally within the library. + * + * @param string|int $offset + * + * @return bool + */ + public function offsetExists($offset): bool + { + return $this->__CollectionoffsetExists($offset); + } + + /** + * Gets a part from the repository. + * + * @deprecated 10.0.0 Use async `$repository->cacheGet()` or sync `$repository->get()` + * This method is deprecated for userland code but can still be used internally within the library. + * + * @param string|int $offset + * + * @return Part|null + */ + public function offsetGet($offset) + { + $item = $this->__CollectionoffsetGet($offset); + + if ($item instanceof WeakReference) { + $item = $item->get(); + } + + if ($item) { + return $this->items[$offset] = $item; + } + + return null; + } + + /** + * Sets a part into the repository. + * + * @deprecated 10.0.0 Use async `$repository->cache->set()` + * + * @param string|int $offset + * @param ?Part $value + */ + public function offsetSet($offset, $value): void + { + $this->__CollectionoffsetSet($offset, $value); + } + + /** + * Unsets an index from the repository. + * + * @deprecated 10.0.0 Use async `$repository->cache->delete()` + * + * @param string|int $offset + */ + public function offsetUnset($offset): void + { + $this->__CollectionoffsetUnset($offset); + } + + /** + * {@inheritDoc} + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * Returns an iterator for the cache. + * + * @return Traversable + */ + public function &getIterator(): Traversable + { + foreach ($this->items as $offset => &$item) { + if ($item instanceof WeakReference) { + // Attempt to get resolved value if promise is resolved without waiting + $item = $item->get() ?? nowait($this->cache->get($offset)); + } + + yield $offset => $item; + } + } + + public function __get(string $key) + { + if (in_array($key, ['discrim', 'cache'])) { + return $this->$key; + } + } + + public function __call($name, $arguments) + { + if (method_exists(CollectionTrait::class, $name)) { + return (new CollectionTrait)->$name(...$arguments); + } + + throw new \BadMethodCallException("Method {$name} does not exist."); + } +}