Создание блога

miketomlin

Новичок
Ну это как-то костыльно...
В чем костыльно?..

Независимо от того, какие идентификаторы нужны в адресах, имена обязательных полей всегда одинаковы. Если по минимуму, то id может быть единственным обязательным полем. Двоякость типа поля проще поддерживать, чем двоякость имени поля с идентификаторами для адресов. Если не дублировать числовые id в слагах, когда слагов по сути нет. Но а если дублировать, то «как-то костыльно» делать выборку по таким слагам вместо числовых id.
 
Последнее редактирование:

firep91613

Новичок
Смотрю сейчас схему комментариев у вордпресс. Там у таблицы comments есть внешний ключ post_id, ну это понятно. И есть parent, видимо для реплик. Правильно ли я понимаю, что эта колонка нужна чтобы хранить id коммента, к которому эта реплика относится?

Ну т.е. нажимая на кнопку "ответить", JS'ом вставляется форма и отправляется со всеми данными тоже аяксом, в том числе и id коммента, которому ответили. И этот id пишется в колонку parent. Сама колонка nullable по умолчанию. Так чтоли получается?
 

miketomlin

Новичок
Я думаю у каждой колонки должно быть свое предназначение. Можно и слаг и айди хранить. Тут, например и айди и слаг - Создание-блога.88142/page-14
Так упомянутый подход этому не мешает. Когда ты объединяешь в адресе слаг и числовой id, числовой практически всегда первичен, а слаг играет роль оформительского довеска. В этом случае будут поля id и, например, slug. Когда в адресе используется только слаг, он является полноценным идентификатором, но при этом числовой, естественно, никто не запрещает использовать. В этом случае слаг будет в поле id, а числовой идентификатор в поле с другим именем. Есть как мин. еще два формата имени помимо просто id. У нас такое поле обычно называют в соответствии с описываемой сущностью и именем таблицы, например user в таблице prefix_user. У фреймворков много заточенных под это дело методов и функций. Но в принципе и это не обязательное требование, например я часто называю таблицы сущностью во можественном числе, а поле с числовым id – в единственном или еще короче: post в таблице prefix_posts, art в таблице prefix_articles. Смысл в том, что во всей БД поля с одинаковыми идентификаторами могут называться одиково, ну и доп. бонус – USING(field).
 
Последнее редактирование:

firep91613

Новичок
Как по уму вставлять данные в аякс запрос? У меня пока есть черновой вариант, но судя по всему он никуда не годится.
JavaScript:
comments.addEventListener('submit', (e) => {
    if (!e.target.classList.contains('comment__form')) return;
    e.preventDefault();

    const parentElem = e.target.parentNode;
    const url = e.target.getAttribute('action');
    const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
    const data = {
        content: e.target.children[1].value,
        post_id: {{ $post->id }},
        user_id: @auth {{ Auth::user()->id }} @else 0 @endauth,
        parent_id: +e.target.dataset.id || null
    };

    fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRF-TOKEN': token
        },
        body: JSON.stringify(data)
    })
        .then(response => response.json())
        .then(data => {
            console.log(data);

            if (!data.comment.parent_id) {
                e.target.after(getCommentAfterPublishing(data));
                e.target.children[1].value = '';
            } else {
                if (!parentElem.parentNode.querySelector('.comment__replies')) {
                    const wrap = document.createElement('div');
                    wrap.classList.add('comment__replies');
                    parentElem.parentNode.append(wrap);
                    wrap.append(getCommentAfterPublishing(data));

                } else {
                    parentElem.parentNode.querySelector('.comment__replies').prepend(getCommentAfterPublishing(data));
                }

                document.querySelector('.comment__action-reply').click();
            }
        })
        .catch(error => {
            console.log(JSON.parse(error));
        });
});
Тут можно открыть консоль и поменять на что угодно:
PHP:
post_id: {{ $post->id }},
user_id: @auth {{ Auth::user()->id }} @else 0 @endauth,
В контроллере сверять текущего пользователя и то что пришло?
 

firep91613

Новичок
А как в реальных приложениях поступают с этим? Следует ли ловить этот эксепшен? Ну подумаешь, видно что Laravel и PHP. Laravel ведь не испугать SQL-инъекциями?
1651
 

AmdY

Пью пиво
Команда форума
Это дев мод, в продакшен проект выкатывается с другими настройками, там не будут светиться трейсы. Пусть валятся экспешины, по мере надобности настроишь их логирование.
Обязательно перечитай всю доку от начала до конца, а не только по мере необходимости.
 

firep91613

Новичок
Появилась идея отображать даты публикации комментариев к постам как тут на форуме и с учетом часовой зоны пользователя. Ну типо "Сегодня", "Вчера", в такой-то день и т.д. Этой задачей должен заниматься PHP или JS? Просто если PHP, то не понятно, как ему можно передать часовую зону пользователя. И сама дата в базе хранится по часовому поясу Гринвича. Я сделал так:
PHP:
<p class="comment__date">{{ $comment->created_at->format('Y-m-d H:i:s') }}</p>
JavaScript:
function getFormatDate(date) {
    const daysOfWeek = ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'];
    const monthsOfYear = ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'];
    const currentDate = new Date();
    const currentDateMs = currentDate.getTime();
    const offset = currentDate.getTimezoneOffset() * 60 * 1000;
    const receivedDateMs = new Date(date).getTime() - offset;
    const receivedDate = new Date(receivedDateMs);
    const receivedDateDay = receivedDate.getDate();
    const currentDay = currentDate.getDate();
    const dayDiff = currentDay - receivedDateDay;
    const hours = (receivedDate.getHours() < 10) ? '0' + receivedDate.getHours() : receivedDate.getHours();
    const minutes = (receivedDate.getMinutes() < 10) ? '0' + receivedDate.getMinutes() : receivedDate.getMinutes();
    const pattern = `${hours}:${minutes}`;

    if (dayDiff === 0) {
        const diff = (currentDateMs - receivedDateMs);

        if (diff < 3600000) {
            if (diff < 60) {
                return 'Только что';
            }

            return `${Math.round(diff / 1000 / 60)} мин. назад`;
        }

        return 'Сегодня в ' + pattern;
    } else if (dayDiff === 1) {
        return 'Вчера в ' + pattern;
    } else if (dayDiff < 7) {
        return `${daysOfWeek[new Date(date).getDay()]} в ` + pattern;
    } else {
        return `${receivedDate.getDate()} ${monthsOfYear[receivedDate.getDay()]} ${receivedDate.getFullYear()}`;
    }
}

const allComments = [...document.querySelectorAll('.comment__date'), ...document.querySelectorAll('.comment__reply-date')];

for (let i = 0; i < allComments.length; i++) {
    allComments[i].innerText = getFormatDate(allComments[i].innerText);
}
Получается какая-то двойная работа... Сначала PHP пишет, потом JS за ним исправляет. Можно ли как-нибудь по грамотнее это реализовать?
 

AmdY

Пью пиво
Команда форума
Делай на стороне пхп, там карбон всё умеет. Там потом полезут нюансы вроде поиска по дате и т.д.
 

firep91613

Новичок
Как лучше организовать раздел "Настройки" в админке? Ну чтобы управлять сменой логотипов, дефолтных аватарок для юзеров и т.д. У меня сейчас все прописанно во вьюшках, ну то есть чтобы сменить логотип, надо лезть во вьюшку. Наверно это не правильно. Я придумал, но не знаю, нормальный ли это вариант...

1. Создать таблицу settings:
SQL:
id      name (string)                    value (string)
1       admin_logo                      путь к картинке
2       front_logo                      строка (на форонте нет логотипа, просто написано "Блог на Laravel")
3       default_user_avatar             путь к картинке
2. Создать компоновщики - https://laravel.su/docs/11.x/views#komponovshhiki-sablonov. В модельке сделать методы, чтобы дергать эти настройки. И саму модельку передавать в компоновщики, а компановщики уже будут вставлять. И, соответственно, все в админке буду загружать.

Сомнения в самой таблице, ну вдруг понадобятся еще какие-нибудь другие настройки (пока не знаю какие). Может можно это как-то лучше сделать? Я просто ничего умнее не придумал (
 

firep91613

Новичок
Блин, херня какая-то получилась...

PHP:
<form action="{{ route('admin.settings.update', $setting) }}" method="POST" enctype="multipart/form-data" class="form form--tag-edit">
    @csrf
    @method('PUT')

    <div class="form__group">
        @if ($setting->key == 'admin_logo' || $setting->key == 'default_users_avatar')
            <label for="{{ $setting->key }}" class="form__label">Выберите изображение:</label>
            <input type="file" name="{{ $setting->key }}" id="{{ $setting->key }}" class="form__input">
        @else
            <label for="{{ $setting->key }}" class="form__label">{{ $setting->key }}</label>
            <input id="{{ $setting->key }}" name="{{ $setting->key }}" type="text" value="{{ old('name', $setting->value) }}" class="form__input">
        @endif

        @error("{{ $setting->key }}")
            <div class="form__error">{{ $message }}</div>
        @enderror
    </div>

    <div class="form__group">
        <input type="submit" value="Сохранить" class="form__submit">
    </div>
</form>
PHP:
public function rules(): array
{
    return [
        'admin_logo' => 'required_without_all:site_logo,site_description,default_users_avatar|image|mimes:jpeg,png,jpg|dimensions:min_width=135,min_height=45,max_width=160,max_height=65',
        'site_logo' => 'required_without_all:admin_logo,site_description,default_users_avatar|max:20',
        'site_description' => 'required_without_all:admin_logo,site_logo,default_users_avatar|max:50',
        'default_users_avatar' => 'required_without_all:admin_logo,site_logo,site_description|image|mimes:jpeg,png,jpg|max:2048|dimensions:max_width=45,max_height=45'
    ];
}
PHP:
<div class="header__logo">
    @inject('settings', 'App\Services\SettingsService')
    <span class="header__image"><img src="{{ asset('images/' . $settings->getAdminLogo()) }}" alt="Логотип"></span>
</div>
Screenshot from 2025-07-03 21-55-43.png
Видимо у каждой настройки должен быть свой класс?
 

firep91613

Новичок
Ребят, ну я совсем забуксовал с этими настройками...
PHP:
class SettingSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(Setting $setting): void
    {
        $setting->create(['slug' => 'admin-logo', 'name' => 'Логотип админки', 'value' => 'test']);
        $setting->create(['slug' => 'site-title', 'name' => 'Заголовок сайта', 'value' => 'test']);
        $setting->create(['slug' => 'site-subtitle', 'name' => 'Подзаголовок сайта', 'value' => 'test']);
        $setting->create(['slug' => 'default-users-avatar', 'name' => 'Аватар по умолчанию', 'value' => 'test']);
    }
}
PHP:
Route::group(['prefix' => 'settings'], function () {
    Route::get('/', [SettingController::class, 'index'])->name('admin.settings.index');
    Route::get('/admin-logo', [SettingAdminLogoController::class, 'show'])->name('admin.settings.admin-logo.show');
    Route::get('/admin-logo/edit', [SettingAdminLogoController::class, 'edit'])->name('admin.settings.admin-logo.edit');
    Route::put('/admin-logo/update', [SettingAdminLogoController::class, 'update'])->name('admin.settings.admin-logo.update');


    // Route::get('/{setting}', [SettingController::class, 'edit'])->name('admin.settings.edit');
    // Route::put('/{setting}', [SettingController::class, 'update'])->name('admin.settings.update');
});
PHP:
@foreach ($settings as $setting)
    <tr class="table__row">
        <td class="table__cell">{{ $setting->id }}</td>
        <td class="table__cell">{{ $setting->name }}</td>
        <td class="table__cell">{{ $setting->value }}</td>
        <td class="table__cell">
            <a class="table__link" href="{{ route('admin.settings.' . $setting->slug . '.edit', $setting->slug) }}">Изменить</a>
        </td>
    </tr>
@endforeach
Я избавился от дурацких условий во вьюшке и валидаторе. Начинаю делать контроллеры для каждой настройки, но маршруты жестко прописаны... Хотя бы скажите, то что я сейчас делаю, это по идиотски или нет?
 

AnrDaemon

Продвинутый новичок
Появилась идея отображать даты публикации комментариев к постам как тут на форуме и с учетом часовой зоны пользователя. Ну типо "Сегодня", "Вчера", в такой-то день и т.д. Этой задачей должен заниматься PHP или JS? Просто если PHP, то не понятно, как ему можно передать часовую зону пользователя. И сама дата в базе хранится по часовому поясу Гринвича.

Получается какая-то двойная работа... Сначала PHP пишет, потом JS за ним исправляет. Можно ли как-нибудь по грамотнее это реализовать?
Как ты заметил, PHP никак не может знать часовой пояс клиента.
Так что пытаться это делать на стороне сервера как минимум глупо.
Отдай часовую зону клиенту и пусть он отображает как хочет.
 

firep91613

Новичок
Так что пытаться это делать на стороне сервера как минимум глупо.
Не, ну я сделал обязательным указание таймзоны при регистрации. А у всех остальных отображается по часовому поясу Москвы.
Отдай часовую зону клиенту и пусть он отображает как хочет.
А зачем клиенту часовая зона Гринвича? Там же время надо, которое в базе хранится, а у клиента JS отдает его зону.
 
Сверху