Къстъм форум със Symfony + Twig

ларавела го убиха, абсурд е да се почва каквото и да е на него.
След като осраха WP-са аз съм избрал точно симфони.
 
Аз не съм съгласен за мнението за Symfony. Наскоро даже излезе нова версия.. съпорта е добър и отделно с него съм си свикнал да работя и там имам най-голям опит. На мен не ми е проблем да се пише повече... даже, както @Revelation каза.. това ти дава контрол над нещата и аз точно това искам. Работил съм преди години с Laravel.. просто не го чувствам моя фреймуорк, но всеки си избира за себе си :)
 
Накрая всичко стига до това на какво сме свикнали така или иначе :) . Cheers, пишете на каквото ви е удобно, само изказах мнение. Важното е, кода да се чете и да е читав що годе според модерните стандарти, другото - са лични предпочитания :)
 
От месеци насам и на мен ми е дошло на ум да почна разработка на форум, подобен на този (говорим само за /discuss). Иска ми се да е насочен само към българските Laravel разработчици и да си помагаме взаимно. Не е кой знае какво - ларавел ти предоставя напълно готова система за логин и регистър (и не само), а останалото са постове, отговори, отговори на отговори...дребни неща... Като се замисля е това (Като изключим излишните функции) 😀 И на мен ми идват в повечко тези реакции, споделяния и лайкове на постове, модераторски панели, съобщения между потребители, докладвания, рожденни дни и т.н., за това няма да е лошо да се разработи нещо базово, върху което да се надгражда (ако е нужно - в нашият случай, по-скоро не).

Пожелавам ти успех и дано се получи нещо готино 🙂

В интерес на истината аз започнах разработката на моя (discussion) форум. Споделям снимки до къде съм стигнал за 2 дена 😁

Приемам всякакви предложения 🥳

jqnmdSw.png
Z2z3vOs.png
SYl9V0B.png
de2Iv8S.png
RUjTjlV.png
XE6RQpV.png
eBpGuzV.png
 
Последно редактирано:
Поздравления! Брутално добро .. изчистено и семпло и в същото време има това от което имаш нужда. За 2 дни работа си е направо перфектно.
В интерес на истината аз моя още 1 ред не съм записал понеже още на другия ден след като написах темата и ми изкочиха лични проблеми и нямах време след работа да седна дори за минутка на компютъра. Надявам се скоро да го започна.
Успех при теб и с интерес ще следя развитие , за това актуализирай feed-a, когато имаш нещо ново за показване :p
 
Да, наистина вървиш в много правилна посока. :)

Предполагам първо ще оформиш главния екран, началната. Може да сложиш тагове, не че ние сме го направили тук, но може би ще внедрим с някакъв лимит, играят добра връзка между темите. Ще се включа и с още предложения, ако нямаш против, гледайки развитието на форумчето.
 
Здравейте отново! Не съм направил кой знае колко още по форумчето, тъй като не ми остава време. От друга страна пък закупих домейн и droplet от digitalocean, но проблемът е, че нещо не ми се получава setup-а на сървъра. Нека спомена, че за първи път конфигурирам сървър. През последните 3 часа от 4 опита - 4 неуспешни, все нещо изкача като грешка и директно rebuild-вам droplet-a и започвам наново. Имам следният въпрос - някой по-запознат в тази сфера ще може да ми даде насоки и като цяло да помогне да вдигнем сървъра, предполагам, че за професионалист няма да отнеме повече от 20-30 минути. Ще съм изключително благодарен. Поздрави! :)

Сървъра:
1 GB Memory / 1 Intel vCPU / 25 GB Disk / AMS3 - Ubuntu 18.04 (LTS) x64


П.С: Няма да издавам все още домейна, но ще ви хареса (надявам се) 🍻
 
Здравейте отново! Не съм направил кой знае колко още по форумчето, тъй като не ми остава време. От друга страна пък закупих домейн и droplet от digitalocean, но проблемът е, че нещо не ми се получава setup-а на сървъра. Нека спомена, че за първи път конфигурирам сървър. През последните 3 часа от 4 опита - 4 неуспешни, все нещо изкача като грешка и директно rebuild-вам droplet-a и започвам наново. Имам следният въпрос - някой по-запознат в тази сфера ще може да ми даде насоки и като цяло да помогне да вдигнем сървъра, предполагам, че за професионалист няма да отнеме повече от 20-30 минути. Ще съм изключително благодарен. Поздрави! :)

Сървъра:
1 GB Memory / 1 Intel vCPU / 25 GB Disk / AMS3 - Ubuntu 18.04 (LTS) x64


П.С: Няма да издавам все още домейна, но ще ви хареса (надявам се) 🍻

Следвай документацията на DO. Там доста добре са описали какво да направиш :)

 
Следвай документацията на DO.

Точно на нея ми бяха неуспешните опити. Разбрах за larasail и мисля, че всичко е наред в момента.
 
Точно на нея ми бяха неуспешните опити. Разбрах за larasail и мисля, че всичко е наред в момента.

Странна работа. Буквално трябва Copy/Paste на всички команди и го подкарваш :D
 
Както и да е, в играта сме :) Подкарах го, всичко изглежда повече от перфектно. Скоро качвам форумчето до там, до където съм стигнал с него и ще ви споделя линка.
 
Здравейте отново! Не съм направил кой знае колко още по форумчето, тъй като не ми остава време. От друга страна пък закупих домейн и droplet от digitalocean, но проблемът е, че нещо не ми се получава setup-а на сървъра. Нека спомена, че за първи път конфигурирам сървър. През последните 3 часа от 4 опита - 4 неуспешни, все нещо изкача като грешка и директно rebuild-вам droplet-a и започвам наново. Имам следният въпрос - някой по-запознат в тази сфера ще може да ми даде насоки и като цяло да помогне да вдигнем сървъра, предполагам, че за професионалист няма да отнеме повече от 20-30 минути. Ще съм изключително благодарен. Поздрави! :)

Сървъра:
1 GB Memory / 1 Intel vCPU / 25 GB Disk / AMS3 - Ubuntu 18.04 (LTS) x64


П.С: Няма да издавам все още домейна, но ще ви хареса (надявам се) 🍻
Ти не каза какви грешки изкачат. Ние как да знаем какво може да се дъни? От права на папки (конфигурация на сървъра на ниско ниво) до конфигурация на самия апп има голям гап.
 
Под мобилна версия има скрул...
 
Хубава работа човек, ама... разкарай тея NFT скамове и маймуни - като че ли, юзърите сме идиоти и с акъла на маймуни. Точно тея NFT-та, с маймуните, са пример за глупост и слабоумие сред ползващите ги. Един вид юзърите ги имаш с един акъл като ейповете.

И я чукни контролера да го погледна, ей така едит метода и индекса например, на posts. Иначе изглежда добре, малко забележки -
Сложи лимит за username. За delete / разни confirm action-и - не съм бил фен никога на sweetalerta мисля, че е това - много е дразнещ - излиза изведнъж на екрана, анимира, затваря се после - гледай от тоя форум - измислили са го без да тормозят юзъра с излишни неща по екрана.

Махни и капчата - все пак тая капча стои зад Auth - няма да я наспамят - къстом е, не знаят програмите като scrapebox какво да търсят и къде да пращат формата.

За форума - то е ясно, че го оправяш още като вид - публикувано / публикувано от - махай ги тез работи, датата да си стои само, и публични профили да могат да се виждат, и някое друго поле за юзърите - да кажем website url - че да събереш регистрации от seo хора, може пък и да напишат нещо.

И softDeletes на всичко - задължително - и използвай uuid-та , вместо id-та, че се exposevat, с тези два трейта после е много удобно - e трябва и моделите да имат uuid пропърти ( $table('uuid')->uuid(); в миграцията ) , горното ще ти създава uuid автоматично, само трябва да го use-неш в модела, дето ще работиш с uuid, пък второто като го викнеш в Модела - няма да ползва id-то за Find:: и елокуент, а ще ползва uuid филда -



PHP:
<?php

namespace App\Traits;

use Illuminate\Support\Str;


trait CreateUuid
{
    /**
     * Override the boot function from Laravel so that
     * we give the model a new UUID when we create it.
     */
    protected static function boot()
    {
        parent::boot();

        $creationCallback = function ($model) {
            if (empty($model->{$model->getKeyName()}))
            {
                $model->uuid = Str::uuid()->toString();
            }
        };

        static::creating($creationCallback);
    }





}

PHP:
<?php

namespace App\Traits;

trait UseUuid
{

    public function getRouteKeyName()
    {
        return 'uuid';
    }

}


Супер е станало, добре че не си се заел със Симфонията.
 
Последно редактирано:
... добре че не си се заел със Симфонията.
Защо? Искаш да кажеш, че горните парчета код, които трябва да приложиш в Ларавел са нещо хубаво и се радваш, че ги има като опция?
 
разкарай тея NFT скамове и маймуни - като че ли, юзърите сме идиоти и с акъла на маймуни. Точно тея NFT-та, с маймуните, са пример за глупост и слабоумие сред ползващите ги. Един вид юзърите ги имаш с един акъл като ейповете.

Не е с такава замисъл :D Най-вероятно ще ги сменя с нещо друго, но след като измисля с какво.

Махни и капчата - все пак тая капча стои зад Auth - няма да я наспамят - къстом е, не знаят програмите като scrapebox какво да търсят и къде да пращат формата.

Капчата в моят случай играе повече роля на анти-идиот, вместо анти-бот. Хора, които не могат да решат задачата "Създайте промелива $foo, в която да съхраните "foo"" не мисля, че има смисъл да пускат теми относно Laravel (дано не съм прозвучал грубо..). Поправете ме, ако греша.

За форума - то е ясно, че го оправяш още като вид - публикувано / публикувано от - махай ги тез работи, датата да си стои само, и публични профили да могат да се виждат, и някое друго поле за юзърите - да кажем website url - че да събереш регистрации от seo хора, може пък и да напишат нещо.

Тепърва ще ги дооправям тези неща. Ще има доста информация, която ще могат да попълнят потребителите за профила си.

И softDeletes на всичко - задължително - и използвай uuid-та , вместо id-та, че се exposevat, с тези два трейта после е много удобно - e трябва и моделите да имат uuid пропърти ( $table('uuid')->uuid(); в миграцията ) , горното ще ти създава uuid автоматично, само трябва да го use-неш в модела, дето ще работиш с uuid, пък второто като го викнеш в Модела - няма да ползва id-то за Find:: и елокуент, а ще ползва uuid филда -

В момента няма softDeletes, което е доста голяма грешка, да. Ще го разработя. Отсносно ID-тата също си прав. Напомням, че това, което виждате в момента е работа за 2 дена 😀

И я чукни контролера да го погледна

PHP:
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;
use App\Models\Category;
use App\Models\User;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {  

        $categories = Category::get();

        $posts = Post::latest()->with('category', 'user')->withCount('replies')
                ->when($request->category, function ($query, $category) {
                    $query->where('category_id', '=', $category);
                })
                ->when($request->status, function($builder, $status) {
                    $builder->when($status == 1, function($query) use ($status) {
                        $query->whereNotNull('best_reply_id');
                    })->when($status == 2, function($query) use ($status) {
                        $query->whereNull('best_reply_id');
                    });
                })
                ->when($request->fresh, function($builder, $fresh) {
                    $builder->when($fresh == 1, function($query) use ($fresh) {
                        $query->whereHas('replies');
                    })->when($fresh == 2, function($query) use ($fresh) {
                        $query->doesntHave('replies');
                    });
                })
                ->simplePaginate(5);

        return view('website.post.index', [
            'posts' => $posts,
            'categories' => $categories
        ]);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        $rand = mt_rand(1, count(config('recaptchaQuestions')));
        $question = config('recaptchaQuestions')[$rand]['question'];

        session()->put('recaptcha-question', $rand);

        return view('website.post.create', [
            'categories' => Category::all(),
            'question' => $question
        ]);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \App\Http\Requests\StorePostRequest  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $request->validate([
            'category_id' => 'required|exists:categories,id',
            'title' => 'required|min:5|max:100',
            'content' => 'required|min:10|max:5000'
        ]);

        $request->merge([
            'user_id' => auth()->user()->id,
            'slug' => \Str::slug($request->title),
            'best_reply_id' => null
        ]);

        $answer = config('recaptchaQuestions')[session()->get('recaptcha-question')]['answer'];

        if(!in_array(str_replace(' ', '', $request->answer), $answer)) {
            $errors = new \Illuminate\Support\MessageBag();

            $errors->add('answer', 'Отговорът на въпроса не е правилен!');
           
            return back()->withInput()->withErrors($errors);
        }

        if(Post::where('slug', $request->slug)->exists()) {
            $request->merge([
                'slug' => $request->slug . "-" . (substr((ord($request->slug)), 0, 2) + mt_rand(1, 173)),
            ]);
        }

        if(Post::create($request->all())) {
            User::find(auth()->user()->id)->increment('points', config('points.create_thread'));
            flash('Успешно добавихте поста!', 'success');
            return redirect()->route('posts.index');
        } else {
            return back();
        }
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function show(Post $post)
    {
        $post->increaseViews($post);
        $post->load('category', 'user', 'replies')->loadCount('replies');
        $post->replies->load('user');
        return view('website.post.show', [
            'post' => $post
        ]);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function edit(Post $post)
    {
        if(auth()->user()->id != $post->user_id) return back();

        $rand = mt_rand(1, count(config('recaptchaQuestions')));
        $question = config('recaptchaQuestions')[$rand]['question'];

        session()->put('recaptcha-question-edit', $rand);

        return view('website.post.edit', [
            'post' => $post,
            'categories' => Category::all(),
            'question' => $question
        ]);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \App\Http\Requests\UpdatePostRequest  $request
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function update(Post $post, Request $request)
    {

        if(auth()->user()->id != $post->user_id) return back();

        $request->validate([
            'category_id' => 'required|exists:categories,id',
            'title' => 'required|min:5|max:100',
            'content' => 'required|min:10|max:5000'
        ]);

        $request->merge([
            'user_id' => auth()->user()->id,
            'slug' => $post->slug,
            'best_reply_id' => null
        ]);

        $answer = config('recaptchaQuestions')[session()->get('recaptcha-question-edit')]['answer'];

        if(!in_array(str_replace(' ', '', $request->answer), $answer)) {
            $errors = new \Illuminate\Support\MessageBag();

            $errors->add('answer', 'Отговорът на въпроса не е правилен!');
           
            return back()->withInput()->withErrors($errors);
        }

        if(Post::find($post->id)->update($request->all())) {
            flash('Успешно редактирахте темата си!', 'success');
        } else {
            flash('Не можахме да редактираме вашата тема в момента. Моля, опитайте по-късно!', 'error');
        }
       
        return redirect()->route('posts.show', ['post' => $post->slug]);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Http\Response
     */
    public function destroy(Post $post)
    {  

        if($post->created_at < now()->subDays(2)) return back();

        $post->update([
            'best_reply_id' => null
        ]);

        foreach($post->replies as $reply) {
            $reply->delete();
        }

        if($post->delete()) {
            flash('Успешно изтрихте темата!', 'success');
            return redirect()->route('posts.index');
        } else {
            flash('Не успяхме да изтрием темата. Моля, опитайте отново!', 'error');
            return back();
        }
    }
}
 
Супер досадно ще е ако трябва да попълваш някакви такива капча глупости всеки път като искаш да постнеш нещо.. - освен това не работи правилно и трябва да гадаеш кой е правилния отговор :) Всички от долните примерни би трябвало да те пускат да преминеш капчата...

PHP:
$foo = 'Laravel-Bulgaria';
$foo='Laravel-Bulgaria';
$foo="Laravel-Bulgaria";

Също контейнерите на различните места са с различна ширина.
 
Последно редактирано:
Супер досадно ще е ако трябва да попълваш някакви такива капча глупости всеки път като искаш да постнеш нещо.. - освен това не работи правилно и трябва да гадаеш кой е правилния отговор :) Всички от долните примерни би трябвало да те пускат да преминеш капчата...

PHP:
$foo = 'Laravel-Bulgaria';
$foo='Laravel-Bulgaria';
$foo="Laravel-Bulgaria";

Също контейнерите на различните места са с различна ширина.
че даже и
PHP:
$foo = "Laravel-Bulgaria";
:)

Предвидил съм го това, както може да видиш в този код

Код:
   3 => [
       'question' => 'Как ще създадете PHP променлива $foo, в която да съхраните "' . config('app.name') . '"?',
       'answer' => [
             '$foo="' . config('app.name') . '";',
             '$foo=\'' . config('app.name') . '\';'
       ]
   ]

само съм забравил да добавя отговорите със space. Пак повтарям, това, което виждаш е направено за 2 дена. Не съм се вманиачавал в изкусуряването на нещата все още.

А относно това, че ще е досадно - може би да, може би не. Ще го реша впоследствие.
 

Back
Горе