Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass Larastan Level 8 #30

Merged
merged 6 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_DATABASE=bassform
# DB_USERNAME=root
# DB_PASSWORD=

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ VILT stack template app for PHP 8.2.x created by Musimana. Features include serv

[Vue3](https://vuejs.org/),
[Inertia](https://inertiajs.com/),
[Laravel 10.x](https://laravel.com/docs),
[Laravel 11.x](https://laravel.com/docs),
[Tailwind 3.x](https://tailwindcss.com/docs)

This project is based on Breeze, with an opinionated style that aims to simplify creating new instances. On top of the Breeze scaffolding,
Expand Down
12 changes: 4 additions & 8 deletions app/Http/Controllers/Auth/EmailVerificationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class EmailVerificationController extends Controller
/** Display the email verification prompt. */
public function show(): RedirectResponse|Response
{
return request()->user()->hasVerifiedEmail()
return request()->user()?->hasVerifiedEmail()
? redirect()->intended(config('metadata.user_homepage'))
: (new AuthViewRepository)->getViewDetails(
self::TEMPLATE_EMAIL_VERIFY,
Expand All @@ -30,23 +30,19 @@ public function show(): RedirectResponse|Response
/** Send a new email verification notification, after a manual request by the user. */
public function store(): RedirectResponse
{
if (request()->user()->hasVerifiedEmail()) {
if (request()->user()?->hasVerifiedEmail()) {
return redirect()->intended(config('metadata.user_homepage'));
}

request()->user()->sendEmailVerificationNotification();
request()->user()?->sendEmailVerificationNotification();

return back()->with('status', 'verification-link-sent');
}

/** Mark the authenticated user's email address as verified. */
public function edit(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(config('metadata.user_homepage') . '?verified=1');
}

if ($request->user()->markEmailAsVerified()) {
if (!$request->user()?->hasVerifiedEmail() && $request->user()?->markEmailAsVerified()) {
/** @var MustVerifyEmail $user */
$user = $request->user();
event(new Verified($user));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function show(): Response
public function store(Request $request): RedirectResponse
{
if (!Auth::guard('web')->validate([
'email' => $request->user()->email,
'email' => $request->user()?->email,
'password' => $request->password,
])) {
throw ValidationException::withMessages([
Expand Down
21 changes: 18 additions & 3 deletions app/Http/Controllers/Auth/ProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Inertia\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;

final class ProfileController extends Controller
{
Expand Down Expand Up @@ -70,9 +71,17 @@ public function edit(): Response
);
}

/** Update the authenticated user's profile information. */
/**
* Update the authenticated user's profile information.
*
* @throws HttpException
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
if (!$request->user()) {
abort(401);
}

$request->user()->fill($request->validated());

if ($request->user()->isDirty('email')) {
Expand All @@ -84,10 +93,16 @@ public function update(ProfileUpdateRequest $request): RedirectResponse
return to_route('profile.edit');
}

/** Delete the authenticated user's account. */
/**
* Delete the authenticated user's account.
*
* @throws HttpException
*/
public function destroy(ProfileDeleteRequest $request): RedirectResponse
{
$user = $request->user();
if (!$user = $request->user()) {
abort(401);
}

auth()->logout();

Expand Down
11 changes: 10 additions & 1 deletion app/Http/Controllers/Auth/ProfilePasswordController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\PasswordUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;

final class ProfilePasswordController extends Controller
{
/** Update the authenticated user's password. */
/**
* Update the authenticated user's password.
*
* @throws HttpException
*/
public function update(PasswordUpdateRequest $request): RedirectResponse
{
if (!$request->user()) {
abort(401);
}

$inputs = $request->validated();

$request->user()->update([
Expand Down
12 changes: 10 additions & 2 deletions app/Http/Controllers/Public/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@

final class PageController extends Controller
{
/** Display the given page. */
/**
* Display the given page.
*
* @throws HttpException
*/
public function show(Page $page): Response
{
if (!$page->template) {
abort(404);
}

return (new PublicViewRepository)
->getViewDetails(
$page->template,
Expand Down Expand Up @@ -59,7 +67,7 @@ public function store(Request $request, Page $page): RedirectResponse
$files = [$files];
}

if (!$generated_filename = $files[0]->store('uploads/pdf')) {
if (!$generated_filename = $files[0]?->store('uploads/pdf')) {
Log::error('ERROR | Failed to store uploaded PDF file for request:', $response);

abort(500, 'Failed to store uploaded PDF file');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ final class ItemLinksNavbarResource implements NavbarItemInterface
/**
* Get the content array for the given page's public link.
*
* @return array<string, string|array<string, string>>
* @return array<string, null|string|array<string, null|string>>
*/
public function getItem(NavbarItem $navbar_item): array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ final class HomepageSitemapResource implements ConstantItemInterface
*/
public function getItem(): array
{
$lastmod = strtotime(strval(Page::max('updated_at')));

return [
'loc' => route('home'),
'lastmod' => date('Y-m-d', strtotime(Page::max('updated_at'))),
'lastmod' => $lastmod ? date('Y-m-d', $lastmod) : '2024-03-01',
'changefreq' => 'weekly',
'priority' => 0.8,
];
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Resources/Views/Sitemaps/PageSitemapResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class PageSitemapResource implements PageItemInterface
*/
public function getItem(Page $page): array
{
$updated_at = strtotime($page->updated_at);
$updated_at = strtotime(strval($page->updated_at));
$lastmod = $updated_at ? date('Y-m-d', $updated_at) : strval(config('metadata.first_published_year')) . '-01-01';

return [
Expand Down
40 changes: 26 additions & 14 deletions app/Providers/DuskServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,61 @@
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Browser;

/**
* @method string getScreenshotFilename(string $filename_base, string $count_string)
* @method Browser scrollToTop()
*/
final class DuskServiceProvider extends ServiceProvider implements DeferrableProvider
{
/** Register Dusk's browser macros. */
public function boot(): void
{
Browser::macro('getScreenshotFilename', function ($filename_base, $count) {
Browser::macro('getScreenshotFilename', function (string $filename_base, string $count_string) {
return date('Y-m-d') . '_' . str_replace(' ', '_', strtolower(config('app.name')))
. '/' . config('dusk.screen_width') . 'x' . config('dusk.screen_height')
. '/' . $filename_base . '_' . $count;
. '/' . $filename_base . '_' . $count_string;
});

Browser::macro('screenshotWholePage', function ($filename_base) {
$this->scrollToTop()->pause(config('dusk.pause_length'));
Browser::macro('screenshotWholePage', function (string $filename_base) {
/** @var Browser $browser */
$browser = $this;
$browser->scrollToTop()->pause(config('dusk.pause_length'));

$screen_max = ceil($this->script('return document.body.offsetHeight / window.innerHeight')[0]);
$screen_max = ceil($browser->script('return document.body.offsetHeight / window.innerHeight')[0]);

for ($screen = 1; $screen <= $screen_max; $screen++) {
$this->screenshot($this->getScreenshotFilename($filename_base, $screen))
$browser->screenshot($browser->getScreenshotFilename($filename_base, (string) $screen))
->pause(config('dusk.pause_length'))
->scrollDownScreenHeight();
}

$this->scrollToTop()->pause(config('dusk.pause_length'));
$browser->scrollToTop()->pause(config('dusk.pause_length'));

return $this;
return $browser;
});

Browser::macro('scrollToTop', function () {
$this->script('window.scrollTo(0, 0)');
/** @var Browser $browser */
$browser = $this;
$browser->script('window.scrollTo(0, 0)');

return $this;
return $browser;
});

Browser::macro('scrollDownScreenHeight', function () {
$this->script('window.scrollBy(0, window.innerHeight)');
/** @var Browser $browser */
$browser = $this;
$browser->script('window.scrollBy(0, window.innerHeight)');

return $this;
return $browser;
});

Browser::macro('scrollToEnd', function () {
$this->script('window.scrollTo(0, document.body.scrollHeight)');
/** @var Browser $browser */
$browser = $this;
$browser->script('window.scrollTo(0, document.body.scrollHeight)');

return $this;
return $browser;
});
}

Expand Down
31 changes: 23 additions & 8 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,36 @@ parameters:

paths:
- app/
- bootstrap/
- config/
- database/
- routes/
- tests/

# Level 9 is the highest level
level: 7

ignoreErrors:
level: 8

excludePaths:
- tests/Browser # 16 errors 2024-03-16, all `Call to an undefined method Laravel\Dusk\Browser::`
- tests/Feature # 252 errors 2024-03-16
- tests/Pest # 32 errors 2024-03-16
- tests/Unit # 150 errors 2024-03-16
- app/Providers/DuskServiceProvider.php # 8 errors 2024-02-01, all `Call to an undefined method App\Providers\DuskServiceProvider::`
- tests/Pest/Functions/CompositeTests # 5 errors 2024-03-29, whole directory needs refactoring

ignoreErrors:
- # From the way the methods are inherited by Pest
message: '#Call to an undefined method Pest\\#'
path: tests
- # To be fixed by adopting Pest syntax
message: '#Undefined variable: \$this#'
path: tests
- # From the way the methods are inherited by the Browser object
message: '#Call to an undefined method Laravel\\Dusk\\Browser::#'
path: tests/Browser
count: 16
- # From the way the methods are inherited by Pest
message: '#Function testNotifiedUpdate#'
path: tests/Feature/Http/Controllers/Auth/PasswordForgottenControllerTest.php
count: 5
- # From the way the methods are inherited by Pest
message: '#Access to an undefined property Pest\\Mixins\\Expectation\<App\\Models\\#'
path: tests/Unit
count: 3

checkGenericClassInNonGenericObjectType: false
2 changes: 1 addition & 1 deletion routes/web/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* verification.notice
* verification.send
* verification.verify
* See [Laravel User Verification Docs](https://laravel.com/docs/10.x/verification)
* See [Laravel User Verification Docs](https://laravel.com/docs/11.x/verification)
*/
Route::get('verify-email', 'show')->name('verification.notice');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
$actual = $this->actingAs($user)->get($verificationUrl);

Event::assertDispatched(Verified::class);
expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
expect($user->fresh()?->hasVerifiedEmail())->toBeTrue();

$actual
->assertSessionHasNoErrors()
Expand All @@ -95,5 +95,5 @@

$this->actingAs($user)->get($verificationUrl);

expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
expect($user->fresh()?->hasVerifiedEmail())->toBeFalse();
})->with('users-unverified');
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use App\Http\Resources\Views\Auth\Metadata\DashboardMetadataResource;
use App\Http\Resources\Views\Auth\Metadata\ProfileEditMetadataResource;
use App\Models\User;
use Illuminate\Http\Request;

test('TEMPLATE_DASHBOARD Vue page component exists', function () {
$template = (new ReflectionClassConstant(
Expand Down Expand Up @@ -76,7 +75,7 @@
->toHaveCorrectPropsAuth(
ProfileController::TEMPLATE_PROFILE_EDIT,
[],
(new ProfileEditMetadataResource)->getItem(new Request)
(new ProfileEditMetadataResource)->getItem()
);
})->with('users');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

use App\Http\Resources\Views\Auth\Metadata\PasswordResetMetadataResource;
use App\Interfaces\Resources\Items\ConstantItemInterface;
use Illuminate\Http\Request;

arch('it implements the expected interface')
->expect(PasswordResetMetadataResource::class)
->toImplement(ConstantItemInterface::class);

test('getItem returns ok', function () {
$actual = (new PasswordResetMetadataResource)->getItem(new Request);
$actual = (new PasswordResetMetadataResource)->getItem();

expect($actual)
->toHaveCamelCaseKeys()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

use App\Http\Resources\Views\Auth\Metadata\ProfileEditMetadataResource;
use App\Interfaces\Resources\Items\ConstantItemInterface;
use Illuminate\Http\Request;

arch('it implements the expected interface')
->expect(ProfileEditMetadataResource::class)
->toImplement(ConstantItemInterface::class);

test('getItem returns ok', function () {
$actual = (new ProfileEditMetadataResource)->getItem(new Request);
$actual = (new ProfileEditMetadataResource)->getItem();

expect($actual)
->toHaveCamelCaseKeys()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
->toHaveCount(4)
->toMatchArray([
'loc' => url('/'),
'lastmod' => '1970-01-01',
'lastmod' => '2024-03-01',
'changefreq' => 'weekly',
'priority' => 0.8,
]);
Expand Down
Loading