Upgrade to Pro — share decks privately, control downloads, hide ads and more …

50 Laravel Tricks in 50 Minutes

willroth
November 19, 2015

50 Laravel Tricks in 50 Minutes

Laravel 5.1 raised the bar for framework documentation, but there's much, much more lurking beneath the surface. In this 50-minute session, we'll explore 50 (yes, 50!) high-leverage implementation tips & tricks that you just won't find in the docs: the IoC Container, Blade, Eloquent, Middleware, Routing, Commands, Queues, Events, Caching — we'll cover them all! Join us as we drink from the fire hose & learn to take advantage of everything that Laravel has to offer to build better software faster!
---
Presented on 11/19/2015 at PHP World

willroth

November 19, 2015
Tweet

More Decks by willroth

Other Decks in Technology

Transcript

  1. View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. class Post extends Eloquent
    {
    public static $autoValidate = true;
    protected static $rules = array();
    protected static function boot()
    {
    parent::boot();
    // You can also replace this with static::creating or static::updating
    static::saving(function($model)
    {
    if ($model::$autoValidate)
    {
    return $model->validate();
    }
    });
    }
    public function validate()
    {
    }
    }

    View Slide

  7. class Post extends Eloquent
    {
    protected static function boot()
    {
    parent::boot();
    static::updating(function($model)
    {
    return false;
    });
    }
    }

    View Slide

  8. class myModel extents Model
    {
    public function category()
    {
    return $this->belongsTo('myCategoryModel', 'categories_id')
    ->where('users_id', Auth::user()->id);
    }
    }

    View Slide

  9. $products = Product::where('category', '=', 3)->get();
    $products = Product::where('category', 3)->get();
    $products = Product::whereCategory(3)->get();

    View Slide

  10. SELECT *, COUNT(*) FROM products GROUP BY category_id HAVING count(*) > 1;
    DB::table('products')
    ->select('*', DB::raw('COUNT(*) as products_count'))
    ->groupBy('category_id')
    ->having('products_count', '>' , 1)
    ->get();
    Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();

    View Slide

  11. $q->whereDate('created_at', date('Y-m-d'));
    $q->whereDay('created_at', date('d'));
    $q->whereMonth('created_at', date('m'));
    $q->whereYear('created_at', date('Y'));

    View Slide

  12. // src/Illuminate/Database/Eloquent/Model.php
    public function save(array $options = array())
    // src/Illuminate/Database/Eloquent/Model.php
    protected function performUpdate(Builder $query, array $options = [])
    {
    if ($this->timestamps && array_get($options, 'timestamps', true))
    {
    $this->updateTimestamps();
    }
    $product = Product::find($id);
    $product->updated_at = '2015-01-01 10:00:00';
    $product->save(['timestamps' => false]);

    View Slide

  13. // app/Article.php
    class Article extends Model
    {
    use \Dimsav\Translatable\Translatable;
    public $translatedAttributes = ['name', 'text'];
    }
    // database/migrations/create_articles_table.php
    public function up()
    {
    Schema::create('articles', function (Blueprint $table) {
    $table->increments('id');
    $table->boolean('online');
    $table->timestamps();
    });
    }
    // resources/views/article.blade.php
    {{ $article->name }}
    {{ $article->text }}
    //database/migrations/create_articles_table.php
    public function up()
    {
    $table->increments('id');
    $table->integer('article_id')->unsigned();
    $table->string('locale')->index();
    $table->string('name');
    $table->text('text');
    $table->unique(['article_id','locale']);
    $table->foreign('article_id')->references('id')
    ->on('articles')
    ->onDelete('cascade');
    }
    // app/ArticleTranslation.php
    class ArticleTranslation extends Model
    {
    public $timestamps = false;
    }
    // app/http/routes.php
    Route::get('{locale}', function($locale) {
    app()->setLocale($locale);
    $article = Article::first();
    return view('article')->with(compact('article'));
    });
    http://50LaravelTricksIn50Minutes.com/fr -- French Translation

    View Slide

  14. $questions = Question::orderByRaw('RAND()')->take(10)->get();

    View Slide

  15. use Ramsey\Uuid\Uuid;
    trait UUIDModel
    {
    public $incrementing = false;
    protected static function boot()
    {
    parent::boot();
    static::creating(function ($model) {
    $key = $model->getKeyName();
    if (empty($model->{$key})) {
    $model->{$key} = (string) $model->generateNewId();
    }
    });
    }
    public function generateNewUuid()
    {
    return Uuid::uuid4();
    }
    }

    View Slide

  16. View Slide

  17. class Category extends Model
    {
    public function products()
    {
    return $this->hasMany('App\Product')->orderBy(‘name');
    }
    }

    View Slide

  18. $customer = Customer::find($customer_id);
    $loyalty_points = $customer->loyalty_points + 50;
    $customer->update(['loyalty_points' => $loyalty_points]);
    // adds one loyalty point
    Customer::find($customer_id)->increment('loyalty_points', 50);
    // subtracts one loyalty point
    Customer::find($customer_id)->decrement('loyalty_points', 50);

    View Slide

  19. $employees = Employee::where('branch_id', 9)->lists('name', 'id');
    return view('customers.create', compact('employees'));
    {!! Form::select('employee_id', $employees, '') !!}
    public function getFullNameAttribute() {
    return $this->name . ' ' . $this->surname;
    }
    [2015-07-19 21:47:19] local.ERROR: exception 'PDOException'
    with message 'SQLSTATE[42S22]: Column not found:
    1054 Unknown column 'full_name' in 'field list'' in
    ...vendor\laravel\framework\src\Illuminate\Database\Connection.php:288
    $employees = Employee::where('branch_id', 9)->get()->lists('full_name', 'id');

    View Slide

  20. function getFullNameAttribute() {
    return $this->first_name . ' ' . $this->last_name;
    }
    {
    "id":1,
    "first_name":"Povilas",
    "last_name":"Korop",
    "email":"[email protected]",
    "created_at":"2015-06-19 08:16:58",
    "updated_at":"2015-06-19 19:48:09"
    }
    class User extends Model
    {
    protected $appends = ['full_name'];
    {
    "id":1,
    "first_name":"Povilas",
    "last_name":"Korop",
    "email":"[email protected]",
    "created_at":"2015-06-19 08:16:58",
    "updated_at":"2015-06-19 19:48:09",
    "full_name":"Povilas Korop"
    }

    View Slide

  21. class Category extends Model
    {
    public function products() {
    return $this->hasMany('App\Product');
    }
    }
    public function getIndex() {
    $categories = Category::with('products')->has('products')->get();
    return view('categories.index', compact('categories'));
    }

    View Slide

  22. public function store()
    {
    $post = new Post;
    $post->fill(Input::all());
    $post->user_id = Auth::user()->user_id;
    $post->user;
    return $post->save()
    }

    View Slide

  23. View Slide

  24. // eloquent
    Post::whereSlug('slug')->get();
    // instead of
    View::make('posts.index')->with(‘posts’, $posts);
    // do this
    View::make('posts.index')->withPosts($posts);

    View Slide

  25. //hide all but the first item
    @foreach ($menu as $item)

    {{ $item->title }}

    @endforeach
    //apply css to last item only
    @foreach ($menu as $item)

    {{ $item->title }}

    @endforeach

    View Slide

  26. View Slide

  27. $devs = [
    ['name' => 'Anouar Abdessalam','email' => '[email protected]'],
    ['name' => 'Bilal Ararou','email' => '[email protected]']
    ];
    $devs = new Illuminate\Support\Collection($devs);
    $devs->first();
    $devs->last();
    $devs->push(['name' => 'xroot','email' => '[email protected]']);

    View Slide

  28. $customers = Customer::all();
    $us_customers = $customers->filter(function ($customer) {
    return $customer->country == 'United States';
    });
    $non_uk_customers = $customers->reject(function ($customer) {
    return $customer->country == 'United Kingdom';
    });

    View Slide

  29. View Slide

  30. // returns a single row as a collection
    $collection = App\Person::find([1]);
    // can return multiple rows as a collection
    $collection = App\Person::find([1, 2, 3]);

    View Slide

  31. $collection = App\Person::all();
    $programmers = $collection->where('type', 'programmer');
    $critic = $collection->where('type', 'critic');
    $engineer = $collection->where('type', 'engineer');

    View Slide

  32. $collection = App\Person::all();
    $names = $collection->implode('first_name', ',');

    View Slide

  33. // returns a collection of first names
    $collection = App\Person::all()->where('type', 'engineer')->lists('first_name');
    // returns all the meta records for user 1
    $collection = App\WP_Meta::whereUserId(1)->get();
    // returns the first & last name meta values
    $first_name = $collection->where('meta_key', 'first_name')->lists('value')[0];
    $last_name = $collection->where('meta_key', 'last_name')->lists('value')[0];

    View Slide

  34. class Link extends Model
    {
    public function users()
    {
    return $this->belongsToMany('Phpleaks\User')->withTimestamps();
    }
    }
    @if ($link->users->count() > 0)
    Recently Favorited By
    @foreach ($link->users()->orderBy('link_user.created_at', 'desc')->take(15)->get() as $user)

    {{ $user->name }}

    @endforeach
    @endif

    View Slide

  35. $collection = collect([
    ['name' => 'Desk'],
    ['name' => 'Chair'],
    ['name' => 'Bookcase']
    ]);
    $sorted = $collection->sortBy(function ($product, $key)
    {
    return array_search($product['name'], [1=>'Bookcase', 2=>'Desk', 3=>'Chair']);
    });

    View Slide

  36. $library = $books->keyBy('title');
    [
    'Lean Startup' => ['title' => 'Lean Startup', 'price' => 10],
    'The One Thing' => ['title' => 'The One Thing', 'price' => 15],
    'Laravel: Code Bright' => ['title' => 'Laravel: Code Bright', 'price' => 20],
    'The 4-Hour Work Week' => ['title' => 'The 4-Hour Work Week', 'price' => 5],
    ]

    View Slide

  37. $collection = App\Person::all();
    $grouped = $collection->groupBy('type');

    View Slide

  38. // the point is to actually combine results from different models
    $programmers = \App\Person::where('type', 'programmer')->get();
    $critic = \App\Person::where('type', 'critic')->get();
    $engineer = \App\Person::where('type', 'engineer')->get();
    $collection = new Collection;
    $all = $collection->merge($programmers)->merge($critic)->merge($engineer);

    View Slide

  39. $collection = collect([1=>11, 5=>13, 12=>14, 21=>15])->getCachingIterator();
    foreach ($collection as $key => $value) {
    dump($collection->current() . ':' . $collection->getInnerIterator()->current());
    }

    View Slide

  40. View Slide

  41. View Slide

  42. Route::group(['prefix' => 'account', 'as' => 'account.'], function () {
    Route::get('login', ['as' => 'login', 'uses' => '[email protected]']);
    Route::get('register', ['as' => 'register', 'uses' => '[email protected]']);
    Route::group(['middleware' => 'auth'], function () {
    Route::get('edit', ['as' => 'edit', 'uses' => '[email protected]']);
    });
    });
    Login
    Register
    Edit Account

    View Slide

  43. // app/Http/routes.php
    Route::group(['middleware' => 'auth'], function () {
    Route::get('{view}', function ($view) {
    try {
    return view($view);
    } catch (\Exception $e) {
    abort(404);
    }
    })->where('view', '.*');
    });

    View Slide

  44. // api controller
    public function show(Car $car)
    {
    if (Input::has('fields')) {
    // do something
    }
    }
    // internal request to api - fields are lost
    $request = Request::create('/api/cars/' . $id . '?fields=id,color', 'GET');
    $response = json_decode(Route::dispatch($request)->getContent());
    // internal request to api - with fields
    $originalInput = Request::input();
    $request = Request::create('/api/cars/' . $id . '?fields=id,color', 'GET');
    Request::replace($request->input());
    $response = json_decode(Route::dispatch($request)->getContent());
    Request::replace($originalInput);

    View Slide

  45. View Slide

  46. // phpunit.xml









    // .env.test – add to .gitignore
    TWILIO_ACCOUNT_SID=fillmein
    TWILIO_ACCOUNT_TOKEN=fillmein
    // tests/TestCase.php
    class TestCase extends Illuminate\Foundation\Testing\TestCase
    {
    /**
    * The base URL to use while testing the application.
    *
    * @var string
    */
    protected $baseUrl = 'http://localhost';
    /**
    * Creates the application.
    *
    * @return \Illuminate\Foundation\Application
    */
    public function createApplication()
    {
    $app = require __DIR__.'/../bootstrap/app.php';
    if (file_exists(dirname(__DIR__) . '/.env.test')) {
    Dotenv::load(dirname(__DIR__), '.env.test');
    }
    $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
    return $app;
    }
    }
    // access directly from your tests using helper function
    env('TWILIO_ACCOUNT_TOKEN');

    View Slide

  47. // gulpfile.js
    var elixir = require('laravel-elixir');
    mix.phpUnit();
    $ gulp tdd

    View Slide

  48. View Slide

  49. // app/Http/Middleware/EncryptCookies.php
    protected $except = [
    'shared_cookie'
    ];
    Cookie::queue('shared_cookie', 'my_shared_value', 10080, null, '.example.com');

    View Slide

  50. $ artisan make:model Books -m

    View Slide

  51. $ composer require genealabs/laravel-sparkinstaller --dev
    //
    Laravel\Spark\Providers\SparkServiceProvider::class,
    GeneaLabs\LaravelSparkInstaller\Providers\LaravelSparkInstallerServiceProvider::class,
    // do not run php artisan spark:install
    $ php artisan spark:upgrade
    // backup /resources/views/home.blade.php or it will be overwritten
    $ php artisan vendor:publish --tag=spark-full

    View Slide

  52. use Exception;
    use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
    use Symfony\Component\Debug\ExceptionHandler as SymfonyDisplayer;
    class Handler extends ExceptionHandler
    {
    protected function convertExceptionToResponse(Exception $e)
    {
    $debug = config('app.debug', false);
    if ($debug) {
    return (new SymfonyDisplayer($debug))->createResponse($e);
    }
    return response()->view('errors.default', ['exception' => $e], 500);
    }
    }

    View Slide

  53. // app/Providers/AppServiceProvider.php
    public function register()
    {
    $this->app->bind(
    'Illuminate\Contracts\Auth\Registrar',
    'App\Services\Registrar'
    );
    if ($this->app->environment('production')) {
    $this->app->register('App\Providers\ProductionErrorHandlerServiceProvider');
    } else {
    $this->app->register('App\Providers\VerboseErrorHandlerServiceProvider');
    }
    }

    View Slide

  54. View Slide

  55. public function up()
    {
    Schema::table('users', function($table)
    {
    $table->string('name', 50)->change();
    });
    }
    $ composer require doctrine/dbal

    View Slide

  56. if (view()->exists('emails.' . $template))
    {
    // ... sending an email to the customer
    }

    View Slide

  57. // bootstrap/app.php
    // replace this:
    $app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
    );
    // with this:
    $app = new Fantabulous\Application(
    realpath(__DIR__.'/../')
    );
    class Application extends \Illuminate\Foundation\Application
    {
    /**
    * Get the path to the storage directory.
    *
    * @return string
    */
    public function storagePath()
    {
    return $this->basePath.'/FantabulousStorage';
    }
    }

    View Slide

  58. class fakeApiCaller
    {
    public function getResultsForPath($path)
    {
    return [
    'status' => 200,
    'body' => json_encode([
    'title' => "Results for path [$path]"
    ]),
    'headers' => [
    "Content-Type" => "application/json"
    ]
    ];
    }
    }
    $app->get('{path?}', function($path)
    {
    $result = Cache::remember($path, 60, function() use ($path) {
    return (new fakeApiCaller)->getResultsForPath($path);
    });
    return response($result['body'], $result['status'], array_only(
    $result['headers'], ['Content-Type', 'X-Pagination']
    ));
    })->where('path', '.*');

    View Slide

  59. $ composer create-project laravel/laravel your-project-name-here dev-develop
    // composer.json
    {
    "require": {
    "php": ">=5.5.9",
    "laravel/framework": "5.2.*"
    },
    "minimum-stability": "dev"
    }
    $ composer update

    View Slide

  60. \DB::listen(function($query, $bindings, $time) {
    var_dump($query);
    var_dump($bindings);
    var_dump($time);
    });
    Event::listen('illuminate.query', function($query) {
    var_dump($query);
    });

    View Slide

  61. // app/Policies/AdminPolicy.php
    class AdminPolicy
    {
    public function managePages($user)
    {
    return $user->hasRole(['Administrator', 'Content Editor']);
    }
    }
    // app/Providers/AuthServiceProvider.php
    public function boot(\Illuminate\Contracts\Auth\Access\GateContract $gate)
    {
    foreach (get_class_methods(new \App\Policies\AdminPolicy) as $method) {
    $gate->define($method, "App\Policies\[email protected]{$method}");
    }
    $this->registerPolicies($gate);
    }
    $this->authorize('managePages'); // in Controllers
    @can('managePages') // in Blade Templates
    $user->can('managePages'); // via Eloquent

    View Slide

  62. $disk= Storage::disk('s3');
    $disk->put($targetFile, file_get_contents($sourceFile));
    $disk = Storage::disk('s3');
    $disk->put($targetFile, fopen($sourceFile, 'r+'));
    $disk = Storage::disk('s3');
    $stream = $disk->getDriver()->readStream($sourceFileOnS3);
    file_put_contents($targetFile, stream_get_contents($stream), FILE_APPEND);
    $stream = Storage::disk('s3')->getDriver()->readStream($sourceFile);
    Storage::disk('sftp')->put($targetFile, $stream)

    View Slide

  63. $schedule->call(function () {
    Storage::delete($logfile);
    })->weekly();

    View Slide

  64. $result = (new Illuminate\Pipeline\Pipeline($container)
    ->send($something)
    ->through('ClassOne', 'ClassTwo', 'ClassThree')
    ->then(function ($something) {
    return 'foo';
    });

    View Slide

  65. View Slide

  66. View Slide

  67. View Slide

  68. class PurchasePodcastCommand extends Command
    {
    public $user;
    public $podcast;
    public function __construct(User $user, Podcast $podcast)
    {
    $this->user = $user;
    $this->podcast = $podcast;
    }
    }
    class PurchasePodcastCommandHandler
    {
    public function handle(BillingGateway $billing)
    {
    // Handle the logic to purchase the podcast...
    event(new PodcastWasPurchased($this->user, $this->podcast));
    }
    }
    class PodcastController extends Controller
    {
    public function purchasePodcastCommand($podcastId)
    {
    $this->dispatch(
    new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
    );
    }
    }

    View Slide

  69. class PurchasePodcast extends Command implements SelfHandling
    {
    protected $user;
    protected $podcast;
    public function __construct(User $user, Podcast $podcast)
    {
    $this->user = $user;
    $this->podcast = $podcast;
    }
    public function handle(BillingGateway $billing)
    {
    // Handle the logic to purchase the podcast...
    event(new PodcastWasPurchased($this->user, $this->podcast));
    }
    }
    class PodcastController extends Controller
    {
    public function purchasePodcast($podcastId)
    {
    $this->dispatch(
    new PurchasePodcast(Auth::user(), Podcast::findOrFail($podcastId))
    );
    }
    }

    View Slide

  70. class PodcastController extends Controller
    {
    public function purchasePodcast(PurchasePodcastRequest $request)
    {
    $this->dispatchFrom('Fantabulous\Commands\PurchasePodcastCommand', $request);
    }
    }
    class PodcastController extends Controller
    {
    public function purchasePodcast(PurchasePodcastRequest $request)
    {
    $this->dispatchFrom('Fantabulous\Commands\PurchasePodcastCommand', $request, [
    'firstName' => 'Taylor',
    ]);
    }
    }

    View Slide

  71. class PurchasePodcast extends Command
    implements ShouldBeQueued, SerializesModels
    {
    public $user;
    public $podcast;
    public function __construct(User $user, Podcast $podcast)
    {
    $this->user = $user;
    $this->podcast = $podcast;
    }
    }

    View Slide

  72. // App\Providers\BusServiceProvider::boot
    $dispatcher->pipeThrough(['UseDatabaseTransactions', 'LogCommand']);
    class UseDatabaseTransactions
    {
    public function handle($command, $next)
    {
    return DB::transaction(function() use ($command, $next)
    {
    return $next($command);
    });
    }
    }
    // App\Providers\BusServiceProvider::boot
    $dispatcher->pipeThrough([function($command, $next)
    {
    return DB::transaction(function() use ($command, $next)
    {
    return $next($command);
    });
    }]);

    View Slide

  73. View Slide

  74. View Slide

  75. View Slide

  76. // app/http/routes.php
    Route::get('/api/posts/{post}', function(Post $post) {
    return $post;
    });
    // behind the scenes
    Post::findOrFail($post);

    View Slide

  77. $schedule->command('emails:send')
    ->hourly()
    ->appendOutputTo($filePath);

    View Slide

  78. // returns titles for all posts
    $titles = $posts->pluck(‘posts.*.title’);

    View Slide









  79. $v = Validator::make($request->all(), [
    'person.*.id' => 'exists:users.id',
    'person.*.name' => 'required:string',
    ]);

    View Slide

  80. // included in database session driver
    user_id
    ip_address

    View Slide

  81. View Slide

  82. View Slide

  83. View Slide