Eager load relations and optimize queries

This commit is contained in:
Igor Scheller 2023-12-13 14:42:25 +01:00 committed by xuwhite
parent 5fccc7e421
commit 05725cd58c
19 changed files with 71 additions and 29 deletions

View File

@ -175,7 +175,9 @@ function angeltype_controller()
$angeltype = AngelType::findOrFail(request()->input('angeltype_id')); $angeltype = AngelType::findOrFail(request()->input('angeltype_id'));
/** @var UserAngelType $user_angeltype */ /** @var UserAngelType $user_angeltype */
$user_angeltype = UserAngelType::whereUserId($user->id)->where('angel_type_id', $angeltype->id)->first(); $user_angeltype = UserAngelType::whereUserId($user->id)->where('angel_type_id', $angeltype->id)->first();
$members = $angeltype->userAngelTypes->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE); $members = $angeltype->userAngelTypes
->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)
->load(['state', 'personalData', 'contact']);
$days = angeltype_controller_shiftsFilterDays($angeltype); $days = angeltype_controller_shiftsFilterDays($angeltype);
$shiftsFilter = angeltype_controller_shiftsFilter($angeltype, $days); $shiftsFilter = angeltype_controller_shiftsFilter($angeltype, $days);
if (request()->input('showFilledShifts')) { if (request()->input('showFilledShifts')) {

View File

@ -7,6 +7,7 @@ use Engelsystem\Models\User\User;
use Engelsystem\ShiftCalendarRenderer; use Engelsystem\ShiftCalendarRenderer;
use Engelsystem\ShiftsFilter; use Engelsystem\ShiftsFilter;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** /**
@ -297,13 +298,15 @@ function users_list_controller()
} }
/** @var User[]|Collection $users */ /** @var User[]|Collection $users */
$users = User::with(['contact', 'personalData', 'state']) $users = User::with(['contact', 'personalData', 'state', 'shiftEntries' => function (HasMany $query) {
$query->where('freeloaded', true);
}])
->orderBy('name') ->orderBy('name')
->get(); ->get();
foreach ($users as $user) { foreach ($users as $user) {
$user->setAttribute( $user->setAttribute(
'freeloads', 'freeloads',
$user->shiftEntries() $user->shiftEntries
->where('freeloaded', true) ->where('freeloaded', true)
->count() ->count()
); );

View File

@ -116,7 +116,7 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
$shifts = collect($shifts); $shifts = collect($shifts);
return Shift::query() return Shift::with(['location', 'shiftType'])
->whereIn('id', $shifts->pluck('id')->toArray()) ->whereIn('id', $shifts->pluck('id')->toArray())
->orderBy('shifts.start') ->orderBy('shifts.start')
->get(); ->get();
@ -189,12 +189,14 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
] ]
); );
$shifts = []; $shifts = new Collection();
foreach ($shiftsData as $shift) { foreach ($shiftsData as $shift) {
$shifts[] = (new Shift())->forceFill($shift); $shifts[] = (new Shift())->forceFill($shift);
} }
return collect($shifts); $shifts->load(['location', 'shiftType']);
return $shifts;
} }
/** /**
@ -354,7 +356,7 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty
*/ */
function ShiftEntries_by_ShiftsFilter(ShiftsFilter $shiftsFilter) function ShiftEntries_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
{ {
return ShiftEntry::with('user') return ShiftEntry::with('user', 'user.state')
->join('shifts', 'shifts.id', 'shift_entries.shift_id') ->join('shifts', 'shifts.id', 'shift_entries.shift_id')
->whereIn('shifts.location_id', $shiftsFilter->getLocations()) ->whereIn('shifts.location_id', $shiftsFilter->getLocations())
->whereBetween('start', [$shiftsFilter->getStart(), $shiftsFilter->getEnd()]) ->whereBetween('start', [$shiftsFilter->getStart(), $shiftsFilter->getEnd()])
@ -638,12 +640,13 @@ function Shifts_by_user($userId, $include_freeloaded_comments = false)
] ]
); );
$shifts = []; $shifts = new Collection();
foreach ($shiftsData as $data) { foreach ($shiftsData as $data) {
$shifts[] = (new Shift())->forceFill($data); $shifts[] = (new Shift())->forceFill($data);
} }
$shifts->load(['shiftType', 'location']);
return collect($shifts); return $shifts;
} }
/** /**

View File

@ -18,6 +18,7 @@ function UserWorkLogsForUser($userId, Carbon $sinceTime = null)
if ($sinceTime) { if ($sinceTime) {
$worklogs = $worklogs->whereDate('worked_at', '>=', $sinceTime); $worklogs = $worklogs->whereDate('worked_at', '>=', $sinceTime);
} }
$worklogs->with(['user', 'creator']);
return $worklogs->get(); return $worklogs->get();
} }

View File

@ -151,7 +151,7 @@ function admin_active()
} }
} }
$query = User::with('personalData') $query = User::with(['personalData', 'state'])
->selectRaw( ->selectRaw(
sprintf( sprintf(
' '

View File

@ -65,7 +65,7 @@ function admin_arrive()
} }
/** @var User[] $users */ /** @var User[] $users */
$users = User::with('personalData')->orderBy('name')->get(); $users = User::with(['personalData', 'state'])->orderBy('name')->get();
$arrival_count_at_day = []; $arrival_count_at_day = [];
$planned_arrival_count_at_day = []; $planned_arrival_count_at_day = [];
$planned_departure_count_at_day = []; $planned_departure_count_at_day = [];

View File

@ -40,7 +40,7 @@ function admin_free()
/** @var User[] $users */ /** @var User[] $users */
$users = []; $users = [];
if ($request->has('submit')) { if ($request->has('submit')) {
$query = User::with('personalData') $query = User::with(['personalData', 'contact', 'state'])
->select('users.*') ->select('users.*')
->leftJoin('shift_entries', 'users.id', 'shift_entries.user_id') ->leftJoin('shift_entries', 'users.id', 'shift_entries.user_id')
->leftJoin('users_state', 'users.id', 'users_state.user_id') ->leftJoin('users_state', 'users.id', 'users_state.user_id')

View File

@ -21,13 +21,13 @@ function admin_groups()
$html = ''; $html = '';
$request = request(); $request = request();
/** @var Group[]|Collection $groups */ /** @var Group[]|Collection $groups */
$groups = Group::query()->orderBy('name')->get(); $groups = Group::with('privileges')->orderBy('name')->get();
if (!$request->has('action')) { if (!$request->has('action')) {
$groups_table = []; $groups_table = [];
foreach ($groups as $group) { foreach ($groups as $group) {
/** @var Privilege[]|Collection $privileges */ /** @var Privilege[]|Collection $privileges */
$privileges = $group->privileges()->orderBy('name')->get(); $privileges = $group->privileges->sortBy('name');
$privileges_html = []; $privileges_html = [];
foreach ($privileges as $privilege) { foreach ($privileges as $privilege) {

View File

@ -326,6 +326,9 @@ function admin_shifts()
$shifts_table = []; $shifts_table = [];
foreach ($shifts as $shift) { foreach ($shifts as $shift) {
$shiftType = $shifttypes_source->find($shift['shift_type_id']);
$location = $locations->find($shift['location_id']);
/** @var Carbon $start */ /** @var Carbon $start */
$start = $shift['start']; $start = $shift['start'];
/** @var Carbon $end */ /** @var Carbon $end */
@ -340,9 +343,9 @@ function admin_shifts()
. '</span>' . '</span>'
. ', ' . round($end->copy()->diffInMinutes($start) / 60, 2) . 'h' . ', ' . round($end->copy()->diffInMinutes($start) / 60, 2) . 'h'
. '<br>' . '<br>'
. location_name_render(Location::find($shift['location_id'])), . location_name_render($location),
'title' => 'title' =>
htmlspecialchars(ShiftType::find($shifttype_id)->name) htmlspecialchars($shiftType->name)
. ($shift['title'] ? '<br />' . htmlspecialchars($shift['title']) : ''), . ($shift['title'] ? '<br />' . htmlspecialchars($shift['title']) : ''),
'needed_angels' => '', 'needed_angels' => '',
]; ];

View File

@ -552,8 +552,7 @@ class ImportSchedule extends BaseController
$existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList); $existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList);
foreach ($existingShifts as $scheduleShift) { foreach ($existingShifts as $scheduleShift) {
$guid = $scheduleShift->guid; $guid = $scheduleShift->guid;
/** @var Shift $shift */ $shift = $scheduleShift->shift;
$shift = Shift::with('location')->find($scheduleShift->shift_id);
$event = $scheduleEvents[$guid]; $event = $scheduleEvents[$guid];
$location = $locations->where('name', $event->getRoom()->getName())->first(); $location = $locations->where('name', $event->getRoom()->getName())->first();
@ -620,7 +619,7 @@ class ImportSchedule extends BaseController
*/ */
protected function getScheduleShiftsByGuid(ScheduleUrl $scheduleUrl, array $events) protected function getScheduleShiftsByGuid(ScheduleUrl $scheduleUrl, array $events)
{ {
return ScheduleShift::query() return ScheduleShift::with('shift.location')
->whereIn('guid', $events) ->whereIn('guid', $events)
->where('schedule_id', $scheduleUrl->id) ->where('schedule_id', $scheduleUrl->id)
->get(); ->get();
@ -633,7 +632,7 @@ class ImportSchedule extends BaseController
*/ */
protected function getScheduleShiftsWhereNotGuid(ScheduleUrl $scheduleUrl, array $events) protected function getScheduleShiftsWhereNotGuid(ScheduleUrl $scheduleUrl, array $events)
{ {
return ScheduleShift::query() return ScheduleShift::with('shift.location')
->whereNotIn('guid', $events) ->whereNotIn('guid', $events)
->where('schedule_id', $scheduleUrl->id) ->where('schedule_id', $scheduleUrl->id)
->get(); ->get();

View File

@ -297,7 +297,6 @@ function view_user_shifts()
return page([ return page([
div('col-md-12', [ div('col-md-12', [
msg(),
view(__DIR__ . '/../../resources/views/pages/user-shifts.html', [ view(__DIR__ . '/../../resources/views/pages/user-shifts.html', [
'title' => shifts_title(), 'title' => shifts_title(),
'add_link' => auth()->can('admin_shifts') ? $link : '', 'add_link' => auth()->can('admin_shifts') ? $link : '',

View File

@ -38,7 +38,8 @@ class QuestionsController extends BaseController
$questions = $this->question $questions = $this->question
->orderBy('answered_at') ->orderBy('answered_at')
->orderByDesc('created_at') ->orderByDesc('created_at')
->get(); ->get()
->load(['user.state', 'answerer.state']);
return $this->response->withView( return $this->response->withView(
'pages/questions/overview.twig', 'pages/questions/overview.twig',

View File

@ -218,7 +218,8 @@ class MessagesController extends BaseController
$join->on('messages.id', '=', 'conversations.last_id'); $join->on('messages.id', '=', 'conversations.last_id');
}) })
->orderBy('created_at', 'DESC') ->orderBy('created_at', 'DESC')
->get(); ->get()
->load(['receiver.personalData', 'receiver.state']);
} }
protected function raw(mixed $value): QueryExpression protected function raw(mixed $value): QueryExpression

View File

@ -53,8 +53,7 @@ class NewsController extends BaseController
$newsId = (int) $request->getAttribute('news_id'); $newsId = (int) $request->getAttribute('news_id');
$news = $this->news $news = $this->news
->with('user') ->with(['user', 'comments.user.state', 'comments.user.personalData'])
->with('comments')
->findOrFail($newsId); ->findOrFail($newsId);
return $this->renderView('pages/news/news.twig', ['news' => $news]); return $this->renderView('pages/news/news.twig', ['news' => $news]);

View File

@ -36,7 +36,8 @@ class QuestionsController extends BaseController
->whereUserId($this->auth->user()->id) ->whereUserId($this->auth->user()->id)
->orderByDesc('answered_at') ->orderByDesc('answered_at')
->orderBy('created_at') ->orderBy('created_at')
->get(); ->get()
->load(['user.state', 'answerer.state']);
return $this->response->withView( return $this->response->withView(
'pages/questions/overview.twig', 'pages/questions/overview.twig',

View File

@ -42,7 +42,7 @@ class Authenticator
} }
$this->user = $this->userFromSession(); $this->user = $this->userFromSession();
if (!$this->user && request()->getAttribute('route-api-accessible', false)) { if (!$this->user && $this->isApiRequest()) {
$this->user = $this->userFromApi(); $this->user = $this->userFromApi();
} }
@ -102,8 +102,10 @@ class Authenticator
if ($user) { if ($user) {
$this->permissions = $user->privileges->pluck('name')->toArray(); $this->permissions = $user->privileges->pluck('name')->toArray();
$user->last_login_at = new Carbon(); if ($user->last_login_at < Carbon::now()->subMinutes(5) && !$this->isApiRequest()) {
$user->save(); $user->last_login_at = Carbon::now();
$user->save(['touch' => false]);
}
} elseif ($this->session->get('user_id')) { } elseif ($this->session->get('user_id')) {
$this->session->remove('user_id'); $this->session->remove('user_id');
} }
@ -206,6 +208,11 @@ class Authenticator
return $this->user; return $this->user;
} }
protected function isApiRequest(): bool
{
return (bool) request()->getAttribute('route-api-accessible', false);
}
public function setPassword(User $user, string $password): void public function setPassword(User $user, string $password): void
{ {
$user->password = password_hash($password, $this->passwordAlgorithm); $user->password = password_hash($password, $this->passwordAlgorithm);

View File

@ -14,6 +14,8 @@ use function array_key_exists;
class Globals extends TwigExtension implements GlobalsInterface class Globals extends TwigExtension implements GlobalsInterface
{ {
protected array $globals = [];
public function __construct(protected Authenticator $auth, protected Request $request) public function __construct(protected Authenticator $auth, protected Request $request)
{ {
} }
@ -22,6 +24,18 @@ class Globals extends TwigExtension implements GlobalsInterface
* Returns a list of global variables to add to the existing list. * Returns a list of global variables to add to the existing list.
*/ */
public function getGlobals(): array public function getGlobals(): array
{
if (empty($this->globals)) {
$this->globals = $this->getGlobalValues();
}
return $this->globals;
}
/**
* Generates the list of global variables
*/
protected function getGlobalValues(): array
{ {
$user = $this->auth->user(); $user = $this->auth->user();
$themes = config('themes'); $themes = config('themes');

View File

@ -207,6 +207,7 @@ class AuthenticatorTest extends ServiceProviderTest
/** /**
* @covers \Engelsystem\Helpers\Authenticator::can * @covers \Engelsystem\Helpers\Authenticator::can
* @covers \Engelsystem\Helpers\Authenticator::isApiRequest
*/ */
public function testCan(): void public function testCan(): void
{ {

View File

@ -31,6 +31,7 @@ class GlobalsTest extends ExtensionTest
/** /**
* @covers \Engelsystem\Renderer\Twig\Extensions\Globals::__construct * @covers \Engelsystem\Renderer\Twig\Extensions\Globals::__construct
* @covers \Engelsystem\Renderer\Twig\Extensions\Globals::getGlobals * @covers \Engelsystem\Renderer\Twig\Extensions\Globals::getGlobals
* @covers \Engelsystem\Renderer\Twig\Extensions\Globals::getGlobalValues
*/ */
public function testGetGlobals(): void public function testGetGlobals(): void
{ {
@ -82,6 +83,7 @@ class GlobalsTest extends ExtensionTest
$this->assertGlobalsExists('theme', $theme2, $globals); $this->assertGlobalsExists('theme', $theme2, $globals);
// User // User
$extension = new Globals($auth, $request);
$globals = $extension->getGlobals(); $globals = $extension->getGlobals();
$this->assertGlobalsExists('user', $user, $globals); $this->assertGlobalsExists('user', $user, $globals);
$this->assertGlobalsExists('user_messages', 0, $globals); $this->assertGlobalsExists('user_messages', 0, $globals);
@ -89,15 +91,21 @@ class GlobalsTest extends ExtensionTest
$this->assertGlobalsExists('theme', $theme, $globals); $this->assertGlobalsExists('theme', $theme, $globals);
// User with not available theme configured // User with not available theme configured
$extension = new Globals($auth, $request);
$user->settings->theme = 9999; $user->settings->theme = 9999;
$globals = $extension->getGlobals(); $globals = $extension->getGlobals();
$this->assertGlobalsExists('themeId', 42, $globals); $this->assertGlobalsExists('themeId', 42, $globals);
// Request query parameter // Request query parameter
$extension = new Globals($auth, $request);
$request->query->set('theme', 1337); $request->query->set('theme', 1337);
$globals = $extension->getGlobals(); $globals = $extension->getGlobals();
$this->assertGlobalsExists('user', [], $globals); $this->assertGlobalsExists('user', [], $globals);
$this->assertGlobalsExists('themeId', 1337, $globals); $this->assertGlobalsExists('themeId', 1337, $globals);
$this->assertGlobalsExists('theme', $theme3, $globals); $this->assertGlobalsExists('theme', $theme3, $globals);
// Second retrieval is loaded directly
$globals = $extension->getGlobals();
$this->assertGlobalsExists('themeId', 1337, $globals);
} }
} }