Savjet - Laravel, klasa dostupna na razini aplikacije


#1

Dakle trebam savjet, trebam klasu napraviti dostupnom na razini aplikacije, ali je i želim (datoteku) izdvojiti od kontrolera, modela…

Riješio sam tako da napravim novi folder Helpers (unutar app foldera) i tamo smjestim klasu (ajmo reć da se zove “NijeKontroler”).
Pozivam je u view sa “App\Helpers\NijeKontroler::metoda()” i sve je ok i funkcionira.
Pitanje je da li je to “best practice” ili postoji bolji i pravilniji način?


#2

Obično je to nekakav library, recimo u codeigniteru je to library(mapa library) kojeg mogu pozvati u view-u ili controlleru i on je zaseban komad code-a.

primjer je recimo library za acl u kojem imam sve vezano za prava , a samostalno komunicira sa bazom i sve ostalo.


#3

Ti mozes dodavati foldera koliko zelis… Ne trebas se zamarati s tim dal je best practise ili nije…
Bitno je napraviti i onda refactor koda raditi.
Ali da to je dobar način.


#4

Zato što se prije nisam zamarao o best practices, nisam se zamaro niti sa OOP u php-u (jer php u svojoj osnovi nije OOP). Pa sad imam “problem” :smiley:
Uglavnom, ako radim - želim da bude kako spada.
Refactoring radim uvijek, to je standardno (kao i nazivi klasa, metoda i property-a).
Obzirom na helper-e postoji nekoliko varijanti kako se to rješava u Laravelu, ova mi se činila najzgodnija, međutim malo je nespretna za korištenje. Iako je lako čitljiva.


#5

Natjeraj se da prosljedjuješ varijable u view template.
Može se i varijabla proslijediti a da sadrži klasu objekt.

Recimo da ovako nije neobično proslijediti iz kontrolera:

use new App\Services\CustomClass;
...
...
...
$customName = new CustomClass();
return view('home', compact('customName'));

a onda u view-u koristiš sa

{{ $customName->method() }}

Naravno u ovom primjeru se očekuje da ova metoda vraća nešto što može da prodje kroz echo od blade-a tj da je string, int…
Ako bi bio slučaj da se treba raditi neka veća logika u metodi, to ipak ne bi trebalo raditi na nivou
view-a/template-a. Bar je moje mišljenje da u view ne ide ništa puno više od foreach-a i slično - držim se pravila šta je dostupno u Smarty-ju, ne treba preko toga ići. Odnosno prosta manipulacija sa varijablama proslijedjenim iz kontrolera.

E sad, ako imaš potrebu za odredjenom varijablom u sve i jednom blade template-u to se može proslijediti kroz boot metod service provider-a:

Recimo imaš klasu:

<?php

namespace App\Services;

class CustomClass
{
    public function __construct()
    {
        //
    }

    /**
     * @param int $a
     * @param int $b
     * @return int
     */
    public static function add(int $a = 1, int $b = 2): int
    {
        return $a + $b;
    }
}

Možeš da uvrstiš varijablu dostupnu u sve i jednom view-u kroz AppServiceProvider:

<?php

namespace App\Providers;

use App\Services\CustomClass;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        View::share('customName', (new CustomClass()));
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

tako da je možeš koristiti poput

@extends('layouts.app')

@section('content')
    <div class="container">
        <!-- Application Dashboard -->
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Adding</div>

                    <div class="panel-body">
                        <!--Object passed from AppServiceProvider-->
                        {{ $customName->add() }}
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Na ovaj će način (View::share('key', 'value')) value biti dostupan u sve i jednom view-u.
Ako želiš da ograničiš to na samo neke template-e, možeš da kreiraš custom ViewComposer i odrediš gdje da je dostupan.

Sharing.
View Composers.

Ako ti treba više od toga, onda bi trebalo da prodješ kroz Service Container i Service Providers

Primjer bi bio:

<?php

namespace App\Providers;

use App\Services\CustomClass;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
//        $this->app->singleton('customName', function () {
//            return new CustomClass();
//        });

        $this->app->bind('customName', CustomClass::class);
    }
}

K’o što vidiš, možeš vezati closure funkciju ili klasu za ime koje dodijeliš.
Onda bilo gdje na nivou aplikacije možeš da pozoveš app helper

app()['customName']
// ili
app('customName')

Vidi u dokumentaciji razliku izmedju singleton i bind metoda. Ovde isto imaš pojašnjenje.

Opet naglašavam da recimo iz sigurnosnih razloga dobre prakse (nije tol’ko nesigurno kol’ko treba steći naviku) u blade view ne treba prosljedjivati ništa osim potrebnih varijabli i to iz controller-a (pogotovo app helper imho), tako da bi primjer s početka mog’o izgledati

// controller method
$customClass = app('customName');
return view('home', compact('customName'));

Isto, resolving klasa na ovaj način nije uobičajen za klase koje nemaju dependencies.

Ja bi’ se drž’o prvog primjera iz svog posta.

Vidi i za kreiranje sopstvenog Service Provider-a i Facade/alias-a.


#6

Ma znam šta trebam slati u view, i sve radim tako da je sva logika u controllerima i da se prosljeđuju samo gotovi podaci. Nije to problem i to mi je jasno.
Problem mi je nastao kada sam htio u view prikazati vrijeme (date-time), ali nisam htio čisti php stviti u view, već sam htio napraviti klasu koja će vraćati datum i vrijeme u formatu kojeg odredim u toj klasi.
Primjerice, klasa se zove DisplayTime, a vraća ili u formatu “2019-01-11 18:00:00” ili “20190111180000”.
I ta klasa bi trebala biti globalno dostupna.
Tražeći došao sam to rješenja sa helperima, ali i tu ima dvije varijante.
Naime, zanima me postoji li baš standard za takav “problem” (govoreći o Laravelu) ili je to do preferencije programera/tima?


#7

Carbon klasa je korištena uglavnom u Laravel-u.
Ili plain PHP DateTime klasa (karbon je nadogradjuje i.e. Carbon extends DateTime).
Vjerujem da tu možeš pronaći (skoro) sve što se tiče kalkulacije vremena.


#8

Carbon i koristim (namjerno ne koristim native date funkciju php-a) :slight_smile:


#9

Uvijek možeš pristupiti Carbon klasi koristeći absolute namespacing (valjda si i napis’o da tako radiš sad)

{{ \Carbon\Carbon::now() }}
{{ \Carbon\Carbon::createFromTimestamp(1500000000)->toDateTimeString() }}

U svakom slučaju ne vidim poseban problem u korištenju kako radiš.
Ako možeš proslijediti kroz controller ili View::share() još bolje.


#10

Poigrah se kako si predložio, sad je još ljepše za korištenje :slight_smile:

App\Providers\GlobalTimeNow.php

<?php

namespace App\Providers;
use Carbon\Carbon;

class GlobalTimeNow{
    //
    public static function timeNow()
    {
        $timeNow=Carbon::now();
        return $timeNow;
    }

    public static function timeNowString()
    {
        $timeNow=Carbon::now();
        $return=(str_replace(" ","",$timeNow));
        $return=str_replace("-","",$return);
        $return=str_replace(":","",$return);
        return ($return);
    }
}

App\Providers\AppServiceProvider.php

<?php

namespace App\Providers;

use App\Providers\GobalTimeNow;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
        View::share('timeNow',(new GlobalTimeNow()));
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

}

I konačno korištenje u blade.php (baš mi ovako treba za razvoj, da mi se uvijek loada “svježi” css)

<link href="{{ asset('css/app.css?v=').$timeNow->timeNowString() }}" rel="stylesheet">

I jopet, HVALA @tpojka ! :slight_smile:

=====================================================

Eh da, i da napomenem, mogao sam napraviti daleko jednostavnije rješenje, međutim ovdje je poanta u vježbanju i razumijevanju Laravela (za one koji će reć da ne treba toliko komplicirati) :slight_smile:

=====================================================


#11

Par stvari.
Providers directory je namijenjen provajder fajlovima i svaki custom package koji budeš koristio ili pravio će imati NameItServiceProvider.php fajl u toj lokaciji. Ako pogledaš ostale provajdere svi imaju sličnu strukturu s razlogom.
Hoću da kažem da je bolje da svoju klasu držiš u Helpers k’o što si im’o pa je otud importuješ u AppServiceProvider.

Možda ti ovo riješi file versioning.
Nakon npm run watch bi trebalo raditi.


#12

Izvrsno! E tako ću i koristiti ubuduće. :slight_smile:
(Mislim na helpers/providers).
Ovo za versioning mi nije bitno, ovo mi je samo za developing fazu lokalno, a htio sam i vidjeti (naučiti) kako napraviti globalnu klasu.


#13

DateTime ima format metodu, umjesto str_replace

https://secure.php.net/manual/en/datetime.format.php


#14

Str_replace sam stavio sebi kao primjer (oznaku, ili kako već to nazvati) gdje se mogu igrati sa formatom, ovo je čisto meni za igranje. Inače bih upotrijebio date(“YmdHis”) za isti rezultat :slight_smile:


#15

Možeš

return Carbon::now()->format('YmdHis');
// ili 
return Carbon::create()->format('YmdHis');
// ili koristeći postojeći metod u klasi
return self::timeNow()->format('YmdHis');