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

AnrDaemon

Продвинутый новичок
Или ты не согласен с этим утверждением?
Учимся читать. Не только ABNF, но вообще в принципе читать.
Секция path содержит определение
Код:
pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
Легко проверить, что оно включает и + и & и ещё много чего ещё. И всё это означает именно то, что написано. Единственное исключение - группа pct-encoded.
 

firep91613

Новичок
Как такое может быть? Вчера я писал код и было все в порядке, все работало. Сегодня я писал тесты, начал запускать их и у меня они проваливались - таблица posts отсутствует. Я открыл в браузере - действительно ошибка: relation "posts" does not exist. Зашел в контейнер, там тоже таблицы нет. Ничего вроде я не менял. Все настройки такие же. Мистика какая-то...
 

firep91613

Новичок
Ну вот, DI. Щас же лучше?
PHP:
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
use Illuminate\View\Factory as ViewFactory;

class PostController extends Controller
{
    private ViewFactory $viewFactory;
    private Redirector $redirector;
    private Post $post;
    private array $fields = ['title', 'slug', 'excerpt', 'content', 'author', 'category'];

    public function __construct(ViewFactory $viewFactory, Redirector $redirector, Post $post)
    {
        $this->viewFactory = $viewFactory;
        $this->redirector = $redirector;
        $this->post = $post;
    }
    
    public function index(): View
    {
        $posts = $this->post->latest()->paginate(5);
        return $this->viewFactory->make('posts.index', ['posts' => $posts]);
    }

    public function create(): View
    {
        return $this->viewFactory->make('posts.create');
    }

    public function store(Request $request): RedirectResponse
    {
        $request->validate([
            'title' => 'required|string|max:500',
            'slug' => 'required|string|max:50|unique:posts',
            'excerpt' => 'required|string|max:1000',
            'content' => 'required|string',
            'author' => 'required|string|max:50',
            'category' => 'required|string|max:50'
        ]);

        $this->post->create($request->only($this->fields));

        return $this->redirector->route('posts.index')->with('success', 'Пост успешно создан!');
    }

    public function show(string $slug): View
    {
        $post = $this->post->where('slug', $slug)->firstOrFail();
        return $this->viewFactory->make('posts.show', ['post' => $post]);
    }

    public function edit(string $slug): View
    {
        $post = $this->post->where('slug', $slug)->firstOrFail();
        return $this->viewFactory->make('posts.edit', ['post' => $post]);
    }

    public function update(Request $request, string $slug): RedirectResponse
    {
        $request->validate([
            'title' => 'required|string|max:500',
            'slug' => 'required|string|max:50',
            'excerpt' => 'required|string|max:1000',
            'content' => 'required|string',
            'author' => 'required|string|max:50',
            'category' => 'required|string|max:50'
        ]);

        $post = $this->post->where('slug', $slug)->firstOrFail();
        $post->update($request->only($this->fields));

        return $this->redirector->route('posts.index')->with('success', 'Пост успешно обновлён!');
    }

    public function destroy(string $slug): RedirectResponse
    {
        $post = $this->post->where('slug', $slug)->firstOrFail();
        $post->delete();

        return $this->redirector->route('posts.index')->with('success', 'Пост успешно удалён!');
    }
}
PHP:
<?php

use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Models\Post;

class PostControllerTest extends TestCase
{
    use DatabaseTransactions;
    
    public function testIndexMethod()
    {
        Post::factory()->count(10)->create();

        $response = $this->get(route('posts.index'));
        $response->assertStatus(200);
        $response->assertViewIs('posts.index');
        $response->assertViewHas('posts');

        $this->assertCount(5, $response->original->getData()['posts']);
    }

    public function testCreateMethod()
    {
        $response = $this->get('/posts/create');
        $response->assertStatus(200);
        $response->assertViewIs('posts.create');
    }

    public function testStoreMethod()
    {
        $data = Post::factory()->make()->toArray();

        $response = $this->withoutMiddleware()->post(route('posts.store'), $data);

        $this->assertDatabaseHas('posts', [
            'title' => $data['title'],
            'slug' => $data['slug'],
            'excerpt' => $data['excerpt'],
            'content' => $data['content'],
            'author' => $data['author'],
            'category' => $data['category']
        ]);

        $response->assertRedirect(route('posts.index'));
        $response->assertSessionHas('success', 'Пост успешно создан!');
    }

    
    public function testShowMethodDisplaysPost()
    {
        $post = Post::factory()->create(['slug' => 'test-slug']);

        $response = $this->get(route('posts.show', ['slug' => 'test-slug']));
        $response->assertStatus(200);
        $response->assertViewHas('post', $post);
    }
    
    public function testShowMethod404()
    {
        $response = $this->get(route('posts.show', ['slug' => 'bla-bla']));
        $response->assertStatus(404);
    }
    
    public function testEditMethodDisplaysView()
    {
        $post = Post::factory()->create(['slug' => 'test-slug']);

        $response = $this->get(route('posts.edit', ['slug' => 'test-slug']));
        $response->assertStatus(200);
        $response->assertViewHas('post', $post);
    }
   
    public function testEditMethod404()
    {
        $response = $this->get(route('posts.edit', ['slug' => 'bla-bla']));
        $response->assertStatus(404);
    }
    
    public function testUpdateMethod()
    {
        Post::factory()->create(['slug' => 'test-slug']);
        $newData = [
            'title' => 'Test title',
            'slug' => 'test-slug',
            'excerpt' => 'Test excerpt',
            'content' => 'Test content',
            'author' => 'Test author',
            'category' => 'Test category'
        ];
        
        $response = $this->withoutMiddleware()->put(route('posts.update', ['slug' => 'test-slug']), $newData);
        $this->assertDatabaseHas('posts', $newData);
        $response->assertRedirect(route('posts.index'));
        $response->assertSessionHas('success', 'Пост успешно обновлён!');
    }
    
    public function testUpdateMethod404()
    {
        $newData = [
            'title' => 'Test title',
            'slug' => 'test-slug',
            'excerpt' => 'Test excerpt',
            'content' => 'Test content',
            'author' => 'Test author',
            'category' => 'Test category'
        ];

        $response = $this->withoutMiddleware()->put(route('posts.update', ['slug' => 'bla-bla']), $newData);
        $response->assertStatus(404);
    }
   
    public function testDestroyMethod()
    {
        Post::factory()->create(['slug' => 'test-slug']);

        $response = $this->withoutMiddleware()->delete(route('posts.destroy', ['slug' => 'test-slug']));
        $this->assertDatabaseMissing('posts', ['slug' => 'test-slug']);
        $response->assertRedirect(route('posts.index'));
        $response->assertSessionHas('success', 'Пост успешно удалён!');
    }
    
    public function testDestroyMethod404()
    {
        $response = $this->withoutMiddleware()->delete(route('posts.destroy', ['slug' => 'bla-bla']));
        $response->assertStatus(404);
    }
}
58.png
 

AmdY

Пью пиво
Команда форума
$this->post->where('slug', $slug) - такие вещи надо прятать внутрь модели-репозитория. Не надо размазывать работу с квери билдером по контроллерам, потом это больно сапортить.
$this->post->findBySlug($slug);

И ещё пользуйся кастомными реквестами, а не проверками в контроллере https://laravel.com/docs/11.x/validation#form-request-validation

$this->redirector->route('posts.index') выноси в константы вроде PostController::ACTION_INDEX

Любая строковая переменная показывает на пахнущий код, и надо задумываться можно ли сделать по другому.
 

AmdY

Пью пиво
Команда форума
Ты же свежий php используешь?
PHP:
public function __construct(protected ViewFactory $viewFactory, protected Redirector $redirector, protected Post $post)
 

firep91613

Новичок
Ты же свежий php используешь?
Да. Забываю все время про эту возможность.

Я правильно понял, что модели лучше не юзать в контроллерах, а создавать репозитории для них с методами и сами репозитории передавать в контроллеры?
 

AmdY

Пью пиво
Команда форума
Я правильно понял, что модели лучше не юзать в контроллерах, а создавать репозитории для них с методами и сами репозитории передавать в контроллеры?
Да, квери билдеры ни в коем случае нельзя использщовать за пределами слоя стораджа. Ни в контроллерах, ни в хендерах. Эта самая проблемная часть при сапорте проектов, потому что самая хрупкая часть и изменения часто надо вносить пачками.
При чём надо понимать, что твои репозитории будут пересекаться между собой. Для атворизации тебе нужен Юзер, для оформление покупки тебе нужен тоже юзер, но уже с контактами и адресом и платежными данными, для работы с каталогом нужен юзер с настройками пользователя. И ты всё это сделал, а потом вдруг появляется требование, чтобы адрес хронился не одной строкой в базе, а в отдельной таблице с кучей полей и адресов может быть больше одного. И надо представить, как и какие измнения ты будешь делать, при этом чтобы не пропустить изменения в других местах.
 

firep91613

Новичок
Создал таблицу categories и связал ее с таблицей posts, теперь в posts только category_id. Чтобы на страницы создания и редактирования поста выводить названия мне надо создать модель Categoty и репозиторий для нее? И репозиторий передавать в контроллер Posts?
 

AmdY

Пью пиво
Команда форума
В 99% люди заводят репозитории под таблицы.
Но с другой стороны у тебя это связанный контекст и ты можешь поделить его по функционалу. Для админки сделать PostsCrudRepository, а для отображения PostsPublicRepository. И так как вывод постов в админке, редактирование постов и категорий связаны в контексте crud - то всё это делаешь в PostsCrudRepository не заводя отдельный репозиторий для категорий.
 
Сверху