›
byrcsc/laravel-mentions · 1.x
Add a different mention format, target lookup, or mention record model.
Use an extension point when the shipped text formats or column lookup cannot express your application's mention rules. Laravel's container creates the configured classes.
A parser accepts one string and returns MentionCandidate objects. This parser
recognizes #topic handles:
namespace App\Mentions;
use Byrcsc\Mentions\Contracts\MentionParser;
use Byrcsc\Mentions\ValueObjects\MentionCandidate;
final class TopicMentionParser implements MentionParser
{
public function parse(string $text): array
{
preg_match_all('/#(?<handle>[A-Za-z0-9_]+)/', $text, $matches);
return array_map(
fn (string $handle): MentionCandidate =>
MentionCandidate::fromHandle("#{$handle}", $handle),
array_values(array_unique($matches['handle'])),
);
}
}Register and select it:
'default_parser' => 'topics',
'parsers' => [
'topics' => App\Mentions\TopicMentionParser::class,
],MentionCandidate::fromHandle() takes the raw matched text first and the
handle without its trigger second.
A resolver receives the remaining candidate batch and returns matched and unmatched values:
namespace App\Mentions;
use App\Models\User;
use Byrcsc\Mentions\Contracts\MentionResolver;
use Byrcsc\Mentions\ValueObjects\ResolutionResult;
use Byrcsc\Mentions\ValueObjects\ResolvedMention;
use Illuminate\Database\Eloquent\Builder;
final class ActiveUserResolver implements MentionResolver
{
public function resolve(array $candidates): ResolutionResult
{
$handles = collect($candidates)
->pluck('handle')
->filter(fn (mixed $handle): bool => is_string($handle))
->map(fn (string $handle): string => mb_strtolower($handle))
->unique()
->values()
->all();
if ($handles === []) {
return new ResolutionResult([], $candidates);
}
$users = User::query()
->where('active', true)
->where(function (Builder $query) use ($handles): void {
foreach ($handles as $handle) {
$query->orWhereLike(
'username',
$handle,
caseSensitive: false,
);
}
})
->get()
->keyBy(fn (User $user): string => mb_strtolower($user->username));
$resolved = [];
$unresolved = [];
foreach ($candidates as $candidate) {
$user = $candidate->handle === null
? null
: $users->get(mb_strtolower($candidate->handle));
if ($user === null) {
$unresolved[] = $candidate;
} else {
$resolved[] = new ResolvedMention($candidate, $user);
}
}
return new ResolutionResult($resolved, $unresolved);
}
}Register the resolver by class name:
'resolvers' => [
'active-users' => App\Mentions\ActiveUserResolver::class,
],Laravel's container resolves constructor dependencies when your resolver needs application services.
A class-string definition resolves handles but supplies no model mapping for markup IDs. Use the expanded resolver definition when both paths are needed.
Extend the package model:
namespace App\Models;
use Byrcsc\Mentions\Models\Mention as BaseMention;
class Mention extends BaseMention
{
protected $casts = [
'created_at' => 'immutable_datetime',
];
}Configure the subclass:
'model' => App\Models\Mention::class,The configured class must extend Byrcsc\Mentions\Models\Mention. Package
relationships and synchronization resolve the base class from the container,
so returned records use your subclass.