Merge pull request #190 from jonnybarnes/handle-modelnotfoundexception

Improve exception handling and model binding
This commit is contained in:
Jonny Barnes 2020-08-09 16:05:10 +01:00 committed by GitHub
commit 9e114b8250
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 147 additions and 82 deletions

View file

@ -4,6 +4,7 @@ namespace App\Exceptions;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -23,7 +24,8 @@ class Handler extends ExceptionHandler
* @var array
*/
protected $dontReport = [
\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class,
NotFoundHttpException::class,
ModelNotFoundException::class,
];
/**
@ -49,35 +51,33 @@ class Handler extends ExceptionHandler
{
parent::report($throwable);
if ($throwable instanceof NotFoundHttpException) {
return;
}
if ($this->shouldReport($throwable)) {
$guzzle = new Client([
'headers' => [
'Content-Type' => 'application/json',
],
]);
$guzzle = new Client([
'headers' => [
'Content-Type' => 'application/json',
],
]);
$guzzle->post(
config('logging.slack'),
[
'body' => json_encode([
'attachments' => [[
'fallback' => 'There was an exception.',
'pretext' => 'There was an exception.',
'color' => '#d00000',
'author_name' => app()->environment(),
'author_link' => config('app.url'),
'fields' => [[
'title' => get_class($throwable) ?? 'Unknown Exception',
'value' => $throwable->getTraceAsString() ?? '',
$guzzle->post(
config('logging.slack'),
[
'body' => json_encode([
'attachments' => [[
'fallback' => 'There was an exception.',
'pretext' => 'There was an exception.',
'color' => '#d00000',
'author_name' => app()->environment(),
'author_link' => config('app.url'),
'fields' => [[
'title' => get_class($throwable) ?? 'Unknown Exception',
'value' => $throwable->getTraceAsString() ?? '',
]],
'ts' => time(),
]],
'ts' => time(),
]],
]),
]
);
]),
]
);
}
}
/**

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
use Jonnybarnes\IndieWeb\Numbers;
@ -14,8 +15,8 @@ class ArticlesController extends Controller
/**
* Show all articles (with pagination).
*
* @param int $year
* @param int $month
* @param int|null $year
* @param int|null $month
* @return View
*/
public function index(int $year = null, int $month = null): View
@ -38,7 +39,12 @@ class ArticlesController extends Controller
*/
public function show(int $year, int $month, string $slug)
{
$article = Article::where('titleurl', $slug)->firstOrFail();
try {
$article = Article::where('titleurl', $slug)->firstOrFail();
} catch (ModelNotFoundException $exception) {
abort(404);
}
if ($article->updated_at->year != $year || $article->updated_at->month != $month) {
return redirect('/blog/'
. $article->updated_at->year
@ -59,7 +65,11 @@ class ArticlesController extends Controller
public function onlyIdInUrl(int $idFromUrl): RedirectResponse
{
$realId = resolve(Numbers::class)->b60tonum($idFromUrl);
$article = Article::findOrFail($realId);
try {
$article = Article::findOrFail($realId);
} catch (ModelNotFoundException $exception) {
abort(404);
}
return redirect($article->link);
}

View file

@ -6,13 +6,14 @@ namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class AuthController extends Controller
{
/**
* Show the login form.
*
* @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
* @return View|RedirectResponse
*/
public function showLogin()
{
@ -27,7 +28,7 @@ class AuthController extends Controller
* Log in a user, set a session variable, check credentials against
* the .env file.
*
* @return \Illuminate\Http\RedirectResponse
* @return RedirectResponse
*/
public function login(): RedirectResponse
{
@ -43,7 +44,7 @@ class AuthController extends Controller
/**
* Show the form to logout a user.
*
* @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
* @return View|RedirectResponse
*/
public function showLogout()
{
@ -58,7 +59,7 @@ class AuthController extends Controller
/**
* Log the user out from their current session.
*
* @return \Illuminate\Http\RedirectResponse;
* @return RedirectResponse;
*/
public function logout(): RedirectResponse
{

View file

@ -12,7 +12,7 @@ class BookmarksController extends Controller
/**
* Show the most recent bookmarks.
*
* @return \Illuminate\View\View
* @return View
*/
public function index(): View
{
@ -24,8 +24,8 @@ class BookmarksController extends Controller
/**
* Show a single bookmark.
*
* @param \App\Models\Bookmark $bookmark
* @return \Illuminate\View\View
* @param Bookmark $bookmark
* @return View
*/
public function show(Bookmark $bookmark): View
{

View file

@ -13,7 +13,7 @@ class ContactsController extends Controller
/**
* Show all the contacts.
*
* @return \Illuminate\View\View
* @return View
*/
public function index(): View
{
@ -34,17 +34,15 @@ class ContactsController extends Controller
/**
* Show a single contact.
*
* @todo Use implicit model binding.
*
* @param string $nick The nickname associated with contact
* @return \Illuminate\View\View
* @param Contact $contact
* @return View
*/
public function show(string $nick): View
public function show(Contact $contact): View
{
$filesystem = new Filesystem();
$contact = Contact::where('nick', '=', $nick)->firstOrFail();
$contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST);
$file = public_path() . '/assets/profile-images/' . $contact->homepageHost . '/image';
$filesystem = new Filesystem();
$image = ($filesystem->exists($file)) ?
'/assets/profile-images/' . $contact->homepageHost . '/image'
:

View file

@ -73,9 +73,9 @@ class FeedsController extends Controller
/**
* Returns the blog JSON feed.
*
* @return \Illuminate\Http\JsonResponse
* @return array
*/
public function blogJson()
public function blogJson(): array
{
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
$data = [
@ -106,7 +106,7 @@ class FeedsController extends Controller
/**
* Returns the notes JSON feed.
*
* @return \Illuminate\Http\JsonResponse
* @return array
*/
public function notesJson()
{

View file

@ -7,11 +7,15 @@ use App\Models\Bookmark;
use App\Models\Like;
use App\Models\Note;
use App\Services\ActivityStreamsService;
use Illuminate\Http\Response;
use Illuminate\View\View;
class FrontPageController extends Controller
{
/**
* Show all the recent activity.
*
* @return Response|View
*/
public function index()
{
@ -19,8 +23,6 @@ class FrontPageController extends Controller
return (new ActivityStreamsService())->siteOwnerResponse();
}
$pageNumber = request()->query('page') ?? 1;
$notes = Note::latest()->get();
$articles = Article::latest()->get();
$bookmarks = Bookmark::latest()->get();

View file

@ -12,7 +12,7 @@ class LikesController extends Controller
/**
* Show the latest likes.
*
* @return \Illuminate\View\View
* @return View
*/
public function index(): View
{
@ -24,8 +24,8 @@ class LikesController extends Controller
/**
* Show a single like.
*
* @param \App\Models\Like $like
* @return \Illuminate\View\View
* @param Like $like
* @return View
*/
public function show(Like $like): View
{

View file

@ -16,10 +16,10 @@ use MStaack\LaravelPostgis\Geometries\Point;
class MicropubController extends Controller
{
protected $tokenService;
protected $hentryService;
protected $hcardService;
protected $updateService;
protected TokenService $tokenService;
protected HEntryService $hentryService;
protected HCardService $hcardService;
protected UpdateService $updateService;
public function __construct(
TokenService $tokenService,

View file

@ -6,7 +6,7 @@ namespace App\Http\Controllers;
use App\Models\Note;
use App\Services\ActivityStreamsService;
use Illuminate\Contracts\View\Factory as ViewFactory;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
@ -20,7 +20,7 @@ class NotesController extends Controller
/**
* Show all the notes. This is also the homepage.
*
* @return ViewFactory|View|Response
* @return View|Response
*/
public function index()
{
@ -45,7 +45,11 @@ class NotesController extends Controller
*/
public function show(string $urlId)
{
$note = Note::nb60($urlId)->with('webmentions')->firstOrFail();
try {
$note = Note::nb60($urlId)->with('webmentions')->firstOrFail();
} catch (ModelNotFoundException $exception) {
abort(404);
}
if (request()->wantsActivityStream()) {
return (new ActivityStreamsService())->singleNoteResponse($note);

View file

@ -12,7 +12,7 @@ class PlacesController extends Controller
/**
* Show all the places.
*
* @return \Illuminate\View\View
* @return View
*/
public function index(): View
{
@ -24,13 +24,11 @@ class PlacesController extends Controller
/**
* Show a specific place.
*
* @param string $slug
* @return \Illuminate\View\View
* @param Place $place
* @return View
*/
public function show(string $slug): View
public function show(Place $place): View
{
$place = Place::where('slug', '=', $slug)->firstOrFail();
return view('singleplace', ['place' => $place]);
}
}

View file

@ -12,7 +12,7 @@ class SearchController extends Controller
/**
* Display search results.
*
* @return \Illuminate\View\View
* @return View
*/
public function search(): View
{

View file

@ -9,9 +9,9 @@ class SessionStoreController extends Controller
/**
* Save the selected colour scheme in the session.
*
* @return \Illuminate\Http\JsonResponse
* @return string[]
*/
public function saveColour()
public function saveColour(): array
{
$css = request()->input('css');

View file

@ -20,7 +20,7 @@ class ShortURLsController extends Controller
/**
* Redirect from '/' to the long url.
*
* @return \Illuminate\Http\RedirectResponse
* @return RedirectResponse
*/
public function baseURL(): RedirectResponse
{
@ -30,7 +30,7 @@ class ShortURLsController extends Controller
/**
* Redirect from '/@' to a twitter profile.
*
* @return \Illuminate\Http\RedirectResponse
* @return RedirectResponse
*/
public function twitter(): RedirectResponse
{
@ -40,7 +40,7 @@ class ShortURLsController extends Controller
/**
* Redirect from '/+' to a Google+ profile.
*
* @return \Illuminate\Http\RedirectResponse
* @return RedirectResponse
*/
public function googlePlus(): RedirectResponse
{
@ -53,7 +53,7 @@ class ShortURLsController extends Controller
*
* @param string Post type
* @param string Post ID
* @return \Illuminate\Http\RedirectResponse
* @return RedirectResponse
*/
public function expandType(string $type, string $postId): RedirectResponse
{

View file

@ -13,18 +13,18 @@ class TokenEndpointController extends Controller
/**
* The IndieAuth Client.
*/
protected $client;
protected Client $client;
/**
* The Token handling service.
*/
protected $tokenService;
protected TokenService $tokenService;
/**
* Inject the dependencies.
*
* @param \IndieAuth\Client $client
* @param \App\Services\TokenService $tokenService
* @param Client $client
* @param TokenService $tokenService
*/
public function __construct(
Client $client,
@ -37,7 +37,7 @@ class TokenEndpointController extends Controller
/**
* If the user has authd via the IndieAuth protocol, issue a valid token.
*
* @return \Illuminate\Http\JsonResponse
* @return JsonResponse
*/
public function create(): JsonResponse
{

View file

@ -60,6 +60,16 @@ class Place extends Model
use Sluggable;
use PostgisTrait;
/**
* Get the route key for the model.
*
* @return string
*/
public function getRouteKeyName()
{
return 'slug';
}
/**
* The attributes that are mass assignable.
*

View file

@ -156,11 +156,11 @@ Route::group(['domain' => config('url.longurl')], function () {
// Contacts
Route::get('contacts', 'ContactsController@index');
Route::get('contacts/{nick}', 'ContactsController@show');
Route::get('contacts/{contact:nick}', 'ContactsController@show');
// Places
Route::get('places', 'PlacesController@index');
Route::get('places/{slug}', 'PlacesController@show');
Route::get('places/{place}', 'PlacesController@show');
Route::get('search', 'SearchController@search');

View file

@ -30,4 +30,18 @@ class ArticlesTest extends TestCase
$response = $this->get('/blog/s/2');
$response->assertRedirect('/blog/' . date('Y') . '/' . date('m') . '/some-code-i-did');
}
/** @test */
public function unknownSlugGives404()
{
$response = $this->get('/blog/' . date('Y') . '/' . date('m') . '/unknown-slug');
$response->assertNotFound();
}
/** @test */
public function unknownArticleIdGives404()
{
$response = $this->get('/blog/s/22');
$response->assertNotFound();
}
}

View file

@ -38,4 +38,11 @@ class ContactsTest extends TestCase
$response = $this->get('/contacts/aaron');
$response->assertViewHas('image', '/assets/profile-images/aaronparecki.com/image');
}
/** @test */
public function unknownContactGives404()
{
$response = $this->get('/contacts/unknown');
$response->assertNotFound();
}
}

View file

@ -211,4 +211,11 @@ END;
$this->assertEquals('Jonny Barnes', Like::find($id)->author_name);
}
/** @test */
public function unknownLikeGives404()
{
$response = $this->get('/likes/202');
$response->assertNotFound();
}
}

View file

@ -57,4 +57,11 @@ class NotesControllerTest extends TestCase
$response = $this->get('/notes/tagged/beer');
$response->assertViewHas('tag', 'beer');
}
/** @test */
public function unknownNoteGives404()
{
$response = $this->get('/notes/112233');
$response->assertNotFound();
}
}

View file

@ -29,4 +29,11 @@ class PlacesTest extends TestCase
$response = $this->get('/places/the-bridgewater-pub');
$response->assertViewHas('place', $place);
}
/** @test */
public function unknownPlaceGives404()
{
$response = $this->get('/places/unknown');
$response->assertNotFound();
}
}