Skip to content

Instantly share code, notes, and snippets.

@danb-humaan
Created December 3, 2015 10:07
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save danb-humaan/b385ef92ed2336fd5d12 to your computer and use it in GitHub Desktop.
Save danb-humaan/b385ef92ed2336fd5d12 to your computer and use it in GitHub Desktop.
Trait for implementing UUIDs in Laravel models
<?php
namespace App\Traits;
use Rhumsaa\Uuid\Uuid;
use Illuminate\Database\Eloquent\ModelNotFoundException;
/**
* Trait UuidModel
* @package App\Traits
*/
trait UuidModel
{
/**
* Binds creating/saving events to create UUIDs (and also prevent them from being overwritten).
*
* @return void
*/
public static function bootUuidModel()
{
static::creating(function ($model) {
// Don't let people provide their own UUIDs, we will generate a proper one.
$model->uuid = Uuid::uuid4()->toString();
});
static::saving(function ($model) {
// What's that, trying to change the UUID huh? Nope, not gonna happen.
$original_uuid = $model->getOriginal('uuid');
if ($original_uuid !== $model->uuid) {
$model->uuid = $original_uuid;
}
});
}
/**
* Scope a query to only include models matching the supplied UUID.
* Returns the model by default, or supply a second flag `false` to get the Query Builder instance.
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*
* @param \Illuminate\Database\Schema\Builder $query The Query Builder instance.
* @param string $uuid The UUID of the model.
* @param bool|true $first Returns the model by default, or set to `false` to chain for query builder.
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder
*/
public function scopeUuid($query, $uuid, $first = true)
{
if (!is_string($uuid) || (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $uuid) !== 1)) {
throw (new ModelNotFoundException)->setModel(get_class($this));
}
$search = $query->where('uuid', $uuid);
return $first ? $search->firstOrFail() : $search;
}
/**
* Scope a query to only include models matching the supplied ID or UUID.
* Returns the model by default, or supply a second flag `false` to get the Query Builder instance.
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*
* @param \Illuminate\Database\Schema\Builder $query The Query Builder instance.
* @param string $uuid The UUID of the model.
* @param bool|true $first Returns the model by default, or set to `false` to chain for query builder.
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder
*/
public function scopeIdOrUuId($query, $id_or_uuid, $first = true)
{
if (!is_string($id_or_uuid) && !is_numeric($id_or_uuid)) {
throw (new ModelNotFoundException)->setModel(get_class($this));
}
if (preg_match('/^([0-9]+|[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/', $id_or_uuid) !== 1) {
throw (new ModelNotFoundException)->setModel(get_class($this));
}
$search = $query->where(function ($query) use ($id_or_uuid) {
$query->where('id', $id_or_uuid)
->orWhere('uuid', $id_or_uuid);
});
return $first ? $search->firstOrFail() : $search;
}
}
@ratatatKE
Copy link

`<?php
namespace App\Traits;
use Ramsey\Uuid\Uuid;
use Illuminate\Database\Eloquent\ModelNotFoundException;
/**

  • Trait UuidModel

  • @Package App\Traits
    /
    trait UuidModel
    {
    /
    *

    • Binds creating/saving events to create UUIDs (and also prevent them from being overwritten).
      *

    • @return void
      */
      public static function bootUuidModel()
      {
      static::creating(function ($model) {
      // Don't let people provide their own UUIDs, we will generate a proper one.
      $model->uuid = Uuid::uuid4()->toString();
      });

      static::saving(function ($model) {
      // What's that, trying to change the UUID huh? Nope, not gonna happen.
      $original_uuid = $model->getOriginal('uuid');

      if ($original_uuid !== $model->uuid) {
          $model->uuid = $original_uuid;
      }
      

      });
      }

    /**

    • Scope a query to only include models matching the supplied UUID.

    • Returns the model by default, or supply a second flag false to get the Query Builder instance.
      *

    • @throws \Illuminate\Database\Eloquent\ModelNotFoundException
      *

    • @param \Illuminate\Database\Schema\Builder $query The Query Builder instance.

    • @param string $uuid The UUID of the model.

    • @param bool|true $first Returns the model by default, or set to false to chain for query builder.

    • @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder
      */
      public function scopeUuid($query, $uuid, $first = true)
      {
      if (!is_string($uuid) || (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $uuid) !== 1)) {
      throw (new ModelNotFoundException)->setModel(get_class($this));
      }

      $search = $query->where('uuid', $uuid);

      return $first ? $search->firstOrFail() : $search;
      }

    /**

    • Scope a query to only include models matching the supplied ID or UUID.

    • Returns the model by default, or supply a second flag false to get the Query Builder instance.
      *

    • @throws \Illuminate\Database\Eloquent\ModelNotFoundException
      *

    • @param \Illuminate\Database\Schema\Builder $query The Query Builder instance.

    • @param string $uuid The UUID of the model.

    • @param bool|true $first Returns the model by default, or set to false to chain for query builder.

    • @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder
      */
      public function scopeIdOrUuId($query, $id_or_uuid, $first = true)
      {
      if (!is_string($id_or_uuid) && !is_numeric($id_or_uuid)) {
      throw (new ModelNotFoundException)->setModel(get_class($this));
      }

      if (preg_match('/^([0-9]+|[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/', $id_or_uuid) !== 1) {
      throw (new ModelNotFoundException)->setModel(get_class($this));
      }

      $search = $query->where(function ($query) use ($id_or_uuid) {
      $query->where('id', $id_or_uuid)
      ->orWhere('uuid', $id_or_uuid);
      });

      return $first ? $search->firstOrFail() : $search;
      }
      }`

@jeffwray
Copy link

So I'm finding in cases where the UUID begins with a number it seems some type casting happens on the first query to id, you could get back both the record where the number is the first part of the uuid (example below id = 4 and the record with the uuid as in the case below

SELECT
 *
 FROM
`something`
WHERE
(`id` = '04c03600-a933-47b5-b5aa-e83f6f3399e5' OR `uuid` = '04c03600-a933-47b5-b5aa-e83f6f3399e5')

screenshot 2017-05-26 10 36 45

I would recommend changing the scopeIdOrUuId query modifier to be something like:

        if (substr_count($id_or_uuid,'-')>=1)
        {
            $search = $query->where(function ($query) use ($id_or_uuid) {
                $query->where('uuid', $id_or_uuid);
            });
        }
        else {
            $search = $query->where(function ($query) use ($id_or_uuid) {
                $query->where('id', $id_or_uuid);
            });
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment