Laravel Digest (November 2013)

Welcome to the third instalment of Laravel Digest, the series where I give a regular rundown of important changes, fixes and additions to Laravel’s master branch. Below are the changes up to the end of November 2013:

  • Relationships can now reference any key on the parent model using a new last parameter for each of the \Eloquent\Model methods (belongsTo: c7d2740, hasOne, hasMany, morph relationships: a634f45):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

class User extends Eloquent
{
    // belongsTo
    public function group()
    {
        return $this->belongsTo('Group', 'group', 'name');
    }

    // hasOne
    public function city()
    {
        return $this->hasOne('City', 'city', 'name');
    }

    // hasMany
    public function roles()
    {
        return $this->hasMany('Role', 'role', 'type');
    }

    // etc...
}
  • Builder#has method now maintains a query’s constraints (7196279)

  • Scoped queries now return the value from the scope method for better chaining (23adaa4)

    • In case you’re confused how this is new functionality (I was), previously, the magic method that called your custom scope method ignored what you returned and instead returned the Builder instance (which to be honest is the 95%+ use case anyway), but it now returns what you actually return from your scope method (so if you weren’t already, make sure you return the Builder instance!)
  • has queries got even better: new whereHas() and orWhereHas() that allow you to set specific constraints on the has relation (a634f45):

1
2
3
4
5
6
<?php
// get all users with posts pre-attached, but only users with
// at least one post that is more than a year old
User::with('posts')->whereHas('posts', function ($query) {
    $query->where('created_at', '<', Carbon::now()->subYears(1));
})->get();

I’m not sure right now if the whereHas affects the with (i.e. what actually gets returned pre-attached) or just the mechanism for filtering the original model by the relationship. My guess is the latter.

1
2
3
4
<!-- this -->
<div>Hello, {{ isset($username) ? $username : 'anonymous' }}.</div>
<!-- can now be done like this -->
<div>Hello, {{ $username or 'anonymous' }}.</div>
  • Cache entries can now be tagged. New functionality includes using Cache::tags to get a list of the tags used in the cache, and items can be flushed by tag (70108fe)

  • Laravel now sends X-frame-Options: SAMEORIGIN by default in the HTTP headers (15513cc)

  • belongsToMany relations now have a firstOrFail method (b162579)

  • Schema builder now has nullableTimestamps method that creates timestamps as normal but makes them nullable fields (a44fbab)

  • Joins can now have complex conditions if joinWhere or leftJoinWhere are used (a644ea0)

  • New required_without_all validation rule that works like required_without but checks that at least one of the specified fields is present rather than all fields (93c1045)

  • Original method that was attempted is passed to Controller#missingMethod now as the first parameter, with the additional parameters as an array as the second parameter (de871fe)

  • New Blade @append method (f0ae98b):

1
2
3
4
5
6
7
8
9
@section('content')
Some content for the content section
@append

...

@section('content')
Some more content for the content section
@append
  • New query builder rememberForever method (c681584)

  • New Eloquent model booting and booted methods (2dfb383)

  • View#withErrors now accepts an optional parameter that is a object that implements MessageProviderInterface (i.e. can be asked for a MessageBag)(3c5ca1a)

    • Use case here is for validators: View::make('some.view')->withErrors($validator);
  • A big change to password resetting/reminder stuff (f86d5ea)

  • Collection#splice now returns the extracted elements (if any) as a new Collection (32f107d)

  • Commas are now allowed in route wildcards (fdd378b)

  • Using host detection in bootstrap/start.php you should now just use the machine name rather than HTTP_HOST, this change is good because it bridges artisan and HTTP environment detection (laravel/laravel@a297fe8)

  • On a BelongsToMany relationship, you can now use wherePivot and orWherePivot to add constraints to the pivot table conveniently (5d48577)

  • Database layer can now have separate connections for reading and writing. I’m not entirely sure why this was added but maybe it’s useful to some (a3b0e87). As I understand it from the tests, you can just override any settings from the connection’s config array in the read or write keys:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

// app/config/database.php

return array(
    // ...

  'connections' => array(
        'mysql' => array(

            // default settings for read/write for this connection
            'driver'    => 'mysql',
            'host'      => '127.0.0.1',
            'database'  => 'laravel',
            'username'  => 'user',
            'password'  => 'pass',
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',

            // read-specific overrides
            'read' => array(
                'database' => 'laravel_read',
            ),
        ),
    ),
);
  • Lots of work on queues (in particular releasing jobs back to the queue for Iron.io queues, as well as logging failed queue jobs into a database connection) (multiple commits).

  • Collection class now has merge, diff and intersect methods to work with multiple collections (2f54024).

  • Paginator has a new method fragment which can be used to set the fragment to be appended to the URIs generated by the paginator (if passed a string) or get the current fragment (if the argument is omitted or null) (ea9a924):

1
2
3
4
<?php

$paginator->fragment('my-frag'); // sets the fragment to be 'my-frag'
echo $paginator->fragment(); // echoes the current fragment (i.e. 'my-frag')
  • Eloquent’s Collection#find can now accept a model as the ‘key’ and it’ll search in the collection for a model which has the same key as the passed model (362db89):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

// get all enabled pages
$pages = Page::whereEnabled(1)->get();

// get page with id of 1
$page  = Page::find(1);

// before:
if ($pages->contains($page->getKey())) {
    // do something
}

// now:
if ($pages->contains($page)) {
    // do something
}
  • Locking added to QueryBuilder (a331432):
    • $builder->lockForUpdate() – same as calling $builder->lock(true)
    • $builder->sharedLock() – same as calling $builder->lock(false)
    • $builder->lock([string|boolean])
      • If true, signifies the row should be locked for update
      • If false, signifies that the row should be locked for share
      • If a string, the string will be used as the text to go in the ‘lock’ part of the query
    • It would appear that even if you don’t do anything to do with locks, you will now always get the shared lock due to the way the lock variable is handled, but maybe I’m wrong there, or maybe that’s implicit in RDBMSes anyway so it doesn’t harm anyone