Building a Duty/Patrol AR-15 [What’s the Difference?]

Not all AR-15s are created equal.

If you are new to the AR-15 or even if you’re experienced, you may ask yourself what is up with the price differences?

Why does one barrel cost more than the other? Why does one bolt carrier group cost $100 more than something else that looks identical?

Best AR-15 BCGs
Just some of the BCGs we tested for the Best AR-15 BCGs

Is there enough of a difference?

I am going to walk you through some topics in this article on what makes a weapon a duty-grade rifle between BCGs, barrels, handguards, receivers, and optics.

And some great companies to keep in mind for your next workhorse build.

This may hurt some feels along the way between the “elitists” and the “poors”. I will be doing my job if that is the case.

Duty AR Meme

What Is A Duty Level AR-15?

Let me first say a few things. The AR market is a huge spectrum of price points.

This is natural in a market of its size. Varying price points ultimately get more people involved in the market.

That is a good thing. Whether you are very well-off..

Cash Cannon

…or someone that lives paycheck to paycheck…

wallet

The AR-15 can be an affordable firearm.

Duty AR
Author’s budget-minded “fun-gun” built for his wife that runs great.

Due to varying price points, there is a lot of variation between the quality of parts and complete rifles.

I don’t judge anyone for having a rifle that they built as cheap as possible. I’ve done it myself.

Sometimes, building a rifle for cheap and treating it like a rented mule can be fun and shows failure points in the design when things aren’t quite perfect.

But there is a big difference between a hobby grade rifle that’s for fun on the range and a duty-grade workhorse to train with and stake your life on.

And the price difference may not be as large as you might expect if you wanted an AR for that task.

Duty AR Meme
While cheap stuff can be fun, buying higher quality parts is an insurance policy on reliability with a duty-level AR.

Simply put, a duty level AR-15 is something that a shooter can rely on for the LONG HAUL.

We are talking high round counts, high heat, and running dirty without breakage, failure, or stoppage.

And while your low-budget rifle may have run great for you over the last 5k rounds, this is typically an exception, not the rule.

Some Assembly Required

Before we start talking about parts, which are obviously important, equally important is proper assembly procedures for the AR-15.

I am a stickler for it, and if you knew me, you would see me on forums and Facebook preaching the gospel of staking and using a torque wrench EVERY time.

A torque wrench, breaker bar, and Aeroshell are your friends.
A torque wrench, breaker bar, and Aeroshell are your friends.

There are plenty of videos online that show how to properly assemble the AR-15.

Obviously, YouTube “Gunsmiths” should be vetted, but there is a vast assortment of videos and articles online that can show how to do the proper tasks.

Duty AR Meme

With any build, even the sub $400 home-rolled specials, proper assembly goes a long way in ensuring proper function. For lack of a better phrase, you can actually polish a turd a little bit.

For an AR you stake your life on, don’t be complacent.

Barrels should be dimpled for low-profile gas blocks, gas keys and castle nuts should be staked, proper torque applied to needed areas, and Loctite applied to the proper hardware.

Now let’s get into the meat and potatoes, shall we?

Bolt Carrier Groups – The Heart of the Gun

The bolt carrier is possibly the most important part of the duty-level workhorse. It’s what keeps the gun pumping and takes a beating through the life of the rifle.

Properly maintained and lubricated a quality BCG can last for the long haul. But what makes it quality?

There are a couple of different popular bolt steels, notably 9310 and 158 Carpenter steel.

Currently, MIL-SPEC steel based off Colt TDP is 158 Carpenter steel. The reason being is that it works, and it does it well.

It is a strong, highly durable steel and is wear resistant. 9310 is harder on average, but harder doesn’t always mean better. So many innuendos…

The problem that comes up with 9310 is quality control. Mike Mihalski of Sons of Liberty Gun Works, sums it up the best.

Mike says, “Most premature broken bolts I’ve seen are made of improperly heat treated 9310. 158C is a superior material in this application.”

This guy knows bolts. It’s kind of his job.

Photo of different gas key hardware and varying degrees of staking.
Photo of different gas key hardware and varying degrees of staking.

Furthermore, quality comes into play with the gas key and hardware. Suitable hardware used at the gas key is grade 8, torqued to 55 in/lbs. and properly staked.

Cheaper models tend to use YFS hardware. These are not rated for the same torque value which can lead to cracking or shearing off the bolt heads due to stress from firing.

Gas keys should also be sealed with Permatex. It is a gasket material for high heat applications that seal the gas key which increases functional reliability in a pressurized system.

Other things to keep in mind are shot peening of the bolt and an enhanced extractor spring for increased reliability.

INDIVIDUAL high-pressure testing (HPT), and magnetic particle inspection (MPI) should be the standard for a duty-level bolt. Typically, cheaper options are only batch tested.

To quote the great Mike Mihalski again: “Remember: The goal of making duty grade weapons is to reduce the potential of mechanical failure to as close to zero as possible. That begins with materials and quality control.”

A properly staked gas key, and individually MPI and HPT bolt.
A properly staked gas key, and individually MPI and HPT bolt.

Honestly, coatings and finishes should not be a major factor in your purchase. Ferric nitro-carburization (nitride), or a typical phosphate/parkerized finish are more than adequate with proper lubrication.

Some people may not like hearing it, but Nickel Boron (NiB) is largely snake oil. It offers no real-world functional advantage and can actually weaken the substrate metal through hydrogen embrittlement.

The good news is that there are options on the market that can fill the role of a heavy-use BCG.

One of my personal favorites is Brownell’s Mil-Spec BCG. It checks off most of the list for a quality BCG.


140
at Brownells

Prices accurate at time of writing

Another option is the Son’s of Liberty Gun Works BCG. It’s practically the gold standard.

For other BCGs to look at, feel free to check out our review of the 11 Best BCGs!

Barrels – The Legs of the Gun

Barrel material can be dictated by the mission or purpose of the AR. I prefer a quality 4150 CMV steel for my barrels, but if you are looking for more precision then 416R stainless steel is also a viable option.

Personally, I do not pine for sub-half MOA groups, so 4150 CMV works for me. 4150 CMV, or Mil-B-11595E, is chosen for its wear resistance and how well it deals with heat compared to stainless and 4140 CM steels.

For a coating, the tried and true Parkerizing and chrome-lining is a great choice.

It deals with heat very well and is highly wear resistant. My favorite chrome-lined barrel is an FN 16″ CHF Mid-Length.


300
at Brownells

Prices accurate at time of writing

It is a highly accurate barrel for being chrome-lined and its pedigree speaks for itself in the high-round count department, but it comes with a high price.

Chrome-lining though is dated technology. A quality nitriding process that case hardens the outside layer of the steel is the future.

Nitriding is a full exterior surface treatment that does not change the dimensions of the barrel like chrome-lining but it gives all the benefits with high round counts, reduced friction, and tends to be a less expensive process.

There are some seriously well-made barrels on the market today without the heavy price tags. One of my personal favorites is the line of Rosco Manufacturing Bloodline barrels.

I have owned two and currently, their 11.5” sits on my AR pistol build. Quality and accuracy are excellent but equally important, the gas port is sized appropriately.

Author’s 11.5” pistol AR with a Rosco Manufacturing barrel.
Author’s 11.5” pistol AR with a Rosco Manufacturing barrel.

Length of barrel is completely based off the shooter’s use and expectations. I prefer barrels over 10.5” for the added velocity and gas length when chambered in 5.56 NATO.

The location of the gas port can aid in reliability and smooth out the recoil impulse of an AR.

Listed below is a chart outlining good rule-of-thumb measurements, but proper gas port sizing should also be considered.

Barrel Length Gas Length
10.3/10.5” Carbine
11.5-13.7” Carbine
14.5” Carbine/Mid-Length
16” Mid-Length
18” Mid-Length/Rifle
20”+ Rifle

It keeps the weapon flatter and more on target. This, in conjunction with an effective muzzle device and a properly weighted buffer can yield an AR that can be highly accurate under faster firing.A properly sized gas port and a company that takes it seriously are important for the complete system.

Speed isn’t everything, but accurate speed IS everything.

Receivers and Handguards – The Bones of the Gun

I won’t go into too much detail about receivers because everyone has their favorites.

The more you spend on receivers, typically the better quality with regards to fit and finish – but this doesn’t mean that a $400 lower is better than a $40 lower in terms of reliability.

The big thing to keep in mind is to go with a company with a track record of being in-spec.

Author’s favorite lower receiver from SOLGW, the “Soul Snatcher”.
Author’s favorite lower receiver from SOLGW, the “Soul Snatcher”.

Material wise, forged 7075 T6 aluminum that is either anodized or Cerakoted is still one of the best choices to go with when it comes to receivers – they have worked for decades now.

My favorite inexpensive receiver set is the newer Aero Precision M4E1. They have always served me well, and let’s face it: they look cool, which is half the battle.

Or the standard Aero Stripped Lower is great for an even lower price point.

Editor’s Pick (Forged)

50
at Brownells

Prices accurate at time of writing

As far as handguards go, there are a lot of them on the market of varying quality.

For most people, a handguard really only serves as a rigid gripping area that keeps their hand from being scorched.

On a workhorse AR, it serves a couple more functions that are important.

A typical inexpensive handguard that are a good choice for a recreational rifle.
A typical inexpensive handguard that are a good choice for a recreational rifle.

Once you begin to bolt more accessories onto the rail, added weight can flex the handguard and be affected by heat from the barrel with rapid firing.

I am talking weapon-mounted lights (WMLs), laser sighting systems, and back-up iron sights. It is important to have a rigid enough handguard for your AR, but weight concerns and the price tag should be kept in mind.

For the crème de la crème, the Geissele MK8 handguard system is the gold standard in bomb-proof performance.


200
at Brownells

Prices accurate at time of writing

For people on a more modest budget, or who want something lighter, you can’t go wrong with a BCM MCMR handguard. I have used them on a few ARs and they fit very tight while also locking up like a bank vault.

Author’s ARs with MI Combat Rail, and DD MFR XS.
Author’s ARs with MI Combat Rail, and DD MFR XS.

The list can go further. I have personally used the Midwest Industries, and also the Daniel Defense MFR XS.

Best Value

179
at Brownells

Prices accurate at time of writing

These options are relatively lightweight but also have significant strength to not flex under hard use. These companies also have a track record for quality so you can buy with confidence.

Optics – The Eyes of the Gun

I included optics in this article because I believe optics are an absolute necessity for a duty weapon. While iron sights are always important, a good optic puts a firearm at a different level in both precision and in less than ideal lighting conditions.

Yet again, the purpose will dictate what style of optic a shooter needs. Red dots and holographic optics are great general-purpose optics and provide quick shooting from awkward positions. Due to being relatively free of parallax, the red dot or imposed reticle does not need to be completely centered in your field of view.

Some of the best red dots have survived the ultimate test of combat. Just about anything Aimpoint will serve well like the Aimpoint PRO, which we reviewed HERE! It is a solid, bomb-proof (literally) optic. 

The "Goldilocks" Red Dot

435
at Amazon

Prices accurate at time of writing

Another great option is the Trijicon MRO – another outstanding red dot on par with the Aimpoint series.

Author Trijicon MRO on an ADM QD co-witness mount.

Low-powered variable optics (LPVOs) are some of the most versatile optics on the market and are gaining massive popularity.

They offer nearly the same speed as a typical red dot or holographic optic but also offer more precision and better target ID at extended ranges.

Whether in first-focal plane (FFP), or second-focal plane (SFP), they offer the best of both worlds.

Quality LPVOs do not come cheap, so be prepared to drop some bennies.

When you start looking into the higher end optics such as the Kahle K16i, Schmidt & Bender EXOS, and Nightforce ATACR, it may seem like all hope is lost.

While these models are on a level of awesome that is hard to compare, there are more affordable options that can hold up to hard use.

Trijicon Accupower 1-4x in a BOBRO QD mount.
Trijicon Accupower 1-4x in a BOBRO QD mount.

One of my favorite affordable LPVOs is the Trijicon Accupower 1-4x.

While clarity, eye box, and field of view may not be as good as much more expensive models, the optic has proven to be tough, reliable, and is at a more comfortable price range.

Parting Shots

When all is said and done, a duty-level AR is only as good as the brain behind the firearm.

Even the most Gucci, expensive firearm is useless without appropriate training in how to run the gun.

Everyone has a different budget, so it will be up to everyone where to spend their money to make it count.

While the quality of a solid work-horse rifle isn’t needed by everyone, there is a difference in both quality and function over the long haul.

What are some of your favorite brands you have used? Any crazy stories of gear being put through Hell and coming out? Let us know in the comments below!

Want to get an out-of-the-box duty quality AR-15, then take a look at the Best AR-15s: Complete Buyer’s Guide!

A Few Of Our Personal AR-15 Uppers
A few of our favorite things…

The post Building a Duty/Patrol AR-15 [What’s the Difference?] appeared first on Pew Pew Tactical.

via Pew Pew Tactical
Building a Duty/Patrol AR-15 [What’s the Difference?]

Laravel — Extending Query Builder


Go to the profile of Dennis Smink

Sometimes the findOrFail() (or any other default) method is not enough and you need to have a method that applies to multiple models at the same time. This article describes how to achieve this. 🚀

Let’s grab a real simple example in this guide. Let’s say you have a News model and a Page model, both have the slug column in the database.

Sure, you could do something like this:

News::where('slug', 'my-slug')->firstOrFail();

But wouldn’t it be way nicer to have something like:

News::findBySlugOrFail('my-slug');

This is where extending the Laravel query builder comes in handy.

Start by creating a new file: app/Builder/MyBuilder.php 
(You can name this anything you like to be more specific, like AppBuilder.php or something like that. Just make sure you name the class inside the same as the file name for the namespace to kick in properly 👏)

With these contents:

<?php

namespace App\Builder;

use Illuminate\Database\Eloquent\Builder;

class MyBuilder extends Builder
{

}

Before we continue adding methods inside this builder, lets prepare our models to start using this class instead of the default Model from Eloquent.

If you where to have a model like News (or Page) it would look somewhat like this:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class News extends Model
{
protected $fillable = [
'slug',
'title',
'content'
];
}

We will have to start telling this model to use the newer query builder class. We do this like so:

<?php

namespace App;

use App\Builder\MyBuilder;

class News extends MyBuilder
{
protected $fillable = [
'slug',
'title',
'content'
];
}

This will still work like before, everything is available like you’re used too. We are now going to add new method to the MyBuilder.php class.

So lets say, we want to have a findBySlug() method:

<?php

namespace App\Builder;

use Illuminate\Database\Eloquent\Builder;

class MyBuilder extends Builder
{
public function findBySlug($slug, $columns = ['*'])
{
return $this->where('slug', $slug)->first($columns);
}
}

This will now allow you to do queries like this:

News::findBySlug('my-slug');

(And if you want, you can even pass in an array with columns you want to select)

You can go absolutely wild with this, lets extend this even further:

<?php

namespace App\Builder;

use Illuminate\Database\Eloquent\Builder;

class MyBuilder extends Builder
{
public function findBySlug($slug, $columns = ['*'])
{
return $this->where('slug', $slug)->first($columns);
}

public function findBySlugOrFail($slug, $columns = ['*'])
{
return $this->where('slug', $slug)->firstOrFail($columns);
}
}

This will make it possible to get a 404 if the first result is never found:

News::findBySlugOrFail('my-slug');

Basically each method from the eloquent class is available in the builder class; whereIn, whereHas, where, whereBetween, with, load etc etc etc..

Another example

I have a project where the slugs are stored inside a separate table, and polymorphed to its models.

To achieve the same thing as above, I will have to query on a relationship inside a model, and this is how I did that:

/**
* Find a model by its slug.
*
* @param string $slug
* @param array $columns
*
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null
*/
public function findBySlug($slug, $columns = ['*'])
{
return $this->whereHas('slug', function ($query) use ($slug) {
return $query->where('slug', $slug);
})->first($columns);
}
/**
* Find a model by its primary slug or throw an exception.
*
* @param string $slug
* @param array $columns
*
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[]
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findBySlugOrFail($slug, $columns = ['*'])
{
return $this->whereHas('slug', function ($query) use ($slug) {
return $query->where('slug', $slug);
})->firstOrFail($columns);
}

As you can see, there are many methods to write for your own logic to clean up controllers and your code at the same time.

And thats it! You can now start writing your own query methods inside your builder. Of course, you can also create multiple builders for specific use cases to separate code and clean it up more. 🚀

Note: please bear in mind that I am a Dutch developer, English is not my native language so this article could contain any grammatical errors.

via Laravel News Links
Laravel — Extending Query Builder

Smoothie: additional features for Eloquent

Smoothie

Some fruity additions to Laravel’s Eloquent:

⚠️ Note: only MySQL is tested and actively supported.

Miscellaneous

Save model and restore modified attributes

This package adds a new option restore to the save method:

$model->save(['restore' => true]);

This forces the model to refresh its original array of attributes from the database before saving. It’s useful when your database row has changed outside the current $model instance, and you need to make sure that the $model‘s current state will be saved exactly, even restoring attributes that haven’t changed in the current instance:

 $model1 = Article::find(1); $model2 = Article::find(1);  $model2->title = 'new title'; $model2->save();  $model1->save(['restore' => true]); // the original title will be restored  // because it hasn't changed in `$model1`

To use this option, you need your model to extend the Baril\Smoothie\Model class instead of Illuminate\Database\Eloquent\Model.

Update only

Laravel’s native update method will not only update the provided fields, but also whatever properties of the model were previously modified:

$article = Article::create(['title' => 'old title']); $article->title = 'new title'; $article->update(['subtitle' => 'new subtitle']);  $article->fresh()->title; // "new title"

This package provides another method called updateOnly, that will update the provided fields but leave the rest of the row alone:

$article = Article::create(['title' => 'old title']); $article->title = 'new title'; $article->updateOnly(['subtitle' => 'new subtitle']);  $article->fresh()->title; // "old title" $article->title; // "new title" $article->subtitle; // "new subtitle"

To use this method, you need your model to extend the Baril\Smoothie\Model class instead of Illuminate\Database\Eloquent\Model.

Explicitly order the query results

The package adds the following method to Eloquent collections:

$collection = YourModel::all()->sortByKeys([3, 4, 2]);

It allows for explicit ordering of collections by primary key. In the above example, the returned collection will contain (in this order):

  • model with id 3,
  • model with id 4,
  • model with id 2,
  • any other models of the original collection, in the same order as before calling sortByKeys.

Similarly, using the findInOrder method on models or query builders, instead of findMany, will preserve the order of the provided ids:

$collection = Article::findMany([4, 5, 3]); // we're not sure that the article  // with id 4 will be the first of  // the returned collection  $collection = Article::findInOrder([4, 5, 3]); // now we're sure

In order to use these methods, you need Smoothie’s service provider to be registered in your config\app.php (or use package auto-discovery):

return [  // ...  'providers' => [  Baril\Smoothie\SmoothieServiceProvider::class,  // ...  ], ];

Timestamp scopes

The Baril\Smoothie\Concerns\ScopesTimestamps trait provides some scopes for models with created_at and updated_at columns:

  • $query->orderByCreation($direction = 'asc'),
  • $query->createdAfter($date, $strict = false) (the $date argument can be of any datetime-castable type, and the $strict parameter can be set to true if you want to use a strict inequality),
  • $query->createdBefore($date, $strict = false),
  • $query->createdBetween($start, $end, $strictStart = false, $strictEnd = false),
  • $query->orderByUpdate($direction = 'asc'),
  • $query->updatedAfter($date, $strict = false),
  • $query->updatedBefore($date, $strict = false),
  • $query->updatedBetween($start, $end, $strictStart = false, $strictEnd = false).

Debugging

This package adds a debugSql method to the Builder class. It is similar as toSql except that it returns an actual SQL query where bindings have been replaced with their values.

Article::where('id', 5)->toSql(); // "SELECT articles WHERE id = ?" -- WTF? Article::where('id', 5)->debugSql(); // "SELECT articles WHERE id = 5" -- much better

In order to use this method, you need Smoothie’s service provider to be registered in your config\app.php (or use package auto-discovery).

(Credit for this method goes to Broutard, thanks!)

Field aliases

Basic usage

The Baril\Smoothie\Concerns\AliasesAttributes trait provides an easy way to normalize the attributes names of a model if you’re working with an existing database with column namings you don’t like.

There are 2 different ways to define aliases:

  • define a column prefix: all columns prefixed with it will become magically accessible as un-prefixed attributes,
  • define an explicit alias for a given column.

Let’s say you’re working with the following table (this example comes from the blog application Dotclear):

dc_blog blog_id blog_uid blog_creadt blog_upddt blog_url blog_name blog_desc blog_status 

Then you could define your model as follows:

class Blog extends Model {  const CREATED_AT = 'blog_creadt';  const UPDATED_AT = 'blog_upddt';   protected $primaryKey = 'blog_id';  protected $keyType = 'string';   protected $columnsPrefix = 'blog_';  protected $aliases = [  'description' => 'blog_desc',  ]; }

Now the blog_id column can be simply accessed this way: $model->id. Same goes for all other columns prefixed with blog_.

Also, the blog_desc column can be accessed with the more explicit alias description.

The original namings are still available. This means that there are actually 3 different ways to access the blog_desc column:

  • $model->blog_desc (original column name),
  • $model->desc (because of the blog_ prefix),
  • $model->description (thanks to the explicit alias).

Note: you can’t have an alias (explicit or implicit) for another alias. Aliases are for actual column names only.

Collisions and priorities

If an alias collides with a real column name, it will have priority over it. This means that in the example above, if the table had a column actually named desc or description, you wouldn’t be able to access it any more. You still have the possibility to define another alias for the column though.

class Article {  protected $aliases = [  'title' => 'meta_title',  'original_title' => 'title',  ]; }

In the example above, the title attribute of the model returns the value of the meta_title column in the database. The value of the title column can be accessed with the original_title attribute.

Also, explicit aliases have priority over aliases implicitely defined by a column prefix. This means that when an "implicit alias" collides with a real column name, you can define an explicit alias that restores the original column name:

class Article {  protected $aliases = [  'title' => 'title',  ];  protected $columnsPrefix = 'a_'; }

Here, the title attribute of the model will return the value of the title column of the database. The a_title column can be accessed with the a_title attribute (or you can define another alias for it).

Accessors, casts and mutators

You can define accessors either on the original attribute name, or the alias, or both.

  • If there’s an accessor on the original name only, it will always apply, whether you access the attribute with its original name or its alias.
  • If there’s an accessor on the alias only, it will apply only if you access the attribute using its alias.
  • If there’s an accessor on both, each will apply individually (and will receive the original $value).
class Blog extends Model {  const CREATED_AT = 'blog_creadt';  const UPDATED_AT = 'blog_upddt';   protected $primaryKey = 'blog_id';  protected $keyType = 'string';   protected $columnsPrefix = 'blog_';  protected $aliases = [  'description' => 'blog_desc',  ];   public function getPrDescAttribute($value)  {  return trim($value);  }   public function getDescriptionAttribute($value)  {  return htmlentities($value);  } }  $blog->pr_desc; // will return the trimmed description $blog->desc; // will return the trimmed description $blog->description; // will return the untrimmed, HTML-encoded description

The same logic applies to casts and mutators.

⚠️ Note: if you define a cast on the alias and an accessor on the original attribute name, the accessor won’t apply to the alias, only the cast will.

Trait conflict resolution

The AliasesAttributes trait overrides the getAttribute and setAttribute methods of Eloquent’s Model class. If you’re using this trait with another trait that override the same methods, you can just alias the other trait’s methods to getUnaliasedAttribute and setUnaliasedAttribute. AliasesAttributes::getAttribute and AliasesAttributes::setAttribute will call getUnaliasedAttribute or setUnaliasedAttribute once the alias is resolved.

class MyModel extends Model {  use AliasesAttributes, SomeOtherTrait {  AliasesAttributes::getAttribute insteadof SomeOtherTrait;  SomeOtherTrait::getAttribute as getUnaliasedAttribute;  AliasesAttributes::setAttribute insteadof SomeOtherTrait;  SomeOtherTrait::setAttribute as setUnaliasedAttribute;  } }

Accessor cache

Basic usage

Sometimes you define an accessor in your model that requires some computation time or executes some queries, and you don’t want to go through the whole process everytime you call this accessor. That’s why this package provides a trait that "caches" (in a protected property of the object) the results of the accessors.

You can define which accessors are cached using either the $cacheable property or the $uncacheable property. If none of them are set, then everything is cached.

class MyModel extends Model {  use \Baril\Smoothie\Concerns\CachesAccessors;   protected $cacheable = [  'some_attribute',  'some_other_attribute',  ]; }  $model = MyModel::find(1); $model->some_attribute; // cached $model->yet_another_attribute; // not cached

Clearing cache

The cache for an attribute is cleared everytime this attribute is set. If you have an accessor for an attribute A that depends on another attribute B, you probably want to clear A’s cache when B is set. You can use the $clearAccessorCache property to define such dependencies:

class User extends Model {  use \Baril\Smoothie\Concerns\CachesAccessors;   protected $clearAccessorCache = [  'first_name' => ['full_name', 'name_with_initial'],  'last_name' => ['full_name', 'name_with_initial'],  ];   public function getFullNameAttribute()  {  return $this->first_name . ' ' . $this->last_name;  }   public function getNameWithInitialAttribute()  {  return substr($this->first_name, 0, 1) . '. ' . $this->last_name;  } }  $user = new User([  'first_name' => 'Jean',  'last_name' => 'Dupont', ]); echo $user->full_name; // "Jean Dupont" $user->first_name = 'Lazslo'; echo $user->full_name; // "Lazslo Dupont": cache has been cleared

Cache and aliases

If you want to use both the AliasesAttributes trait and the CachesAccessors trait in the same model, the best way to do it is to use the AliasesAttributesWithCache trait, which merges the features of both traits properly. Setting an attribute or an alias will automatically clear the accessor cache for all aliases of the same attribute.

Fuzzy dates

The package provides a modified version of the Carbon class that can handle SQL "fuzzy" dates (where the day, or month and day, are zero).

With the original version of Carbon, such dates wouldn’t be interpreted properly, for example 2010-10-00 would be interpreted as 2010-09-30.

With this version, zeros are allowed. An additional method is provided to determine if the date is fuzzy:

$date = Baril\Smoothie\Carbon::createFromFormat('Y-m-d', '2010-10-00'); $date->day; // will return null $date->isFuzzy(); // will return true if month and/or day is zero

The format and formatLocalized methods now have two additional (optional) arguments $formatMonth and $formatYear. If the date is fuzzy, the method will automatically fallback to the appropriate format:

$date = Baril\Smoothie\Carbon::createFromFormat('Y-m-d', '2010-10-00'); $date->format('d/m/Y', 'm/Y', 'Y'); // will display "10/2010"

⚠️ Note: because a fuzzy date can’t convert to a timestamp, a date like 2010-10-00 is transformed to 2010-10-01 internally before conversion to timestamp. Thus, any method or getter that relies on the timestamp value might return an "unexpected" result:

$date = Baril\Smoothie\Carbon::createFromFormat('Y-m-d', '2010-10-00'); $date->dayOfWeek; // will return 5, because October 1st 2010 was a friday

If you need fuzzy dates in your models, use the Baril\Smoothie\Concerns\HasFuzzyDates trait. Then, fields cast as date or datetime will use this modified version of Carbon:

class Book extends \Illuminate\Database\Eloquent\Model {  use \Baril\Smoothie\Concerns\HasFuzzyDates;   protected $casts = [  'is_available' => 'boolean',  'release_date' => 'date', // will allow fuzzy dates  ]; }

Alternatively, you can extend the Baril\Smoothie\Model class to achieve the same result. This class already uses the HasFuzzyDates trait (as well as some other traits described in the subsequent sections):

class Book extends \Baril\Smoothie\Model {  protected $casts = [  'is_available' => 'boolean',  'release_date' => 'date', // will allow fuzzy dates  ]; }

⚠️ Note: you will need to disable MySQL strict mode in your database.php config file in order to use fuzzy dates:

return [  'connections' => [  'mysql' => [  'strict' => false,  // ...  ],  ],  // ... ];

If you don’t want to disable strict mode, another option is to use 3 separate columns and merge them into one. To achieve this easily, you can use the mergeDate method in the accessor, and the splitDate method is the mutator:

class Book extends \Illuminate\Database\Eloquent\Model {  use \Baril\Smoothie\Concerns\HasFuzzyDates;   public function getReleaseDateAttribute()  {  return $this->mergeDate(  'release_date_year',  'release_date_month',  'release_date_day'  );  }   public function setReleaseDateAttribute($value)  {  $this->splitDate(  $value,  'release_date_year',  'release_date_month',  'release_date_day'  );  } }

The last 2 arguments of both methods can be omitted, if your column names use the suffixes _year, _month and _day. The following example is similar as the one above:

class Book extends \Illuminate\Database\Eloquent\Model {  use \Baril\Smoothie\Concerns\HasFuzzyDates;   public function getReleaseDateAttribute()  {  return $this->mergeDate('release_date');  }   public function setReleaseDateAttribute($value)  {  $this->splitDate($value, 'release_date');  } }

⚠️ Note: your _month and _day columns must be nullable, since a "zero" month or day will be stored as null.

Mutually-belongs-to-many-selves relationship

Usage

This new type of relationship defines a many-to-many, mutual relationship to the same table/model. Laravel’s native BelongsToMany relationship can already handle self-referencing relationships, but with a direction (for example sellers/buyers). The difference is that the MutuallyBelongsToManySelves relationship is meant to handle "mutual" relationships (such as friends):

class User extends \Illuminate\Database\Eloquent\Model {  use \Baril\Smoothie\Concerns\HasMutualSelfRelationships;   public function friends()  {  return $this->mutuallyBelongsToManySelves();  } }

With this type of relationship, attaching $user1 to $users2‘s friends will implicitely attach $user2 to $user1‘s friends as well:

$user1->friends()->attach($user2->id); $user2->friends()->get(); // contains $user1

Similarly, detaching one side of the relation will detach the other as well:

$user2->friends()->detach($user1->id); $user1->friends()->get(); // doesn't contain $user2 any more

The full prototype for the mutuallyBelongsToManySelves method is similar to belongsToMany, without the first argument (which we don’t need since we already know that the related class is the class itself):

public function mutuallyBelongsToManySelves(   // Name of the pivot table (defaults to the snake-cased model name,  // concatenated to itself with an underscore separator,  // eg. "user_user"):  $table = null,   // First pivot key (defaults to the model's default foreign key, with  // an added number, eg. "user1_id"):  $firstPivotKey = null,   // Second pivot key (the pivot keys can be passed in any order since  // the relationship is mutual):  $secondPivotKey = null,   // Parent key (defaults to the model's primary key):  $parentKey = null,   // Relation name (defaults to the name of the caller method):  $relation = null)

In order to use the mutuallyBelongsToManySelves method, your model needs to either use the Baril\Smoothie\Concerns\HasMutualSelfRelationships, or extend the Baril\Smoothie\Model class.

Cleanup command

In order to avoid duplicates, the MutuallyBelongsToManySelves class will ensure that attaching $model1 to $model2 will insert the same pivot row as attaching $model2 to $model1: the key defined as the first pivot key of the relationship will always receive the smaller id. In case you’re working with pre-existing data, and you’re not sure that the content of your pivot table follows this rule, you can use the following Artisan command that will check the data and fix it if needed:

php artisan smoothie:fix-pivots "App\\YourModelClass" relationName

N-ary many-to-many relationships

Let’s say that you’re building a project management app. Each user of your app has many roles in your ACL system: projet manager, developer… But each role applies to a specific project rather than the whole app.

Your basic database structure probably looks something like this:

projects id - integer name - string roles id - integer name - string users id - integer name - string project_role_user project_id - integer role_id - integer user_id - integer 

Of course, you could define classic belongsToMany relations between your models, and even add a withPivot clause to include the 3rd pivot column:

class User extends Model {  public function projects()  {  return $this->belongsToMany(Project::class, 'project_role_user')->withPivot('role_id');  }   public function roles()  {  return $this->belongsToMany(Role::class, 'project_role_user')->withPivot('project_id');  } }

It won’t be very satisfactory though, because:

  • querying $user->projects() or $user->roles() might return duplicated results (in case the user has 2 different roles in the same project, or the same role in 2 different projects),
  • Both relations are not related to one another, so there’s no elegant way to retrieve the user’s role for a specific project, or the projects where the user has a specific role.

That’s where the belongsToMultiMany relation comes in handy.

Setup

Step 1: add a primary key to your pivot table.

class AddPrimaryKeyToProjectRoleUserTable extends Migration { public function up() { Schema::table('project_role_user', function (Blueprint $table) { $table->increments('id')->first(); }); } public function down() { Schema::table('project_role_user', function (Blueprint $table) { $table->dropColumn('id'); }); } } 

Step 2: have your model use the Baril\Smoothie\Concerns\HasMultiManyRelationships trait (or extend the Baril\Smoothie\Model class).

Step 3: define your relations with belongsToMultiMany instead of belongsToMany. The prototype for both methods is the same except that:

  • the 2nd argument (pivot table name) is required for belongsToMultiMany (because we wouldn’t be able to guess it),
  • there’s an additional 3rd (optional) argument which is the name of the primary key of the pivot table (defaults to id).
class User extends Model {  use HasMultiManyRelationships;   public function projects()  {  return $this->belongsToMultiMany(Project::class, 'project_role_user');  }   public function roles()  {  return $this->belongsToMultiMany(Role::class, 'project_role_user');  } }

You can do the same in all 3 classes, which means you will declare 6 different relations. Note that:

  • To avoid confusion, it’s better (but not required) to give the same name to the similar relations (Project::roles() and User::roles()).
  • You don’t have to define all 6 relations if there are some of them you know you’ll never need.

Also, notice that the definition of the relations are independant: there’s nothing here that says that projects and roles are related to one another. The magic will happen only because they’re defined as "multi-many" relationships and because they’re using the same pivot table.

Querying the relations

Overall, multi-many relations behave exactly like many-to-many relations. There are 2 differences though.

The first difference is that multi-many relations will return "folded" (ie. deduplicated) results. For example, if $user has the role admin in 2 different projects, $user->roles will return admin only once (contrary to a regular BelongsToMany relation). Should you need to fetch the "unfolded" results, you can just chain the unfolded() method:

$user->roles()->unfolded()->get();

The 2nd (and most important) difference is that when you "chain" 2 (or more) "sibling" multi-many relations, the result returned by each relation will be automatically constrained by the previously chained relation(s).

Check the following example:

$roles = $user->projects->first()->roles;

Here, a regular BelongsToMany relation would have returned all roles related to the project, whether they’re attached to this $user or another one. But with multi-many relations, $roles contains only the roles of $user in this project.

If you ever need to, you can always cancel this behavior by chaining the all() method:

$project = $user->projects->first(); $roles = $project->roles()->all()->get();

Now $roles contains all the roles for $project, whether they come from this $user or any other one.

Another way to use the multi-many relation is as follows:

$project = $user->projects->first(); $roles = $user->roles()->for('project', $project)->get();

This will return only the roles that $user has on $project. It’s a nicer way to write the following:

$project = $user->projects->first(); $roles = $user->roles()->withPivot('project_id', $project->id)->get();

The arguments for the for method are:

  • the name of the "other" relation in the parent class (here: projects, as in the method User::projects()), or its singular version (project),
  • either a model object or id, or a collection (of models or ids), or an array of ids.

Eager-loading

The behavior described above works with eager loading too:

$users = User::with('projects', 'projects.roles')->get(); $user = $users->first(); $user->projects->first()->roles; // only the roles of $user on this project

Similarly as the all() method described above, you can use withAll if you don’t want to constrain the 2nd relation:

$users = User::with('projects')->withAll('projects.roles')->get();

Note: for non multi-many relations, or "unconstrained" multi-many relations, withAll is just an alias of with:

$users = User::with('projects', 'status')->withAll('projects.roles')->get(); // can be shortened to: $users = User::withAll('projects', 'projects.roles', 'status')->get();

Querying relationship existence

Querying the existence of a relation will also have the same behavior:

User::has('projects.roles')->get();

The query above will return the users who have a role in any project.

Attaching / detaching related models

Attaching models to a multi-many relation will fill the pivot values for all the previously chained "sibling" multi-many relations

$user->projects()->first()->roles()->attach($admin); // The new pivot row will receive $user's id in the user_id column.

Detaching models from a relation will also take into account all the "relation chain":

$user->projects()->first()->roles()->detach($admin); // Will detach the $admin role from this project, for $user only. // Other admins of this project will be preserved.

Again, the behavior described above can be disabled by chaining the all() method:

$user->projects()->first()->roles()->all()->attach($admin); // The new pivot row's user_id will be NULL.  $user->projects()->first()->roles()->all()->detach($admin); // Will delete all pivot rows for this project and the $admin role, // whoever the user is.

Multi-many relations "wrapper"

The WrapMultiMany relation provides an alternative way to handle multi-many relations. It can be used together with the BelongsToMultiMany relations or independantly.

Instead of looking at the ternary relation as six many-to-many relations that can be chained after another, we could look at it this way:

  • a user has many role/project pairs,
  • each of these pairs has one role and one project.

Of course, similarly, a role has many user/project pairs and a project has many role/user pairs.

To implement this, we could create a model for the pivot table and then define all relations manually, but the WrapMultiMany relation provides a quicker alternative.

class User extends Model {  use HasMultiManyRelationships;   public function authorizations()  {  return $this->wrapMultiMany([  'project' => $this->belongsToMultiMany(Project::class, 'project_role_user'),  'role' => $this->belongsToMultiMany(Role::class, 'project_role_user'),  ]);  } }

The authorizations method above defines the following relations:

  • a HasMany relation from User to the pivot table,
  • a BelongsTo relation named project, from the pivot table to Project,
  • a BelongsTo relation named role, from the pivot table to Role.

You can query the relations like any regular relation, and even eager-load them:

$users = User::with('authorizations', 'authorizations.role', 'authorizations.project')->get();  foreach ($users as $user) {  foreach ($user->authorizations as $authorization) {  dump($authorization->role);  dump($authorization->project);  } }

You can use the following methods to insert or update data in the pivot table:

$user->authorizations()->attach($pivots, $additionalAttributes); $user->authorizations()->sync($pivots); $user->authorizations()->detach($pivots);

The $pivots argument can be of different types:

$pivots = $user->authorizations->first(); // a Model $pivots = $user->authorizations->slice(0, 2); // an EloquentCollection of Models $pivots = ['role_id' => $roleId, 'project_id' => $projectId]; // an associative array keyed by the column names... $pivots = ['role' => $roleId, 'project' => $projectId]; // ... or the relation names $pivots = ['role' => Role::first(), 'project' => Project::first()]; // ... where values can be ids or Models $pivots = [ ['role_id' => $roleId, 'project_id' => $projectId] ]; // an array of such associative arrays $pivots = collect([ ['role_id' => $roleId, 'project_id' => $projectId] ]); // or even a Collection

Orderable behavior

Adds orderable behavior to Eloquent models (forked from https://github.com/boxfrommars/rutorika-sortable).

Setup

First, add a position field to your model (see below how to change this name):

public function up() {  Schema::create('articles', function (Blueprint $table) {  // ... other fields ...  $table->unsignedInteger('position');  }); }

Then, use the \Baril\Smoothie\Concerns\Orderable trait in your model. The position field should be guarded as it won’t be filled manually.

class Article extends Model {  use \Baril\Smoothie\Concerns\Orderable;   protected $guarded = ['position']; }

You need to set the $orderColumn property if you want another name than position:

class Article extends Model {  use \Baril\Smoothie\Concerns\Orderable;   protected $orderColumn = 'order';  protected $guarded = ['order']; }

Basic usage

You can use the following method to change the model’s position (no need to save it afterwards, the method does it already):

  • moveToOffset($offset) ($offset starts at 0 and can be negative, ie. $offset = -1 is the last position),
  • moveToStart(),
  • moveToEnd(),
  • moveToPosition($position) ($position starts at 1 and must be a valid position),
  • moveUp($positions = 1, $strict = true): moves the model up by $positions positions (the $strict parameter controls what happens if you try to move the model "out of bounds": if set to false, the model will simply be moved to the first or last position, else it will throw a PositionException),
  • moveDown($positions = 1, $strict = true),
  • swapWith($anotherModel),
  • moveBefore($anotherModel),
  • moveAfter($anotherModel).
$model = Article::find(1); $anotherModel = Article::find(10) $model->moveAfter($anotherModel); // $model is now positioned after $anotherModel, and both have been saved

Also, this trait:

  • automatically defines the model position on the create event, so you don’t need to set position manually,
  • automatically decreases the position of subsequent models on the delete event so that there’s no "gap".
$article = new Article(); $article->title = $request->input('title'); $article->body = $request->input('body'); $article->save();

This model will be positioned at MAX(position) + 1.

To get ordered models, use the ordered scope:

$articles = Article::ordered()->get(); $articles = Article::ordered('desc')->get();

(You can cancel the effect of this scope by calling the unordered scope.)

Previous and next models can be queried using the previous and next methods:

$entity = Article::find(10); $entity->next(10); // returns a QueryBuilder on the next 10 entities, ordered $entity->previous(5)->get(); // returns a collection with the previous 5 entities, in reverse order $entity->next()->first(); // returns the next entity

Mass reordering

The move* methods described above are not appropriate for mass reordering because:

  • they would perform many unneeded queries,
  • changing a model’s position affects other model’s positions as well, and can cause side effects if you’re not careful.

Example:

$models = Article::orderBy('publication_date', 'desc')->get(); $models->map(function($model, $key) {  return $model->moveToOffset($key); });

The sample code above will corrupt the data because you need each model to be "fresh" before you change its position. The following code, on the other hand, will work properly:

$collection = Article::orderBy('publication_date', 'desc')->get(); $collection->map(function($model, $key) {  return $model->fresh()->moveToOffset($key); });

It’s still not a good way to do it though, because it performs many unneeded queries. A better way to handle mass reordering is to use the saveOrder method on a collection:

$collection = Article::orderBy('publication_date', 'desc')->get(); // $collection is not a regular Eloquent collection object, it's a custom class // with the following additional method: $collection->saveOrder();

That’s it! Now the items’ order in the collection has been applied to the position column of the database.

To define the order explicitely, you can do something like this:

$collection = Status::all(); $collection->sortByKeys([2, 1, 5, 3, 4])->saveOrder();

Note: Only the models within the collection are reordered / swapped between one another. The other rows in the table remain untouched.

Orderable groups / one-to-many relationships

Sometimes, the table’s data is "grouped" by some column, and you need to order each group individually instead of having a global order. To achieve this, you just need to set the $groupColumn property:

class Article extends Model {  use \Baril\Smoothie\Concerns\Orderable;   protected $guarded = ['position'];  protected $groupColumn = 'section_id'; }

If the group is defined by multiple columns, you can use an array:

protected $groupColumn = ['field_name1', 'field_name2'];

Orderable groups can be used to handle ordered one-to-many relationships:

class Section extends Model {  public function articles()  {  return $this->hasMany(Article::class)->ordered();  } }

Ordered many-to-many relationships

If you need to order a many-to-many relationship, you will need a position column (or some other name) in the pivot table.

Have your model use the \Baril\Smoothie\Concerns\HasOrderedRelationships trait (or extend the Baril\Smoothie\Model class):

class Post extends Model {  use \Baril\Smoothie\Concerns\HasOrderedRelationships;   public function tags()  {  return $this->belongsToManyOrdered(Tag::class);  } }

The prototype of the belongsToManyOrdered method is similar as belongsToMany with an added 2nd parameter $orderColumn:

public function belongsToManyOrdered(  $related,  $orderColumn = 'position',  $table = null,  $foreignPivotKey = null,  $relatedPivotKey = null,  $parentKey = null,  $relatedKey = null,  $relation = null)

Now all the usual methods from the BelongsToMany class will set the proper position to attached models:

$post->tags()->attach($tag->id); // will attach $tag and give it the last position $post->tags()->sync([$tag1->id, $tag2->id, $tag3->id]) // will keep the provided order $post->tags()->detach($tag->id); // will decrement the position of subsequent $tags

When queried, the relation is sorted by default. If you want to order the related models by some other field, you will need to use the unordered scope first:

$post->tags; // ordered by position $post->tags()->ordered('desc')->get(); // reverse order $post->tags()->unordered()->get(); // unordered  // Note that orderBy has no effect here since the tags are already ordered by position: $post->tags()->orderBy('id')->get();  // This is the proper way to do it: $post->tags()->unordered()->orderBy('id')->get();

Of course, you can also define the relation like this if you don’t want it ordered by default:

class Post extends Model {  use \Baril\Smoothie\Concerns\HasOrderedRelationships;   public function tags()  {  return $this->belongsToManyOrdered(Tag::class)->unordered();  } }  $article->tags; // unordered $article->tags()->ordered()->get(); // ordered

The BelongsToManyOrdered class has all the same methods as the Orderable trait, except that you will need to pass them a related $model to work with:

  • moveToOffset($model, $offset),
  • moveToStart($model),
  • moveToEnd($model),
  • moveToPosition($model, $position),
  • moveUp($model, $positions = 1, $strict = true),
  • moveDown($model, $positions = 1, $strict = true),
  • swap($model, $anotherModel),
  • moveBefore($model, $anotherModel) ($model will be moved before $anotherModel),
  • moveAfter($model, $anotherModel) ($model will be moved after $anotherModel),
  • before($model) (similar as the previous method from the Orderable trait),
  • after($model) (similar as next).
$tag1 = $article->tags()->first(); $tag2 = $article->tags()->last(); $article->tags()->moveBefore($tag1, $tag2); // now $tag1 is at the second to last position

Note that if $model doesn’t belong to the relationship, any of these methods will throw a Baril\Smoothie\GroupException.

There’s also a method for mass reordering:

$article->tags()->setOrder([$id1, $id2, $id3]);

In the example above, tags with ids $id1, $id2, $id3 will now be at the beginning of the article’s tags collection. Any other tags attached to the article will come after, in the same order as before calling setOrder.

Ordered morph-to-many relationships

Similarly, the package defines a MorphToManyOrdered type of relationship. The 3rd parameter of the morphToManyOrdered method is the name of the order column (defaults to position):

class Post extends Model {  use \Baril\Smoothie\Concerns\HasOrderedRelationships;   public function tags()  {  return $this->morphToManyOrdered('App\Tag', 'taggable', 'tag_order');  } }

Same thing with the morphedByManyOrdered method:

class Tag extends Model {  use \Baril\Smoothie\Concerns\HasOrderedRelationships;   public function posts()  {  return $this->morphedByManyOrdered('App\Post', 'taggable', 'order');  }   public function videos()  {  return $this->morphedByManyOrdered('App\Video', 'taggable', 'order');  } }

Tree-like structures and closure tables

This is an implementation of the "Closure Table" design pattern for Laravel and SQL. This pattern allows for faster querying of tree-like structures stored in a relational database.

Setup

You will need to create a closure table in your database. For example, if your main table is tags, you will need a closure table named tag_tree (you can change this name if you want — see below), with the following columns:

  • ancestor_id: foreign key to your main table,
  • descendant_id: foreign key to your main table,
  • depth: unsigned integer.

Of course, you don’t need to write the migration manually: the package provides an Artisan command for that (see below).

Also, your main table will need a parent_id column with a self-referencing foreign key (you can change this name too — see below). This column is the one that holds the actual hierarchical data: the closures are merely a duplication of that information.

Once your database is ready, have your model implement the Baril\Smoothie\Concerns\BelongsToTree trait.

You can use the following properties to configure the table and column names:

  • $parentForeignKey: name of the self-referencing foreign key in the main table (defaults to parent_id),
  • $closureTable: name of the closure table (defaults to the snake-cased model name suffixed with _tree, eg. tag_tree).
class File extends \Illuminate\Database\Eloquent\Model {  use \Baril\Smoothie\Concerns\BelongsToTree;   protected $parentForeignKey = 'folder_id';  protected $closureTable = 'file_closures'; }

Artisan commands

Note: you need to configure your model as described above before you use these commands.

The grow-tree command will generate the migration file for the closure table:

php artisan smoothie:grow-tree "App\\YourModel"

If you use the --migrate option, then the command will also run the migration. If your main table already contains data, it will also insert the closures for the existing data.

php artisan smoothie:grow-tree "App\\YourModel" --migrate

⚠️ Note: if you use the --migrate option, any other pending migrations will run too.

There are some additional options: use --help to learn more.

If you ever need to re-calculate the closures, you can use the following command:

php artisan smoothie:fix-tree "App\\YourModel"

It will truncate the table and fill it again based on the data from the main table.

Finally, the show-tree command provides a quick-and-easy way to output the content of the tree. It takes a label parameter that defines which column (or accessor) to use as label. Optionally you can also specify a max depth.

php artisan smoothie:show-tree "App\\YourModel" --label=name --depth=3

Basic usage

Just fill the model’s parent_id and save the model: the closure table will be updated accordingly.

$tag = Tag::find($tagId); $tag->parent_id = $parentTagId; // or: $tag->parent()->associate($parentTag); $tag->save();

The save method will throw a \Baril\Smoothie\TreeException in case of a redundancy error (ie. if the parent_id corresponds to the model itself or one of its descendants).

When you delete a model, its closures will be automatically deleted. If the model has descendants, the delete method will throw a TreeException. You need to use the deleteTree method if you want to delete the model and all its descendants.

try {  $tag->delete(); } catch (\Baril\Smoothie\TreeException $e) {  // some specific treatment  // ...  $tag->deleteTree(); }

Relationships

The trait defines the following relationships (which can’t be renamed for now):

  • parent: BelongsTo relation to the parent,
  • children: HasMany relation to the children,
  • ancestors: BelongsToMany relation to the ancestors,
  • ancestorsWithSelf: BelongsToMany relation to the ancestors, including $this,
  • descendants: BelongsToMany relation to the descendants.
  • descendantsWithSelf: BelongsToMany relation to the descendants, including $this.

⚠️ Note: The ancestors and descendants (and -WithSelf) relations are read-only! Trying to use the attach or detach method on them will throw an exception.

The ancestors and descendants relations can be ordered by depth (ie. with the direct parent/children first):

$tags->descendants()->orderByDepth()->get();

Loading or eager-loading the descendants relation will automatically load the children relation (with no additional query). Furthermore, it will load the children relation recursively for all the eager-loaded descendants:

$tags = Tag::with('descendants')->limit(10)->get();  // The following code won't execute any new query: foreach ($tags as $tag) {  dump($tag->name);  foreach ($tag->children as $child) {  dump('-' . $child->name);  foreach ($child->children as $grandchild) {  dump('--' . $grandchild->name);  }  } }

Of course, same goes with the ancestors and parent relations.

You can retrieve the whole tree with this method:

It will return a collection of the root elements, with the children relation eager-loaded on every element up to the leafs.

Methods

The trait defines the following methods:

  • isRoot(): returns true if the item’s parent_id is null,
  • isLeaf(): checks if the item is a leaf (ie. has no children),
  • hasChildren(): $tag->hasChildren() is similar to !$tag->isLeaf(), albeit more readable,
  • isChildOf($item),
  • isParentOf($item),
  • isDescendantOf($item),
  • isAncestorOf($item),
  • isSiblingOf($item),
  • commonAncestorWith($item): returns the first common ancestor between 2 items, or null if they don’t have a common ancestor (which can happen if the tree has multiple roots),
  • distanceTo($item): returns the "distance" between 2 items,
  • depth(): returns the depth of the item in the tree,
  • subtreeDepth(): returns the depth of the subtree of which the item is the root.

Query scopes

  • withAncestors($depth = null, $constraints = null): shortcut to with('ancestors'), with the added ability to specify a $depth limit (eg. $query->withAncestors(1) will only load the direct parent). Optionally, you can pass additional $constraints.
  • withDescendants($depth = null, $constraints = null).
  • withDepth($as = 'depth'): will add a depth column (or whatever alias you provided) on your resulting models.
  • whereIsRoot($bool = true): limits the query to the items with no parent (the behavior of the scope can be reversed by setting the $bool argument to false).
  • whereIsLeaf($bool = true).
  • whereHasChildren($bool = true): is just the opposite of whereIsLeaf.
  • whereIsDescendantOf($ancestorId, $maxDepth = null, $includingSelf = false): limits the query to the descendants of $ancestorId, with an optional $maxDepth. If the $includingSelf parameter is set to true, the ancestor will be included in the query results too.
  • whereIsAncestorOf($descendantId, $maxDepth = null, $includingSelf = false).
  • orderByDepth($direction = 'asc'): this scope will work only when querying the ancestors or descendants relationships (see examples below).
$tag->ancestors()->orderByDepth(); Tag::with(['descendants' => function ($query) {  $query->orderByDepth('desc'); }]);

Ordered tree

In case you need each level of the tree to be explicitely ordered, you can use the Baril\Smoothie\Concerns\BelongsToOrderedTree trait (instead of BelongsToTree).

You will need a position column in your main table (the name of the column can be configured with the $orderColumn property).

class Tag extends \Illuminate\Database\Eloquent\Model {  use \Baril\Smoothie\Concerns\BelongsToOrderedTree;   protected $orderColumn = 'order'; }

The children relation will now be ordered. In case you need to order it by some other field (or don’t need the children ordered at all), you can use the unordered scope:

$children = $this->children()->unordered()->orderBy('name');

Also, all methods defined by the Orderable trait described above will now be available:

$lastChild->moveToPosition(1);

Cacheable behavior

This package provides a Cacheable trait for models. This is not a per-item or per-query cache but rather a caching system that will store the whole table contents as a collection. Thus, it’s to be used with small tables that store referential data that won’t change very often (such as a list of countries or statuses).

Basic principles

The basic principles are:

  • The first time a "cached" query is executed, the whole contents of the table will be stored in the cache as an Eloquent\Collection with an infinite lifetime.
  • The following methods will always use the cache when called statically on the model class: first and its variants, find and its variants, pluck, count and all.
  • Other queries won’t be cached by default, but caching can be enabled on certain conditions by chaining the usingCache method to the query builder (see below).
  • When a model is inserted, updated or deleted, the cache for its table is cleared. You can also clear the cache manually using the clearCache static method.

Setup

Just use the Cacheable trait on your model class. Optionally, you can specify which cache driver to use with the $cache property:

class Country extends Model {  use \Baril\Smoothie\Concerns\Cacheable;   protected $cache = 'redis'; }

Of course $cache must reference a cache store defined in the cache.php config file.

If you need a finer customization of the cache store (such as setting tags), you can do so by overriding the getCache method:

class Country extends Model {  use \Baril\Smoothie\Concerns\Cacheable;   public function getCache()  {  return app('cache')->store('redis')->tags(['referentials']);  } }

By default, what will be stored in the cache is the return of Model::all(), but it can be customized by overriding the loadFromDatabase method, for example if you need to load relations:

class Country extends Model {  use \Baril\Smoothie\Concerns\Cacheable;   protected static function loadFromDatabase()  {  return static::with('languages')->get();  } }

Now the countries will be stored in the cache with their languages relation loaded.

Caching queries

Caching specific queries is possible but only for very simple queries (see below). In order to enable cache on a query, you need to chain the usingCache method to the builder:

Country::where('code', 'fr_FR')->usingCache()->get();

When the get method is called, the following occurs:

  1. The collection with the whole table contents is fetched from the cache (or from the database and stored in the cache if it was previously empty).
  2. All where and orderBy clauses of the query are applied to the collection (using the where and sortBy methods).
  3. The filtered and sorted collection is returned.

Step 2 will work only on the following conditions:

  • All the where and orderBy clauses are translatable into method calls on the collection. This excludes more complex clauses such as raw SQL clauses, WHERE clauses joined by an OR operator or with a LIKE operator.
  • No other clauses (such as having, groupBy or with) must be applied to the query, since they’re not translatable.

⚠️ If you use untranslatable clauses and still enable cache, no exception will be thrown, but the clauses will be ignored and the query will return unexpected results.

Cached relations

Since Laravel relations behave like query builders, they can use cache too. Of course, the related model needs to use the Cacheable trait.

class User extends Model {  public function country()  {  return $this->belongsTo(Country::class)->usingCache();  } }

Now the country relation will always use cache when queried, unless you disable it explicitely:

$user->country()->usingCache(false)->get();

BelongsToMany (and BelongsToMultiMany) relations can use cache too, but:

  • a query to the pivot table will still be executed,
  • the model that defines the relation need to use the CachesRelationships trait.
class User extends Model {  use \Baril\Smoothie\Concerns\CachesRelationships;   public function groups()  {  return $this->belongsToMany(Group::class)->usingCache();  } }

via Laravel News Links
Smoothie: additional features for Eloquent

Add 2FA to your Laravel Application using Hydro Raindrop

Hydro Raindrop

Hydro Raindrop was built to protect against phishing, hacking, and illegal attempts to access your clients’ data. Hydro has easy to implement APIs and a beautiful mobile app available for your users on Android and iOS. The best part is, the integration is 100% FREE for you and your users!

Read more about Hydro Raindrop

Requirements

  • PHP 7.1
  • Laravel 5.8
  • The Hydro App (iOS or Android)

Laravel

This package allows developers to integrate a second layer of security (Multi Factor Authentication) to their apps.

Register

Before you can use the service, you need to create a developer account at www.hydrogenplatform.com. You can create a new application to obtain a Client ID, Client Secret and Application ID.

By default you can use the Sandbox environment, you can apply for a production environment any time through the www.hydrogenplatform.com website.

Installation

Install the package using the following composer command:

composer require adrenth/laravel-hydro-raindrop 

Install assets

Publish the public assets:

php artisan vendor:publish --tag=public --force 

Install configuration

Publish the configuration file app/hydro-raindrop.php:

php artisan vendor:publish --tag=config 

Authentication Routes (optional)

To add Laravels’ default Authentication routes, execute this command:

php artisan make:auth 

Please see the official documentation on this subject: https://laravel.com/docs/5.8/authentication

Environment configuration

Add the environment variables to your .env.example file:

HYDRO_RAINDROP_CLIENT_ID = "[Client ID here]" HYDRO_RAINDROP_SECRET = "[Client Secret here]" HYDRO_RAINDROP_APPLICATION_ID = "[Application ID here]" HYDRO_RAINDROP_ENVIRONMENT = "sandbox" 

Don’t commit sensitive information to your repositories. Your .env file should contain the actual credentials and should be ignored by Git.

Look for the app/raindrop.php file and review the configuration.

After changing you configuration, don’t forget to run the following command which clears the configuration cache.

php artisan config:cache 

Run database migrations

Run the database migrations.

php artisan migrate 

This will add the column hydro_id, hydro_raindrop_enabled and hydro_raindrop_confirmed to the users database table (table name is configurable, check config/raindrop.php).

Middleware

Add the raindrop middleware to the App/Http/Kernel:

protected $routeMiddleware = [ // .. 'hydro-raindrop' => \Adrenth\LaravelHydroRaindrop\Middleware::class ]; 

Usage

Now add the raindrop middleware to the routes you’d like to protect with Hydro Raindrop MFA.

Route::get('/admin', function () { return view('admin.index'); })->middleware(['auth', 'hydro-raindrop']); 

Note that the hydro-raindrop middleware only works with an authenticated session. So it should be used in combination with the auth middleware.

Throttling / Lockout after x attempts

Unless you need something really fancy, you can probably use Laravel’s route throttle middleware for that:

Route::get('/admin', function () { return view('admin.index'); })->middleware(['auth', 'hydro-raindrop', 'throttle']); 

Overriding Package Views

It is possible to override the views provided by this package.

Please see the (Laravel documentation page)[https://laravel.com/docs/5.8/packages#views] about overriding views.

Helpers

The UserHelper class can be used when developers want to create their own interface for handling the HydroID and enabling/disabling the MFA security layer.

Console commands

Command Description
hydro-raindrop:reset-hydro {user} Reset Hydro Raindrop MFA for user.
hydro-raindrop:transfer-hydro {userA} {userB} Transfer Hydro Raindrop MFA from user to another user.
hydro-raindrop:unblock-user {user} Unblock given user which was blocked due too many failed MFA attempts.

Events

Event Payload Description
UserIsBlocked $user Fired after a user has been blocked duu too many failed MFA attempts.
UserLoginIsBlocked $user Fired after a login but before the authentication session is destroyed.
UserMfaSessionStarted $user Fired when MFA session is being started.
SignatureFailed $user Fired when the MFA signature failed i.e. user enters invalid MFA message.
SignatureVerified $user Fired when MFA signature is correct i.e. user enters valid MFA message.
HydroIdAlreadyMapped $user, $hydroId Fired when the HydroID is already mapped to the application by any user.
HydroIdDoesNotExist $user, $hydroId Fired after the HydroID has been sent to the API and the HydroID cannot be found.
HydroIdRegistered $user, $hydroId Fired when HydroID is successfully registered.
HydroIdRegistrationFailed $user, $hydroId Fired when HydroID registration failed after calling the API. API Error.

Further reading

For more info on Hydro or MFA and how it’s changing the world, check out the following:

Looking for a drop-in solution? Hydro Raindrop is also available for the following Content Management Systems:

via Laravel News Links
Add 2FA to your Laravel Application using Hydro Raindrop

The Man Behind the Gun: The Institute of Military Technology Present the Eugene Stoner Stories

The Man Behind the Gun: The Institute of Military Technology Present the Eugene Stoner Stories

Posted in AR-15, Daily News, Rifles by with No Comments
Tags: , , , , , , ,

Reed Knight

Reed Knight talks about Eugene Stoner and shows some of his original design drawings (IMT)

We are all familiar with the AR15 and M16 and you’re almost certainly familiar with the man behind the design, Eugene Morrison Stoner. Well, The Institute of Military Technology has put together a fascinating documentary telling the story of the man and the guns he designed. IMT are in the unique position of having many of Stoner’s prototypes in their collection as well as archival photographs and many of his original design papers and drawings.

IMT

ArmaLite AR-10, serial number 1 (IMT)

Reed Knight, founder and owner of Knight’s Armament Company and the Institute of Military Technology, discusses the importance of Stoner’s work and talks about his time working with the legendary designer. The 30-minute documentary film, shared on IMT’s youtube channel, showcases in 4K some of the extremely rare Stoner prototypes held by the Institute’s reference collection. These include the M8 AR10 prototype, the AR-10 serial number #1, the XAR-15-1 the first AR15 prototype, Armalite M16 serial number #1 and his later guns including the Stoner 63 and AR-18. It’s wonderful to get a look at some of these unique prototypes.

Check out the film below:

In addition to Reed Knight discussing Stoner’s work, the video also features archival footage of Stoner himself discussing his work talking about the development of the 5.56 round and the M16 and its rocky entry into service. Filmed in the late 1980s and early 1990s and held by the Smithsonian the footage is very rarely seen.

IMT m16

Armalite M16, serial number 1 (IMT)

IMT belt fed

Stoner 86/Ares LMG prototype (IMT)

The film ends with a meeting of masters, Stoner and Kalashnikov – two men who shaped small arms design immeasurably with the descendants of their designs still in service, still in production and certain to remain dominant for decades to come. The series is set to continue with Reed Knight promising more stories about the man and a closer look at his designs teasing a plethora of Stoner’s work including the AR7, the Stoner 62/63, the AR-18, the SR-25, the Colt 2000, the Ares Light Machine Gun and the SR-50. It’s fascinating to get a feel for the kind of designer and man Stoner was, I can’t wait for future videos from IMT.

Matthew Moss

Matthew Moss – Assistant Editor.

Matt is a British historian specialising in small arms development and military history. He has written for a variety of publications in both the US and UK he also runs http://bit.ly/2anXcdj, a blog that explores the history, development and use of firearms. Matt is also co-founder of www.armourersbench.com, a new video series on historically significant small arms.

Reach Matt at: matt@thefirearmblog.com

via The Firearm Blog
The Man Behind the Gun: The Institute of Military Technology Present the Eugene Stoner Stories

Band-Aid Fabric Tough Strips

I cook all the meals in my household and have worked in a bunch of manual jobs, from welding to construction, so I have a lot of experience with cuts, abrasions, and burns. After much experience and unwilling experiment, I highly recommend Band-Aid Tough Strips without exception or qualification.

Every other variety I have tried, including the plastic “waterproof” Tough Strips and the regular plastic and fabric Band-Aids, have, in a word, stunk. Why the fabric Tough Strips stay on through sweat and multiple soapings, I don’t know. The adhesive does seem to be of a different sort. But the fact is they do stay on through everyday and not-so-everyday abuse, and no other bandage I’ve tried comes close. Also they’re a little bigger than regular bandages, and the extra bit often makes the difference between not-quite and fully covering a wound. Be sure to apply them to dry skin while trying to avoid getting any antibiotic ointment on the sticky part as that stuff is like adhesive kryptonite.

— Tim Heffernan

[This is a Cool Tools Favorite from 2011]

Band-Aid Fabric Tough Strips ($6+)

Available from Amazon

via Cool Tools
Band-Aid Fabric Tough Strips

“Gun Crime” is a Made Up Word from the Anti-Gun World, Here are the Real Numbers

Opinion

U.S. Murder Concentrations by Local
U.S. Murder Concentrations by Local, IMAGE Crime Prevention Research Center

USA – -(AmmoLand.com)- When we talk about firearms,there is a LOT of misinformation and … outright lies told by the gun grabbing left.

For the left gun control is a means to an end, a way to gain power. Not to save lives. This is one of the reasons their programs fail. They don’t want to stop shootings but use them to retain power.

Here are the facts they choose to ignore. Facts YOU can use.

There are approximately 120,000,000 gun owners in the US. We are the majority of voting adults.

53% of all shooting are by black men under 30. So, 3% of the US population does more than ½ the killing. 83% of all shooting are gang and drug-related.

Only about 7% of all shootings from a rifle or shotgun and less than 40% of that 7% are involve Modern Sporting Rifles.

The U.S. ranks 11th in mass shootings for industrialized nations. 50% of the counties in the US will not have a murder with-in their boundary. 2% of the counties had 53% of all murders in the U.S.

Both Japan and Korea have much higher suicide rates than the US. Both nations virtually ban private gun ownership. 75% of all gun deaths are attributed to suicide. Guns are not the cause, just the means.

An Obama administration study showed guns are used over 500,000 time a year to save a life or prevent crime or assault.

U.S. Numbers

  • Total Number of gun deaths: 38,000
  • Suicides: 28,500
  • Gand and Drug: 7,885
  • Remaining: 1,615

If not for gangs and drugs, the number of murders by guns is below 1,700 in a county of 330,000,000 people. Remove the gangs and drugs, the US is one of the SAFEST countries in the world.

So fellow firearms owners – here are facts – the REAL facts

You’re reading this online, so share it. SHIFT the paradigm. For the left to face the reality that gun ownership is not the cause of violence. It is the lefts failed social programs of welfare that destroys families, and open borders that bring in opioid that destroys lives that are the real cause of gun deaths.

Use these facts, fight for our rights with EDUCATION and the TRUTH!

We have a country to save.

Oh, where did I get these facts? The FBI unified crime report. They’re all there for ANYONE to see.

Also check out the Crime Prevention Research Center.


About Don McDougall

Don McDougall
Don McDougall

Don McDougall is an NRA instructor and member of the Los Padres “Friends of the NRA” committee. If he’s not at the range, you will find him setting the record straight with on gun issues and gun safety on AmmoLand Shooting Sports News.

The post “Gun Crime” is a Made Up Word from the Anti-Gun World, Here are the Real Numbers appeared first on AmmoLand.com.

via AmmoLand.com
“Gun Crime” is a Made Up Word from the Anti-Gun World, Here are the Real Numbers

Searchman – MYSQL Driver For Laravel Scout

laravel-searchman

MySql Driver for Laravel Scout

Requirements

  • Requires Laravel Installed ^5.6
  • Requires Laravel Scout ^7.0

Installation

composer require nwogu\laravel-searchman

Setup

Searchman Provides a Mysql Driver for Laravel Scout, for full text search
with indexing priorities and sql where expressions.

Laravel Scout Documentation

After installing Searchman, you can publish the configuration
using the vendor:publish Artisan command. This command will publish the searchman.php
configuration file to your config directory:

php artisan vendor:publish --provider="Nwogu\SearchMan\Provider\SearchableServiceProvider"

Add the Nwogu\SearchMan\Traits\SearchMan trait to the model you would like to make searchable. This trait uses Laravel’s Scout Searchable and adds extra methods required by the engine:

<?php namespace App; use Illuminate\Database\Eloquent\Model; use Nwogu\SearchMan\Traits\SearchMan; class Meeting extends Model { use SearchMan; } 

As with Scout, you can overide the searchableAs method in your model to change
the default index table name.

Queuing

As at v1.0.1, Searchman has not been effectively tested with queues.

Migrations

Run the command php artisan searchman:make-index {Model} to generate the index
for the specific model.

php artisan searchman:make-index "App\Meeting"

A migration file will be created in laravel’s default base migrations folder.

run the migration with php artisan migrate to publish the migration.

At the point, you can now start indexing your models.

Laravel Scout Documentation

Priority Handlers

Searchman is built around indexing priorities. By default, two priority handlers
are available for use

  • Nwogu\SearchMan\PriorityHandlers\LocationPriorityHandler
  • Nwogu\SearchMan\PriorityHandlers\LongTextPriorityHandler

You can specify which handler to use for your indexing by defining the method
getIndexablePriorities on your indexable model. It should return an array
specifing the column name and the handler.

 public function getIndexablePriorities() { return [ 'minutes' => LongTextPriorityHandler::class, 'email' => LocationPriorityHandler::class ]; } 

By default, the LocationPriorityHandler is used for all indexing. you can
overide this in the searchman config file.
Building your own handlers is easy. Implement the Priority handler Interface and you are good to go.

Searching

Laravel Scout only suports strict where clauses. but with Searchman, you can specify the operation of
your where statements using :.

App\Meeting::search("discussion on health")->where("attendance:>", 10)->get(); 

For more on search, look up the Laravel Scout Documentation

Results

Calling get on your query would return an Eloquent collection of models. However using raw would
return an eloquent collection ordered by priorities.

 {#3017 +"id": 9, +"society_id": 1, +"name": "General Meeting Thursday, 21 Mar, 2019", +"type": "general meeting", +"minute": "<p>tjlkj;km;</p>", +"start_time": "2019-03-18 14:00:00", +"end_time": "2019-03-18 17:00:00", +"presider": 1, +"total_attendance": 1, +"created_at": "2019-03-18 19:16:01", +"updated_at": "2019-03-18 19:16:01", +"meeting_date": "2019-03-21 20:19:00", +"priority": 3.0, +"document_id": 9, } 

via Laravel News Links
Searchman – MYSQL Driver For Laravel Scout

Previewing mail notifications in Laravel just got easier

Previewing mail notifications in Laravel just got easier

Since Laravel 5.8.16 the MailMessage class created by a Mail Notification implements the Renderable interface. This makes previewing it in a browser for testing much simpler. Rather than jumping through the hoops required before you can now do this:

Route::get('mail-preview', function () { $invoice = App\Invoice::find(1); return (new App\Notifications\InvoicePaid($invoice)) ->toMail(null); }); 

This is now much more in line with the ability to preview Mailable classes in the browser.

Thanks to Avraham Appel for making the pull request to make this possible.

via Laravel News Links
Previewing mail notifications in Laravel just got easier

Laravel S3 Tools

Overview

This Laravel package contains additional functionality not currently in Laravel for interfacing with Amazon’s S3 service. In particular, there are methods for dealing with versioned objects within S3. It simply extends the existing core classes to add support for versioning in S3, and is tied into the Storage facade for convenience. It was designed to be a drop-in replacement, and is backwards compatible with the core functionality, so there shouldn’t be any conflicts. I developed this package originally for my own need to deal with versioned objects in S3 and wanted the convenience of Laravel’s Storage facade.

With this package, you can easily:

  • Manage versioned objects stored in S3
    • Get a list of versions for a given object stored in S3
    • Retrieve a specific version of an object stored in S3
    • Delete a specific version of an object stored in S3
  • Set or clear Amazon S3/API option values
  • Execute other Amazon S3/API commands against your objects

Other methods and conveniences may be added in the future, depending largely upon either my own needs, or suggestions from the community. Pull requests, bug reports, etc. are welcome! 🙂

NOTE: Yes, I know that you can make use of the underlying Amazon S3 API package to do these sorts of things. But I wanted the convenience of tying them into the Storage facade, as well as for some potential additional functionality down the road. So, if you’d rather do this:

// Instantiate an Amazon S3 client. $s3 = new S3Client([  'version' => 'latest',  'region' => 'us-west-2' ]);  // Fetch the latest version of a file try {  $s3->putObject([  'Bucket' => 'my-bucket',  'Key' => 'myfile.png',  'VersionId' => 'fiWFsPPFwbvlGh37rB9IaZYkO4pzOgWGz'  ]); } catch (Aws\S3\Exception\S3Exception $e) {  echo "There was an error retrieving the file.\n"; }

… instead of this:

$file = Storage::disk('s3-tools')->getVersion($versionId)->get('myfile.png');

… then that’s on you. Have fun. 🙂

Requirements

This package assumes you have already installed the following packages:

Laravel should already have the league/flysystem package installed, but you may need to install the others. I’ve added them as dependencies to this package, so it should be all automatic for you anyway.

Installation

You can install the package via composer:

composer require incursus/laravel-s3-tools

Once it is installed, you will need to add the service provider, as usual, to your config/app.php file:

 ...  'providers' => [  ...  Incursus\LaravelS3Tools\S3ToolsServiceProvider::class,  ...  ],  ...

Configuration

Environment Variables

AWS Environment Variables

The laravel-s3-tools package makes use of the existing AWS/S3 configuration within Laravel, so if you’ve already configured your app to use S3, you are good to go! Of course, provided you are using the most recent AWS/S3 config statements (these were changed not too long ago in Laravel). To make sure, check your .env file for the following:

AWS_ACCESS_KEY_ID=<YOUR KEY> AWS_SECRET_ACCESS_KEY=<YOUR SECRET> AWS_DEFAULT_REGION=<DEFAULT REGION> AWS_BUCKET=<YOUR BUCKET NAME> 

If you aren’t sure what value to use in AWS_DEFAULT_REGION, check this page for more information (use the value shown in the Region column in the table on that page.

S3 Tools Disk Name

By default, this package will use a disk name of s3-tools. If you’d like to rename it to something else, you can use the S3_TOOLS_DISK_NAME environment variable in your .env file, as show below.

S3_TOOLS_DISK_NAME="diskname" 

Disk Configuration

The laravel-s3-tools package requires that you setup a new disk configuration in your config/filesystems.php file. It’s pretty simple, really. Just copy the entry below and paste it into your config/filesystems.php file. It will automatically look in your .env file for a custom disk name, and if not found, will fall back to the default value of simply s3-tools. This disk name will be the disk you use in the Storage facade whenever you want to utilize the functionality of this package. Th new disk configuration can also be used for normal, non-versioned S3 operations, or you can just use the original ‘s3’ configuration for that. Up to you!

So, your config/filesystems.php file should look something like this:

 <?php  return [   ...   'disks' => [   ...   's3' => [  'driver' => 's3',  'key' => env('AWS_ACCESS_KEY_ID'),  'secret' => env('AWS_SECRET_ACCESS_KEY'),  'region' => env('AWS_DEFAULT_REGION'),  'bucket' => env('AWS_BUCKET'),  'url' => env('AWS_URL'),  ],   // Add this entry  env('S3_TOOLS_DISK_NAME', 's3-tools') => [  'driver' => env('S3_TOOLS_DISK_NAME', 's3-tools'),  'key' => env('AWS_ACCESS_KEY_ID'),  'secret' => env('AWS_SECRET_ACCESS_KEY'),  'region' => env('AWS_DEFAULT_REGION'),  'bucket' => env('AWS_BUCKET'),  'url' => env('AWS_URL'),  ],  ],  ...

Usage

Summary of Methods

This it the TL;DR section. The following are the methods available to you with the laravel-s3-tools package. Each is described in more detail, with examples, below:

Method Name Arguments Description
setOption() $optionName, $optionValue Sets the value of a single AWS/S3 API option
setOptions() $optionArray Sets multiple AWS/S3 API option values
clearOption() $optionName Resets/clears a single AWS/S3 API option
clearOptions() N/A Resets/clears all AWS/S3 API options that you’ve set through either setOption() or setOptions()
getObjectVersions() $objectPath Fetches a list of versions of the specified object stored in S3
getVersion() $versionId Shortcut for setOption('VersionId', $versionString)
has() $objectPath Works the same as the normal has() method in Laravel, but provides support for checking for existence of a specific version of an object.
delete() $objectPath Works the same as the normal delete() method in Laravel, but provides support for deleting a specific version of an object.

Get a list of versions for a given object

A list of available versions of an object (file) stored in S3 can be retrieved and processed as follows. The returned list of versions will appear in reverse-chronological order based on the date last modified. The most recent version (the latest version) will always be the first element (0th) in the returned array.

$versions = Storage::disk('s3-tools')->getVersions('myfile.png');  foreach($versions as $v) {  echo '<li> Version ID: ' . $v['versionId'];  echo '<li> File Size: ' . $v['fileSize'] . ' bytes';  echo '<li> Is Latest Version?: ' . $v['isLatest']; // Will be true or false (boolean)  echo '<li> Date Modified: ' . $v['dateModified'];  echo '<li> ----------------------------------------------'; }

The output from the above code will appear similar to the following:

- Version ID: WX6q0O9qkcAcqld3DidZo2m5z68uGKnn - File Size: 132645 bytes - Is Latest Version?: 1 - Date Modified: 2019-03-21T19:35:29+00:00 ---------------------------------------------- - Version ID: nMw5IAmOPdMK0MR3eXtkSPVQTd18Vucd - File Size: 3631 bytes - Is Latest Version?: - Date Modified: 2019-03-21T19:16:26+00:00 ---------------------------------------------- ... 

Retrieve the latest version of an object

To retrieve the latest version of the a given object, simply use the Storage facade as usual. Here is an example of retrieving the latest version of an image from S3.

// Fetch the latest version of the file from S3 $file = Storage::disk('s3-tools')->get('myfile.png');   // Show the image in the browser return response($file)->header('Content-Type', 'image/png');

Fetch a specific version of an object

However, unlike Laravel, it can also be used to specify a specific version of an object that you wish to retrieve. The versionId field returned by getVersions() can be used to retrieve a specific version of an object from S3:

// Fetch the image from S3 $versionId = 'fiWFsPPFwbvlGh37rB9IaZYkO4pzOgWGz'; $file = Storage::disk('s3-tools')->getVersion($versionId)->get('myfile.png');   // Show the image in the browser return response($file)->header('Content-Type', 'image/png');

Delete the latest version of an Object

Without specifying a specific version, the latest version of an Object will be deleted:

$result = Storage::disk('s3-tools')->delete('some/longer/S3/path/business-plan.pdf');

The above operation will actually not "delete" the file from S3 if versioning is enabled for the bucket. By default, S3 will place a DeleteMarker for that version of the file. However, you are charged a nominal fee by Amazon for DeleteMarker storage. To fully delete a file, and leave no DeleteMarker in its place, you need to delete the specific version of the file as demonstrated below.

Alternatively, you can do the following to help manage your DeleteMarkers in S3:

  • Login to the S3 Console
  • Select your Bucket
  • Open Properties
  • Click Lifecycle
  • Create a rule set to Permanently Delete n days after the object’s creation date

Delete a specific version of an Object

If you specify a versionId, you can delete just that particular version of the object, assuming it exists. This operation will also not leave behind a DeleteMarker – think of it as a "hard delete" operation.

$versionId = 'fiWFsPPFwbvlGh37rB9IaZYkO4pzOgWGz'; $result = Storage::disk('s3-tools')->getVersion($versionId)->delete('some/longer/S3/path/business-plan.pdf');

Setting AWS/S3 API Options

At times, you may need to provide additional options for a given request. The options for each API call are well-documented on Amazon’s API Reference site. As an example, consider this request which does the same thing as the built-in getVersion() method in this package:

$result = Storage::disk('s3-tools')->setOption('VersionId', $versionString)->get('myfile.png');

You can also use the plural version called setOptions() to pass in an array of options:

$options = [  'VersionId' => 'fiWFsPPFwbvlGh37rB9IaZYkO4pzOgWGz',  'IfModifiedSince' => '2 days ago' ];  $result = Storage::disk('s3-tools')->setOptions($options)->delete('myfile.png');

The clearOption() method will reset a specific option, while the clearOptions() method will reset them all. If you experience any weirdness while doing complex operations into and out of S3, it may behoove you call clearOptions() to reset things prior to making certain API calls.

 // Retrieve a specific version of a file $versionId = 'fiWFsPPFwbvlGh37rB9IaZYkO4pzOgWGz'; $file = Storage::disk('s3-tools')->setOption('VersionId', $versionId)->get('myfile.png');  // Clear out ll of our options $file = Storage::disk('s3-tools')->clearOptions();  // or alternatively, just clear the 'VersionId' option //$file = Storage::disk('s3-tools')->clearOption('VersionId');  // Get the latest version of another file ... $file = Storage::disk('s3-tools')->get('myfile.png'); 

Execute Other Amazon S3 API Commands

Using the command() method, you can execute any other API call to S3 as well, and there are a great number of them. However, you will be responsible for not only passing in all of the appropriate options, but also parsing the response. All responses returned via this method are sent back to you in raw format. In some senses, this is a bit extraneous, since you could just use the offical S3 API to execute them, but I’ve included it here just to provide a method of consistency should you decide to use this package for other things.

Consider the following example which does the same thing as the built-in getVersions() method of this package:

$result = Storage::disk('s3-tools')->command('ListObjectVersions', [  'Prefix' => 'some/longer/S3/path/business-plan.pdf' ]);

Here is the same command above, but using a different bucket name:

$result = Storage::disk('s3-tools')->command('ListObjectVersions', [ 'Bucket' => 'MyBucketName', 'Prefix' => 'some/longer/S3/path/business-plan.pdf' ]); 

Here is an example of creating a new S3 bucket. Remember, bucket names in S3 must conform to DNS naming conventions, so:

  • Should not contain uppercase characters
  • Should not contain underscores
  • Should be between 3 and 63 characters long
  • Should not end with a dash
  • Cannot contain two, adjacent periods
  • Cannot contain dashes next to periods (e.g., "my-.bucket.com" and "my.-bucket" are invalid)
$result = Storage::disk('s3-tools')->command('CreateBucket', [ 'Bucket' => 'my-terrific-bucket-name', 'ACL' => 'private' ]); 

Here is a final example for you. Removing multiple objects in a single API call. In this example, we delete the latest version of myfile.png and business-plan.pdf, as well as a specific version of a fictitious spreadsheet.

$result = Storage::disk('s3-tools')->command('DeleteObjects', [ 'Delete' => [ [ 'Key' => 'myfile.png' ], [ 'Key' => 'some/longer/S3/path/business-plan.pdf' ], [ 'Key' => 'some/longer/S3/path/financial-planning-spreadsheet.xlsx', 'VersionId' => 'fiWFsPPFwbvlGh37rB9IaZYkO4pzOgWGz' ] ] ]); 

Notes on Storage::command Usage

  • When using the Storage::command method, the only "option" value that WILL actually default is Bucket … it will default to the value of AWS_BUCKET from your .env file if a bucket name isn’t passed in directly.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email scott@incurs.us instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

via Laravel News Links
Laravel S3 Tools