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

firep91613

Новичок

AnrDaemon

Продвинутый новичок
Он только один - отдавать то, что есть. Потому что бэк и фронт - это ДВА РАЗНЫХ приложения.
Работающих в разном окружении и выполняющих разные задачи.
 

firep91613

Новичок
как ты начал данные в мусор превращать
Щас вообще не понял. Что значит "превращать данные в мусор"? Я просто написал скрипт, создал JS файл, вставил код, подключил ко вьюшке. Все. В инструментах разработчика, кстати, правильно отображает. Да, забыл, в CSS написал комментарии - там так же каракули...
 

firep91613

Новичок
Есть файл config/custom.php, я там храню свои конфигурации всякие. Мне нужно передать эту штуку в модель - https://github.com/illuminate/contracts/blob/12.x/Config/Repository.php

PHP:
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Config\Repository as Config;

class Setting extends Model
{   /*
    public function __construct(protected Config $config)
    {
        parent::__construct();
    }
    */

    protected $fillable = ['value'];

    public function isImage($value): bool {
        //$imageExtensions = $this->config->get('custom.allowed_image_extensions');
        $imageExtensions = \Illuminate\Support\Facades\Config::get('custom.allowed_image_extensions');
        $extension = strtolower(pathinfo($value, PATHINFO_EXTENSION));

        return in_array($extension, $imageExtensions);
    }
}
Проблема в том, что через конструктор это не работает, вообще. Даже через контейнер не привязывается... Работает только фасад или хэлпер. Мне нужно проверять изображение или нет.

PHP:
<td class="table__cell">
    @if($setting->isImage($setting->value, config('custom.allowed_image_extensions')))
        <img src="{{ config('custom.symbolic_images_link') . $setting->value }}" alt="">
    @else
        {{ $setting->value }}
    @endif
</td>
Передавать в сам метод не вариант. Модель используется еще в другом классе. Как быть? Фасад не хочется юзать...
 

firep91613

Новичок
Я тут подумал, зачем мне передавать все в конструктор, ну там вью нужен нескольким методам, редирект вроде двум только. Для всех запросов загружается одно и тоже, даже если это не нужно. Сделал внедрение в методы. Вроде все хорошо, но есть некоторые методы, где аж четыре аргумента становиться. Вот, к примеру:
PHP:
public function edit(Post $post, CategoryQueries $categoryQueries, TagQueries $tagQueries, ViewFactory $view): View
{
    return $view->make(self::ACTION_EDIT, [
        'post' => $post,
        'categories' => $categoryQueries->getAll(),
        'tags' => $tagQueries->getAll()
    ]);
}
А если кроме категорий и тегов понадобится что-то еще? Будет пять. Не перебор ли?
 

AmdY

Пью пиво
Команда форума
В этом и суть, используя DI ты видишь как растёт сложность и количество зависимостей. По мере роста этих зависимостей ты можешь их объединять. Заводишь отдельный класс по логической связи (привет разработчика мадженты):
PHP:
class BlogComposer {

   public funtcion __construcor(public Post $post, public CategoryQueries $categoryQueries, public TagQueries $tagQuerie) {}

}
 

firep91613

Новичок
У меня получилось пока сделать только в одном месте.
PHP:
<?php declare(strict_types=1);

namespace App\Http\Controllers\Admin;

use App\Composers\StatComposer;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
use Illuminate\View\Factory as ViewFactory;

final class DashboardController extends Controller
{
    const ACTION_INDEX = 'admin.dashboard';
    const LAST_USERS_COUNT = 5;
    const LAST_COMMENTS_COUNT = 5;

    public function __invoke(StatComposer $statComposer, ViewFactory $view): View
    {
        return $view->make(self::ACTION_INDEX, [
            'usersCount' => $statComposer->userQueries->count(),
            'postsCount' => $statComposer->postQueries->count(),
            'latestUsers' => $statComposer->userQueries->getLastUsers(self::LAST_USERS_COUNT),
            'latestComments' => $statComposer->commentQueries->getLastComments(self::LAST_COMMENTS_COUNT)
        ]);
    }
}

public function edit(Post $post, CategoryQueries $categoryQueries, TagQueries $tagQueries, ViewFactory $view): View - вот тут видимо можно объеденить только $categoryQueries и $tagQueries? Потому что в маршруте же привязка есть - '/{post:slug}/edit'.

public function update(UpdatePostRequest $request, Post $post, PostQueries $postQueries, Redirector $redirect): RedirectResponse - а тут даже и не знаю что можно объеденить.
 

firep91613

Новичок
public function update(UpdatePostRequest $request, Post $post, PostQueries $postQueries, Redirector $redirect): RedirectResponse - а тут даже и не знаю что можно объеденить.
Я все понял! Надо в классах, которые Queries оставить только выборки, а всю логику с обновлением и удалением перенести в модели. Тогда будет только три.
 

firep91613

Новичок
Не получится. В моделях не работают кострукторы :( Куда тогда этот метод деть?
PHP:
public function update(Post $post, array $data): void
{
    try {
        if (isset($data['image'])) {
            $data['image'] = $this->uploader->uploadPostImage($data['image']);
        }

        $oldImage = $post->image;
        $tag_id = $data['tag_id'];
        unset($data['tag_id']);
        $this->db->beginTransaction();
        $post->update($data);
        $post->tags()->sync($tag_id);
        $this->db->commit();

        if ($oldImage && isset($data['image'])) {
            $this->storage->delete($oldImage);
        }
    } catch (\Throwable $e) {
        if (isset($data['image'])) {
            $this->storage->delete($data['image']);
        }

        $this->db->rollBack();
        throw new DbException("Ошибка обновления поста: {$e->getMessage()}");
    }
}
 

artoodetoo

великий и ужасный
Я думаю у каждой колонки должно быть свое предназначение. Можно и слаг и айди хранить. Тут, например и айди и слаг - Создание-блога.88142/page-14
Хранить то можно, но что является реальным ключом? Попробуй отредактировать этот "слаг" в URL
. Убедишься, что ключ здесь один. Остальное - для красоты.
 

artoodetoo

великий и ужасный
Не получится. В моделях не работают кострукторы :( Куда тогда этот метод деть?
Этот вопрос понятен только тебе. Какая связь этого кода с конструктором который якобы не работает, зачем его куда-то переносить, почему именно в модель?
ИМХО, в классе модели должны быть описаны только отношения и возможно какой-то необходимый кастинг типов. Для "логики" нужен дополнительный слой.
 

artoodetoo

великий и ужасный
Почему у меня в JS файлах вместо русских символов отображаются каракули? Это что, nginx не правильно кодировку отдает? Laravel же вроде никаких лишних заголовков не добавляет.

Посмотреть вложение 1653
У тебя код на PHP в какой кодировке? ))) А страница в какой кодировке?
Вот не думал что в 2025 такие заморочки ещё существуют.

Кстати, привычка всё скриншотить вместо копирования текста где это возможно, это плохая привычка. Вот как этот текст скормить в "автоматически распознаватель кодировки online" ?

Спросил ChatGPT как понимать эту картинку:
5300822004087255404.jpg
 
Последнее редактирование:

firep91613

Новичок
Этот вопрос понятен только тебе. Какая связь этого кода с конструктором который якобы не работает, зачем его куда-то переносить, почему именно в модель?
Я хотел сократить количество зависимостей у метода update. Потому что почти в каждом контроллере у этого метода по четыре зависимости. Для того чтобы сделать обновление нужно передавать экземпляр модели и этот класс, PostQueries в данном случае (Post $post, PostQueries $postQueries). А если сделать у модели метод, скажем, postUpdate, то его можно было бы вызывать на самом экземпляре модели и тогда бы PostQueries был бы не нужен.

ИМХО, в классе модели должны быть описаны только отношения и возможно какой-то необходимый кастинг типов. Для "логики" нужен дополнительный слой.
У меня так и сделано.
 

firep91613

Новичок
У тебя код на PHP в какой кодировке? ))) А страница в какой кодировке?
UTF-8 все. Я даже VSCode сменил на PhpStorm. Там внизу видно - UTF-8. Но ничего не поменялось.

Кстати, привычка всё скриншотить вместо копирования текста где это возможно, это плохая привычка. Вот как этот текст скормить в "автоматически распознаватель кодировки online" ?
Я как-то не подумал об этом.
 

artoodetoo

великий и ужасный
Данные из базы берутся? Если да, то надо проверить кодировку соединения с базой. Это не то, что светится в уголке PhpStorm, а то что прописано в параметрах соединения с базой. В Laravel это config/database.php По умолчанию клиент mysql соединяется как cp1252 !

В принципе, данные могут храниться в поле например с кодировкой cp1251, а читаться клиентом (т.е. php-скриптом) с кодировкой соединения utf8mb4. Или наоброт. Сервер БД умеет перекодировать туда-сюда. Shit happens когда данные перекодируются не в ту кодировку, которую ты объявляешь для отображения вебстраницы/данных json. Или данные уже были испорчены при записи по той же причине - кодировка соединения была указана не та.

Это, как мне кажется, и есть твой случай.
 
Последнее редактирование:

artoodetoo

великий и ужасный
А если сделать у модели метод, скажем, postUpdate, то его можно было бы вызывать на самом экземпляре модели и тогда бы PostQueries был бы не нужен.
Хз что такое PostQueries.
Ты можешь конечно шурупы забивать молотком, но они не для этого сделаны. Не надо раздувать класс модели. По моему на том уровне, на котором ты находишься вообще незачем играть в улучшайзинг. Тебе надо написать блог, а не передвинуть текст в другой класс. Только время тратишь впустую.
 

firep91613

Новичок
Данные из базы берутся? Если да, то надо проверить кодировку соединения с базой. Это не то, что светится в уголке PhpStorm, а то что прописано в параметрах соединения с базой. В Laravel это config/database.php По умолчанию клиент mysql соединяется как cp1252 !

В принципе, данные могут храниться в поле например с кодировкой cp1251, а читаться клиентом (т.е. php-скриптом) с кодировкой соединения utf8mb4. Или наоброт. Сервер БД умеет перекодировать туда-сюда. Shit happens когда данные перекодируются не в ту кодировку, которую ты объявляешь для отображения вебстраницы/данных json. Или данные уже были испорчены при записи по той же причине - кодировка соединения была указана не та.

Это, как мне кажется, и есть твой случай.
Не, сам контент, статьи, отображаются правильно. Я же писал, что это в файлах JS и CSS в исходном коде, когда открываешь. Вот приехала страница в браузер. Там такой код:
HTML:
    <script src="http://localhost:8080/js/slug.js"></script>
    <script>
        (new SlugGenerator(
            document.getElementById('title'),
            document.getElementById('slug')
        )).init();
    </script>
Содержимое файла slug.js такое:
JavaScript:
class SlugGenerator {
    #translitMap = {
        'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo', 'ж': 'zh',
        'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o',
        'п': 'p', 'Ñ€': 'r', 'Ñ': 's', 'Ñ‚': 't', 'у': 'u', 'Ñ„': 'f', 'Ñ…': 'h', 'ц': 'ts',
        'ч': 'ch', 'ш': 'sh', 'щ': 'sch', 'ÑŠ': '', 'Ñ‹': 'y', 'ÑŒ': '', 'Ñ': 'e', 'ÑŽ': 'yu', 'Ñ': 'ya'
    };
    #isPasting = false;
    #title;
    #slug;

    constructor(title, slug) {
        this.#title = title;
        this.#slug = slug;
    }

    init() {
        this.#title.addEventListener('input', this.#inputHandler.bind(this));
        this.#title.addEventListener('paste', this.#pasteHandler.bind(this));
    }

    #inputHandler(e) {
        if (this.#isPasting) return;

        const target = e.target;
        const targetValue = target.value;

        slug.value = this.#getUpdatedSlug(targetValue);
    }

    #pasteHandler(e) {
        this.#isPasting = true;

        const target = e.target;
        const data = e.clipboardData.getData('text/plain').trim();

        this.#setNewTitleValue(target, data);
        slug.value = this.#getUpdatedSlug(target.value);

        e.preventDefault();
        setTimeout(() => this.#isPasting = false, 0);
    }

    #getUpdatedSlug(targetValue) {
        let slug = '';

        for (let i = 0; i < targetValue.length; i++) {
            const ch = targetValue[i].toLowerCase();

            if (ch === ' ') {
                slug += '-';
            } else if (/[a-zа-ÑÑ‘0-9]/.test(ch)) {
                slug += this.#translitMap[ch] ?? ch.toLowerCase();
            }
        }

        return slug;
    }

    #setNewTitleValue(elem, data) {
        const start = elem.selectionStart;
        const end = elem.selectionEnd;

        elem.value = elem.value.substring(0, start) + data + elem.value.substring(end);
        elem.selectionStart = elem.selectionEnd = start + data.length;
    }
}
У меня там в pgsql стоит 'charset' => env('DB_CHARSET', 'utf8'). Я там вообще ничего не трогал. Это как у ларавела было так и осталось.
 

firep91613

Новичок
Хз что такое PostQueries.
А это как раз дополнительный слой для логики. Чтобы не раздувать модели. Вот:
PHP:
<?php declare(strict_types=1);

namespace App\Queries\Admin;

use App\Models\Post;
use App\Exceptions\DbException;
use App\Services\ImageUploader;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Database\Connection;
use Illuminate\Pagination\LengthAwarePaginator;

final class PostQueries
{
    const PER_PAGE = 5;

    public function __construct(
        protected Post $model,
        protected Connection $db,
        protected Filesystem $storage,
        protected ImageUploader $uploader
    ) {}

    public function save(array $data): void
    {
        try {
            if (isset($data['image'])) {
                $data['image'] = $this->uploader->uploadPostImage($data['image']);
            }

            $tag_id = $data['tag_id'];
            unset($data['tag_id']);

            $this->db->beginTransaction();
            $post = $this->model->create($data);
            $post->tags()->attach($tag_id);
            $this->db->commit();
        } catch (\Throwable $e) {
            if (isset($data['image'])) {
                $this->storage->delete($data['image']);
            }

            $this->db->rollBack();
            throw new DbException("Ошибка создания поста: {$e->getMessage()}");
        }
    }

    public function update(Post $post, array $data): void
    {
        try {
            if (isset($data['image'])) {
                $data['image'] = $this->uploader->uploadPostImage($data['image']);
            }

            $oldImage = $post->image;
            $tag_id = $data['tag_id'];
            unset($data['tag_id']);
            $this->db->beginTransaction();
            $post->update($data);
            $post->tags()->sync($tag_id);
            $this->db->commit();

            if ($oldImage && isset($data['image'])) {
                $this->storage->delete($oldImage);
            }
        } catch (\Throwable $e) {
            if (isset($data['image'])) {
                $this->storage->delete($data['image']);
            }

            $this->db->rollBack();
            throw new DbException("Ошибка обновления поста: {$e->getMessage()}");
        }
    }

    public function delete(Post $post): void
    {
        try {
            $image = $post->image;

            $this->db->beginTransaction();
            $post->tags()->detach();
            $post->category()->dissociate();
            $post->delete();
            $this->db->commit();

            if ($image) {
                $this->storage->delete($image);
            }
        } catch (\Throwable $e) {
            $this->db->rollBack();
            throw new DbException("Ошибка удаления поста: {$e->getMessage()}");
        }
    }

    public function latestPaginated(): LengthAwarePaginator
    {
        return $this->model->orderBy('id', 'desc')->paginate(self::PER_PAGE);
    }

    public function count(): int
    {
        return $this->model->count();
    }
}
Тебе надо написать блог
Дак он уже написан. В принципе, им уже можно пользоваться и заливать на хостинг. Хотя может я и ошибаюсь. Регистрация есть, авторизация, пользователям можно менять группы, пользователи могут писать комментарии к статьям, искать информацию, редактировать свои профили. Админка тоже есть, там можно всем управлять. Ну тесты еще не все написал.
По моему на том уровне, на котором ты находишься вообще незачем играть в улучшайзинг.
А на каком уровне стоит? Мне все таки хочется сделать код максимально чистым.
 
Сверху