Refactor models to use new attribute cast

This commit is contained in:
Jonny Barnes 2022-11-26 10:50:19 +00:00
parent a8de52077f
commit cfca6a1de5
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
9 changed files with 218 additions and 292 deletions

View file

@ -75,7 +75,7 @@ class MicropubMediaController extends Controller
return [ return [
'url' => $mediaItem->url, 'url' => $mediaItem->url,
'published' => $mediaItem->created_at->toW3cString(), 'published' => $mediaItem->created_at->toW3cString(),
'mime_type' => $mediaItem->getMimeType(), 'mime_type' => $mediaItem->mimetype,
]; ];
}); });

View file

@ -6,6 +6,7 @@ namespace App\Models;
use Cviebrock\EloquentSluggable\Sluggable; use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -58,70 +59,54 @@ class Article extends Model
*/ */
protected $guarded = ['id']; protected $guarded = ['id'];
/** protected function html(): Attribute
* Process the article for display.
*
* @return string
*/
public function getHtmlAttribute(): string
{ {
$environment = new Environment(); return Attribute::get(
$environment->addExtension(new CommonMarkCoreExtension()); get: function () {
$environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); $environment = new Environment();
$environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); $environment->addExtension(new CommonMarkCoreExtension());
$markdownConverter = new MarkdownConverter($environment); $environment->addRenderer(FencedCode::class, new FencedCodeRenderer());
$environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer());
$markdownConverter = new MarkdownConverter($environment);
return $markdownConverter->convert($this->main)->getContent(); return $markdownConverter->convert($this->main)->getContent();
},
);
} }
/** protected function w3cTime(): Attribute
* Convert updated_at to W3C time format.
*
* @return string
*/
public function getW3cTimeAttribute(): string
{ {
return $this->updated_at->toW3CString(); return Attribute::get(
get: fn () => $this->updated_at->toW3CString(),
);
} }
/** protected function tooltipTime(): Attribute
* Convert updated_at to a tooltip appropriate format.
*
* @return string
*/
public function getTooltipTimeAttribute(): string
{ {
return $this->updated_at->toRFC850String(); return Attribute::get(
get: fn () => $this->updated_at->toRFC850String(),
);
} }
/** protected function humanTime(): Attribute
* Convert updated_at to a human readable format.
*
* @return string
*/
public function getHumanTimeAttribute(): string
{ {
return $this->updated_at->diffForHumans(); return Attribute::get(
get: fn () => $this->updated_at->diffForHumans(),
);
} }
/** protected function pubdate(): Attribute
* Get the pubdate value for RSS feeds.
*
* @return string
*/
public function getPubdateAttribute(): string
{ {
return $this->updated_at->toRSSString(); return Attribute::get(
get: fn () => $this->updated_at->toRSSString(),
);
} }
/** protected function link(): Attribute
* A link to the article, i.e. `/blog/1999/12/25/merry-christmas`.
*
* @return string
*/
public function getLinkAttribute(): string
{ {
return '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl; return Attribute::get(
get: fn () => '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl,
);
} }
/** /**
@ -134,7 +119,7 @@ class Article extends Model
*/ */
public function scopeDate(Builder $query, int $year = null, int $month = null): Builder public function scopeDate(Builder $query, int $year = null, int $month = null): Builder
{ {
if ($year == null) { if ($year === null) {
return $query; return $query;
} }
$start = $year . '-01-01 00:00:00'; $start = $year . '-01-01 00:00:00';

View file

@ -4,43 +4,11 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Carbon;
/**
* App\Models\Bookmark.
*
* @property int $id
* @property string $url
* @property string|null $name
* @property string|null $content
* @property string|null $screenshot
* @property string|null $archive
* @property array|null $syndicates
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read string $longurl
* @property-read Collection|Tag[] $tags
* @property-read int|null $tags_count
*
* @method static Builder|Bookmark newModelQuery()
* @method static Builder|Bookmark newQuery()
* @method static Builder|Bookmark query()
* @method static Builder|Bookmark whereArchive($value)
* @method static Builder|Bookmark whereContent($value)
* @method static Builder|Bookmark whereCreatedAt($value)
* @method static Builder|Bookmark whereId($value)
* @method static Builder|Bookmark whereName($value)
* @method static Builder|Bookmark whereScreenshot($value)
* @method static Builder|Bookmark whereSyndicates($value)
* @method static Builder|Bookmark whereUpdatedAt($value)
* @method static Builder|Bookmark whereUrl($value)
* @mixin Eloquent
*/
class Bookmark extends Model class Bookmark extends Model
{ {
use HasFactory; use HasFactory;
@ -71,13 +39,10 @@ class Bookmark extends Model
return $this->belongsToMany('App\Models\Tag'); return $this->belongsToMany('App\Models\Tag');
} }
/** protected function longurl(): Attribute
* The full url of a bookmark.
*
* @return string
*/
public function getLongurlAttribute(): string
{ {
return config('app.url') . '/bookmarks/' . $this->id; return Attribute::get(
get: fn () => config('app.url') . '/bookmarks/' . $this->id,
);
} }
} }

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use App\Traits\FilterHtml; use App\Traits\FilterHtml;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
@ -17,46 +18,38 @@ class Like extends Model
protected $fillable = ['url']; protected $fillable = ['url'];
/** protected function url(): Attribute
* Normalize the URL of a Like.
*
* @param string $value The provided URL
*/
public function setUrlAttribute(string $value)
{ {
$this->attributes['url'] = normalize_url($value); return Attribute::set(
set: fn ($value) => normalize_url($value),
);
} }
/** protected function authorUrl(): Attribute
* Normalize the URL of the author of the like.
*
* @param string|null $value The authors url
*/
public function setAuthorUrlAttribute(?string $value)
{ {
$this->attributes['author_url'] = normalize_url($value); return Attribute::set(
set: fn ($value) => normalize_url($value),
);
} }
/** protected function content(): Attribute
* If the content contains HTML, filter it.
*
* @param string|null $value The content of the like
* @return string|null
*/
public function getContentAttribute(?string $value): ?string
{ {
if ($value === null) { return Attribute::get(
return null; get: function ($value, $attributes) {
} if ($value === null) {
return null;
}
$mf2 = Mf2\parse($value, $this->url); $mf2 = Mf2\parse($value, $attributes['url']);
if (Arr::get($mf2, 'items.0.properties.content.0.html')) { if (Arr::get($mf2, 'items.0.properties.content.0.html')) {
return $this->filterHtml( return $this->filterHtml(
$mf2['items'][0]['properties']['content'][0]['html'] $mf2['items'][0]['properties']['content'][0]['html']
); );
} }
return $value; return $value;
}
);
} }
} }

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -37,53 +38,63 @@ class Media extends Model
return $this->belongsTo(Note::class); return $this->belongsTo(Note::class);
} }
/** protected function url(): Attribute
* Get the URL for an S3 media file.
*
* @return string
*/
public function getUrlAttribute(): string
{ {
if (Str::startsWith($this->path, 'https://')) { return Attribute::get(
return $this->path; get: function ($value, $attributes) {
} if (Str::startsWith($attributes['path'], 'https://')) {
return $attributes['path'];
}
return config('filesystems.disks.s3.url') . '/' . $this->path; return config('filesystems.disks.s3.url') . '/' . $attributes['path'];
}
);
} }
/** protected function mediumurl(): Attribute
* Get the URL for the medium size of an S3 image file.
*
* @return string
*/
public function getMediumurlAttribute(): string
{ {
$basename = $this->getBasename($this->path); return Attribute::get(
$extension = $this->getExtension($this->path); get: fn ($value, $attributes) => $this->getSizeUrl($attributes['path'], 'medium'),
);
return config('filesystems.disks.s3.url') . '/' . $basename . '-medium.' . $extension;
} }
/** protected function smallmurl(): Attribute
* Get the URL for the small size of an S3 image file.
*
* @return string
*/
public function getSmallurlAttribute(): string
{ {
$basename = $this->getBasename($this->path); return Attribute::get(
$extension = $this->getExtension($this->path); get: fn ($value, $attributes) => $this->getSizeUrl($attributes['path'], 'small'),
);
return config('filesystems.disks.s3.url') . '/' . $basename . '-small.' . $extension;
} }
/** protected function mimetype(): Attribute
* Give the real part of a filename, i.e. strip the file extension. {
* return Attribute::get(
* @param string $path get: function ($value, $attributes) {
* @return string $extension = $this->getExtension($attributes['path']);
*/
public function getBasename(string $path): string return match ($extension) {
'gif' => 'image/gif',
'jpeg', 'jpg' => 'image/jpeg',
'png' => 'image/png',
'svg' => 'image/svg+xml',
'tiff' => 'image/tiff',
'webp' => 'image/webp',
'mp4' => 'video/mp4',
'mkv' => 'video/mkv',
default => 'application/octet-stream',
};
},
);
}
private function getSizeUrl(string $path, string $size): string
{
$basename = $this->getBasename($path);
$extension = $this->getExtension($path);
return config('filesystems.disks.s3.url') . '/' . $basename . '-' . $size . '.' . $extension;
}
private function getBasename(string $path): string
{ {
// the following achieves this data flow // the following achieves this data flow
// foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar // foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar
@ -95,40 +106,10 @@ class Media extends Model
}, ''), '.'); }, ''), '.');
} }
/** private function getExtension(string $path): string
* Get the extension from a given filename.
*
* @param string $path
* @return string
*/
public function getExtension(string $path): string
{ {
$parts = explode('.', $path); $parts = explode('.', $path);
return array_pop($parts); return array_pop($parts);
} }
/**
* Get the mime type of the media file.
*
* For now we will just use the extension, but this could be improved.
*
* @return string
*/
public function getMimeType(): string
{
$extension = $this->getExtension($this->path);
return match ($extension) {
'gif' => 'image/gif',
'jpeg', 'jpg' => 'image/jpeg',
'png' => 'image/png',
'svg' => 'image/svg+xml',
'tiff' => 'image/tiff',
'webp' => 'image/webp',
'mp4' => 'video/mp4',
'mkv' => 'video/mkv',
default => 'application/octet-stream',
};
}
} }

View file

@ -6,6 +6,7 @@ namespace App\Models;
use Cviebrock\EloquentSluggable\Sluggable; use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
@ -105,53 +106,46 @@ class Place extends Model
])); ]));
} }
/** protected function longurl(): Attribute
* The Long URL for a place.
*
* @return string
*/
public function getLongurlAttribute(): string
{ {
return config('app.url') . '/places/' . $this->slug; return Attribute::get(
get: fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'],
);
} }
/** protected function shorturl(): Attribute
* The Short URL for a place.
*
* @return string
*/
public function getShorturlAttribute(): string
{ {
return config('app.shorturl') . '/places/' . $this->slug; return Attribute::get(
get: fn ($value, $attributes) => config('app.shorturl') . '/places/' . $attributes['slug'],
);
} }
/** protected function uri(): Attribute
* This method is an alternative for `longurl`.
*
* @return string
*/
public function getUriAttribute(): string
{ {
return $this->longurl; return Attribute::get(
get: fn () => $this->longurl,
);
} }
/** protected function externalUrls(): Attribute
* Dealing with a jsonb column, so we check input first.
*
* @param string|null $url
*/
public function setExternalUrlsAttribute(?string $url)
{ {
if ($url === null) { return Attribute::set(
return; set: function ($value, $attributes) {
} if ($value === null) {
$type = $this->getType($url); return $attributes['external_urls'] ?? null;
$already = []; }
if (array_key_exists('external_urls', $this->attributes)) {
$already = json_decode($this->attributes['external_urls'], true); $type = $this->getType($value);
} $already = [];
$already[$type] = $url;
$this->attributes['external_urls'] = json_encode($already); if (array_key_exists('external_urls', $attributes)) {
$already = json_decode($attributes['external_urls'], true);
}
$already[$type] = $value;
return json_encode($already);
}
);
} }
/** /**

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -40,14 +41,11 @@ class Tag extends Model
return $this->belongsToMany('App\Models\Bookmark'); return $this->belongsToMany('App\Models\Bookmark');
} }
/** protected function tag(): Attribute
* When creating a Tag model instance, invoke the nomralize method on the tag.
*
* @param string $value
*/
public function setTagAttribute(string $value)
{ {
$this->attributes['tag'] = $this->normalize($value); return Attribute::set(
set: fn ($value) => self::normalize($value),
);
} }
/** /**

View file

@ -6,13 +6,14 @@ namespace App\Models;
use App\Traits\FilterHtml; use App\Traits\FilterHtml;
use Codebird\Codebird; use Codebird\Codebird;
use Exception;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Jonnybarnes\WebmentionsParser\Authorship; use Jonnybarnes\WebmentionsParser\Authorship;
use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException;
class WebMention extends Model class WebMention extends Model
{ {
@ -43,72 +44,79 @@ class WebMention extends Model
return $this->morphTo(); return $this->morphTo();
} }
/** protected function author(): Attribute
* Get the author of the webmention.
*
* @return array
*
* @throws AuthorshipParserException
*/
public function getAuthorAttribute(): array
{ {
$authorship = new Authorship(); return Attribute::get(
$hCard = $authorship->findAuthor(json_decode($this->mf2, true)); get: function ($value, $attributes) {
if (
! array_key_exists('mf2', $attributes) ||
$attributes['mf2'] === null
) {
return null;
}
if ($hCard === false) { $authorship = new Authorship();
return []; $hCard = $authorship->findAuthor(json_decode($attributes['mf2'], true));
}
if ( if ($hCard === false) {
array_key_exists('properties', $hCard) && return null;
array_key_exists('photo', $hCard['properties']) }
) {
$hCard['properties']['photo'][0] = $this->createPhotoLink($hCard['properties']['photo'][0]);
}
return $hCard; if (
} array_key_exists('properties', $hCard) &&
array_key_exists('photo', $hCard['properties'])
) {
$hCard['properties']['photo'][0] = $this->createPhotoLink($hCard['properties']['photo'][0]);
}
/** return $hCard;
* Get the published value for the webmention.
*
* @return string|null
*/
public function getPublishedAttribute(): ?string
{
$mf2 = $this->mf2 ?? '';
$microformats = json_decode($mf2, true);
if (isset($microformats['items'][0]['properties']['published'][0])) {
try {
$published = carbon()->parse(
$microformats['items'][0]['properties']['published'][0]
)->toDayDateTimeString();
} catch (\Exception $exception) {
$published = $this->updated_at->toDayDateTimeString();
} }
} else { );
$published = $this->updated_at->toDayDateTimeString();
}
return $published;
} }
/** protected function published(): Attribute
* Get the filtered HTML of a reply.
*
* @return string|null
*/
public function getReplyAttribute(): ?string
{ {
if ($this->mf2 === null) { return Attribute::get(
return null; get: function ($value, $attributes) {
} $mf2 = $attributes['mf2'] ?? '';
$microformats = json_decode($this->mf2, true); $microformats = json_decode($mf2, true);
if (isset($microformats['items'][0]['properties']['content'][0]['html'])) { if (isset($microformats['items'][0]['properties']['published'][0])) {
return $this->filterHtml($microformats['items'][0]['properties']['content'][0]['html']); try {
} $published = carbon()->parse(
$microformats['items'][0]['properties']['published'][0]
)->toDayDateTimeString();
} catch (Exception) {
$published = $this->updated_at->toDayDateTimeString();
}
} else {
$published = $this->updated_at->toDayDateTimeString();
}
return null; return $published;
}
);
}
protected function reply(): Attribute
{
return Attribute::get(
get: function ($value, $attributes) {
if (
! array_key_exists('mf2', $attributes) ||
$attributes['mf2'] === null
) {
return null;
}
$microformats = json_decode($attributes['mf2'], true);
if (isset($microformats['items'][0]['properties']['content'][0]['html'])) {
return $this->filterHtml($microformats['items'][0]['properties']['content'][0]['html']);
}
return null;
}
);
} }
/** /**
@ -121,11 +129,13 @@ class WebMention extends Model
{ {
$url = normalize_url($url); $url = normalize_url($url);
$host = parse_url($url, PHP_URL_HOST); $host = parse_url($url, PHP_URL_HOST);
if ($host == 'pbs.twimg.com') {
if ($host === 'pbs.twimg.com') {
//make sure we use HTTPS, we know twitter supports it //make sure we use HTTPS, we know twitter supports it
return str_replace('http://', 'https://', $url); return str_replace('http://', 'https://', $url);
} }
if ($host == 'twitter.com') {
if ($host === 'twitter.com') {
if (Cache::has($url)) { if (Cache::has($url)) {
return Cache::get($url); return Cache::get($url);
} }
@ -137,6 +147,7 @@ class WebMention extends Model
return $profile_image; return $profile_image;
} }
$filesystem = new Filesystem(); $filesystem = new Filesystem();
if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) { if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
return '/assets/profile-images/' . $host . '/image'; return '/assets/profile-images/' . $host . '/image';

View file

@ -83,7 +83,6 @@ class PlacesTest extends TestCase
'url' => ['https://www.openstreetmap.org/way/1234'], 'url' => ['https://www.openstreetmap.org/way/1234'],
], ],
]); ]);
$this->assertInstanceOf('App\Models\Place', $ret);
$this->assertCount(11, Place::all()); $this->assertCount(11, Place::all());
} }