https://assets.amuniversal.com/107b71f0c72101396b35005056a9545d
Thank you for voting.
Hmm. Something went wrong. We will take a look as soon as we can.
Dilbert Daily Strip
Just another WordPress site
https://assets.amuniversal.com/107b71f0c72101396b35005056a9545d
Thank you for voting.
Hmm. Something went wrong. We will take a look as soon as we can.
Dilbert Daily Strip
https://death.andgravity.com/_file/query-builder-how/join-operator.svg
This is the fourth article in a series about
writing an SQL query builder for my feed reader library.
Today, we’ll dive into the code by rewriting it from scratch.
Think of it as part walk-through, part tutorial;
along the way, we’ll talk about:
As you read this, keep in mind that it is linear by necessity.
Development was not linear:
I tried things out, I changed my mind multiple times,
and I rewrote everything at least once.
Even now, there may be equally-good â or better â implementations;
the current one is simply good enough.
Contents
We want a way to build SQL strings that takes care of formatting:
>>> query = Query()
>>> query.SELECT('one').FROM('table')
<builder.Query object at 0x7fc953e60640>
>>> print(query)
SELECT
one
FROM
table
… and allows us to add parts incrementally:
>>> query.SELECT('two').WHERE('condition')
<builder.Query object at 0x7fc953e60640>
>>> print(query)
SELECT
one,
two
FROM
table
WHERE
condition
While not required,
I recommend reading the previous articles
to get a sense of the problem we’re trying to solve,
and the context we’re solving it in.
In short, whatever we build should:
Our solution does not exist in a void;
it exists to be used by my feed reader library.
Notably, we’re not making a general-purpose library with external users
whose needs we’re trying to anticipate;
there’s exactly one user with a pretty well-defined use case,
and strict backwards compatibility is not necessary.
This allows us to make some upfront decisions to help with maintainability:
As mentioned before,
my prototype was based on the idea that
queries can be represented as plain data structures.
Looking at a nicely formatted query,
a natural representation may come to mind:
SELECT
one,
two
FROM
table
WHERE
condition AND
another-condition
See it?
It’s a mapping with a list of strings for each clause:
{
'SELECT': [
'one',
'two',
],
'FROM': [
'table',
],
'WHERE': [
'condition',
'another-condition',
],
}
Let’s use this as our starting model, and build ourselves a query builder 🙂
We start with a class:
|
|
We use a class because most of the time
we don’t want to interact with the underlying data structure,
since it’s more likely to change.
We’re not subclassing dict,
since that would unintentionally expose its methods (and thus, behavior),
and we may need those names for something else.
Also, a class allows us to reduce verbosity:
# we want
query.SELECT('one', 'two').FROM('table')
# not
query['SELECT'].extend(['one', 'two'])
query['FROM'].append('table')
We store various data as class variables
instead of hardcoding it or using module variables
as a cheap way to customize things (more on that later).
Also, using a variable makes it clearer what those things are.
For now, we refrain from any customization in the initializer;
if we need more clauses, we can add them to keywords directly.
We add all the known keywords upfront to get free error checking â
we can rely on data[keyword] raising a KeyError for unknown keywords.
We could use dataclasses,
but of the generated magic methods, we’d only use the __repr__(),
and most of the time it would be too long to be useful.
Next, we add code for adding stuff:
|
|
add() is roughly equivalent to data[keyword]â.extend(args).
The main difference is that we first dedent the arguments and remove trailing whitespace.
This is an upfront decision: we clean everything up and make as many choices when adding things,
so the part that generates output doesn’t have to care about any of that,
and error checking happens as early as possible.
Another difference is that it returns self,
which enables method chaining: queryâ.add(...)â.add(...).
__getattr__() gets called when an attribute does not exist,
and allows us to return something instead of getting the default AttributeError.
What we return is a KEYWORD(*args) callable made on the fly
by wrapping add() in a partial (this is the metaprogramming part);
a closure would be functionally equivalent.1
Requiring the keywords to be uppercase is mostly a stylistic choice,
but it does advantages:
it signals to the reader these are special "methods",
and avoids shadowing dunder methods like __deepcopy__() with no extra code.
We don’t even bother raising AttributeError explicitly,
we just let getattr() do it for us.
We could store the partial on the instance,
which would side-step __getattr__() on subsequent calls,
so we only make one partial per keyword;
we could do it upfront, in the constructor;
we could even generate actual methods,
so there’s only one per keyword per class!
Or we can do nothing â they’re likely premature optimization, and not worth the effort.
I said error checking error checking happens as early as possible;
that’s almost true:
if you look carefully at the code,
you may notice queryâ.ESLECT doesn’t raise an exception
until called, queryâ.ESLECT().
Doing most of the work in add() does some benefits, though:
it allows it to be used with partial,
and it’s an escape hatch for when we want
to use a "keyword" that’s not a Python identifier
(it’ll be useful later).
Finally, we turn what we have back into an SQL string:
|
|
|
|
The main API is str();
this requires almost zero effort to learn,
since it’s the standard way of turning things into strings in Python.
str(query) calls __str__, which delegates the work to the _lines() generator.
We use a generator mainly because it allows writing
yield line instead of rv.append(line),
making for somewhat cleaner code.
A second benefit of a generator is that it’s lazy:
this means we can pass it around
without having to build an intermediary list or string;
for example, to an open file’s writelines() method,
or in yield from in another generator
(e.g. if we allow nested subqueries).
We don’t need any of this here,
but it can be useful when generating a lot of values.
We split the logic for individual clauses into a separate generator, _lines_keyword(),
because we’ll keep adding stuff to it.
(I initially left everything in _lines(),
and refactored when things got too complicated;
no need to do that now.)
Since we’ll want to indent things in the same way in more than one place,
we make it a static "method" using partial.
You may notice we’re not sorting the clauses in any way;
dicts guarantee insertion order in Python 3.6 or newer2,
and we add them to data upfront,
so the order in keywords is maintained.
To make sure we don’t break stuff that’s already working,
let’s add a simple test:
|
|
We’ll keep adding to it at the end of each section,
but since it’s not all that interesting feel free to skip that.
For a minimal solution, we’re done; we have 62 lines / 38 statements.
The code so far:
builder.py,
test_builder.py.
At this point, the WHERE output doesn’t really make sense:
>>> print(Query().WHERE('a', 'b'))
WHERE
a,
b
We fix it by special-casing the separators for a few clauses:
|
|
|
|
We could’ve used defaultdict and gotten rid of default_separator,
but then we’d have to remember that the non-comma separators need a space (' AND');
it’s clearer to put it directly in code.
Also, we could’ve put the special separator on a new line ('one\nâAND two' vs. 'one AND\nâtwo').
While this is recommended by some style guides,
it makes the code a bit more complicated for little benefit,
and (maybe) makes it less obvious that AND is just another separator.
We add WHERE to the test.
|
|
The code so far:
builder.py,
test_builder.py.
One of the requirements is making it possible to implement on top of our builder
scrolling window queries.
For this to happen,
code needs to get the result column names
(the SELECT expressions or their aliases),
so it can use them in the generated WHERE condition.
The output of the following looks OK, but to extract alias programmatically,
we’d need to parse column AS alias:
>>> query = Query().SELECT('column AS alias')
>>> query.data['SELECT'][0]
'column AS alias'
Passing a pair of strings when we want an aliased column seems like an acceptable thing.
Since the column expression might be quite long,
we’ll make the alias the first thing in the pair.
>>> print(Query().SELECT(('alias', 'one'), 'two'))
SELECT
one AS alias,
two
As mentioned earlier, we’ll store the data in cleaned up and standard form,
so the output code doesn’t have to care about that.
A 2-tuple is a decent choice,
but to make the code easier to read,
we’ll depart a tiny bit from plain data structures,
and use a named tuple instead.
|
|
Conveniently, this also gives us a place where to convert the string-or-pair,
in the form of the from_arg() alternate constructor.
We could’ve made it a stand-alone function,
but this makes clearer what’s being returned.
Note that we use an empty string to mean "no alias".
In general, it’s a good idea to distinguish this kind of absence by using None,
since the empty string may be a valid input,
and None can prevent some bugs â e.g. you can’t concatenate None to a string.
Here, an empty string cannot be a valid alias, so we don’t bother.
Using it is just a one-line change to add():
|
|
On output, we have two concerns:
SELECT expr AS column-alias,WITH table-name AS (stmt)We can model this with mostly-empty defaultdicts with per-clause format strings:
|
|
To choose between "has alias" and "doesn’t have alias",
we take advantage of True and False being equal to 1 and 0
(this may be too clever for our own good, but eh).
|
|
We add an aliased expression to the test.
|
|
The code so far:
builder.py,
test_builder.py.
Currently, WITH is still a little broken:
>>> print(Query().WITH(('table-name', 'SELECT 1')))
WITH
table-name AS SELECT 1
Since common table expressions always have the SELECT statement paranthesized,
we’d like to have that out of the box, and be indented properly:
WITH
table-name AS (
SELECT 1
)
A simple way of handling this is to change the WITH format string to
'{alias} AS (\n{indented}\n)',
where indented is the value, but indented.3
This kinda works, but is somewhat limited in usefulness;
for instance, we can’t easily build something like this on top:
Query().FROM(('alias', 'SELECT 1'), is_subquery=True)
Instead, let’s keep refining our model about values,
and indicate something is a subquery using a flag:
|
|
We can then decide if a clause always has subqueries,
and set it accordingly:
|
|
|
|
Using it for output is just an extra if:
|
|
We add WITH to our test.
|
|
The code so far:
builder.py,
test_builder.py.
One clause that’s missing is JOIN.
And it’s important, changing your mind about what you’re selecting from
happens quite often.
JOIN is a bit more complicated,
mostly because it has different forms â JOIN, LEFT JOIN and so on;
SQLite supports at least 10 variations.
I initially handled it by special-casing,
considering any keyword that contained JOIN a separate keyword.
This has a few drawbacks, though;
aside from making the code more complicated,
it reorders some of the tables:
queryâ.JOIN('a')â.LEFT_JOIN('b')â.JOIN('c')
results in JOIN a JOIN b LEFT JOIN c.
A better solution is to continue refining our model.
Take a look at these railroad diagrams for the SELECT statement:
select-core (FROM clause)
You may notice the table-or-subquery followed by comma in FROM
is actually a subset of the table-or-subquery followed by join-operator in join-clause.
That is, for SQLite, a comma is just another join operator.
Put the other way around, a join operator is just another separator.
First, let’s record which join operator a value has:
|
|
|
|
|
|
We could’ve probably just hardcoded this in add()
(if 'JOIN' in keyword: ...),
but doing it like this makes it easier to see at a glance that
"JOIN is a fake FROM".
Using keyword as a separator is relatively straightforward:
|
|
We add a JOIN to the test.
|
|
The code so far:
builder.py,
test_builder.py.
The final adjustment to our model is to support SELECT DISTINCT.
DISTINCT (or ALL) is like a flag that applies to the whole clause;
we’ll model it as such:
|
|
|
|
Since most of the time we’re OK with the default value of flag,
we don’t bother using an instance variable in __init__;
instead, we use a class variable;
setting flag on an instance will then shadow the class variable.
We set the flag based on a known list of keywords for each clause;
like with fake keywords, we pull the flag "parsing" logic into a separate method:
|
|
|
|
|
|
Using the flag for output is again straightforward:
|
|
We add a SELECT DISTINCT to our test.
|
|
The code so far:
builder.py,
test_builder.py.
Since now the simple test isn’t that simple anymore, we split it in two:
one with a really simple query, and one with a really complicated query.
Like so.
|
|
The code so far:
builder.py,
test_builder.py.
One last feature:
I’d like to reuse the cleanup and indent logic to write paranthesized lists.
Good thing __init__ doesn’t do anything yet,
and we can add such conveniences there:
|
|
Using it looks like:
>>> print(Query({'(': ['one', 'two'], ')': ['']}, separators={'(': 'OR'}))
(
one OR
two
)
We could’ve required the data argument to have the same structure as the attribute;
however, that’s quite verbose to write,
and I’d have to instantiate _Things and clean up strings myself;
that’s nor very convenient.
Instead, we take it to mean "here’s some strings to add() for these keywords".
Note that if we ever do want to set the entire data structure,
we still can, with a tiny detour: q = Query(); qâ.dataâ.update(...).
We add a separate test for the fancy __init__.
|
|
We’re done; we have 148 lines / 101 statements.
Throughout this, even with the most basic of tests, coverage did not drop below 96%.
The final version of the code:
builder.py,
test_builder.py.
When talking about trade-offs,
I said we’ll only add features as needed;
this may seem a bit handwavy â
how can I tell adding them won’t make the code explode?
Because I did add them;
that’s what prototyping was for.
But since they weren’t actually used, I removed them.
There’s no point in them rotting away in there.
They’re in git, we can always add them back later.
Here’s how you’d go about implementing a few of them.
Make them flag keywords, to support the OR $ACTION variants.
To make VALUES bake in the parentheses, set its format to ({value}).
That’s to add one values tuple at a time.
To add one column at a time, we could do this:
add()ing INSERT with arbitrary flagsINSERT('column', into='table')add('INSERT INTO table', 'column')parens_keywordssubquery_keywords, but they apply once per keyword, not per valueIt’d look like this:
# first insert sets flag
query.INSERT('one', into='table').VALUES(':one')
# later we just add stuff
query.INSERT('two').VALUES(':two')
Allow setting add(..., is_subquery=True); you’d then do:
query.FROM('subquery', is_subquery=True)
query.FROM('not subquery')
Using Query objects as subqueries
without having to convert them explicitly to strings
would allow changing them after being add()ed.
To do it, we just need to allow _Thing.value to be a Query,
and override its is_subquery based on an isinstance() check.
This one goes a bit meta:
compound(keyword) method,data['COMPOUND'] with the appropriate fake keyword__getattr__ return a compound() partial for compound keywords_lines()That’s it for now. 🙂
Learned something new today? Share this with others, it really helps!
Want more? Get updates via email
or Atom feed.
This is my first planned series, and still a work in progress.
This means you get a say in it.
Email me your questions or comments,
and I’ll do my best to address them in one of the future articles.
I’m sorry. [return]
The guaranteed insertion order was actually added
to the language specification in 3.7,
but in 3.6 both CPython and PyPy had it as an implementation detail. [return]
That’s what I did initially. [return]
Planet Python
https://www.thefirearmblog.com/blog/wp-content/uploads/2021/08/4A2CFFE5-BACD-472E-AD16-B9448EFF192F-180×180.jpeg
If you go through all the various blog sites, there are a ton of articles about various tips and tricks you can take away from concealed carry courses. In the past, I’ve written everything from my favorite carry handguns to accessories and even mindset articles, but I’ve never really talked about the certain tricks you […]
The post Concealed Carry Corner: Things You Learn After Carrying Over Time appeared first on The Firearm Blog.
The Firearm Blog
https://i.ytimg.com/vi/KJAXLf78QPs/maxresdefault.jpgIn this video, we will be looking at how to get data from multiple databases in one project. We will learn to establish connections with multiple databases and switch at runtime.Laravel News Links
https://krayincrm.com/wp-content/uploads/2021/07/krayin_og.png
Execute these commands below, in order
1.
composer create-project krayin/laravel-crm
2.
php artisan krayin-crm:install
On server
Warning: Before going into production mode we recommend you uninstall developer dependencies. In order to do that, run the command below:
composer install --no-dev
Open the specified entry point in your hosts file in your browser or make an entry in hosts file if not done.
On local
Open brower and paste the
http(s)://example.com/admin/login
email:[email protected]
password:admin123
Laravel News Links
https://www.louderwithcrowder.com/media-library/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbWFnZSI6Imh0dHBzOi8vYXNzZXRzLnJibC5tcy8yNzI1NjExMi9vcmlnaW4uanBnIiwiZXhwaXJlc19hdCI6MTYzMjAzMTM3Mn0.hoK4La1Gic-Tc0bfPSPTJ__NAG6BichSlJtOGRyBts4/image.jpg?width=980
Yesterday I saw a meme that read something like "I now know which of my neighbors would’ve turned in Anne Frank to the Nazis." COVID-19 has revealed who can be trusted, and who is a snitch to the authorities. One could argue our C-Nazis readily identify themselves in public. The unidentified man in the video I’m about to drop on you took the Nazi comparison two steps further. He is the level of angry everyone should be.
For the record, I tried to find the raw file for this, but JP’s video will have to suffice.
"Have you been a good little Nazi and gotten the vaccine? Hail Fauci! Hail Fauci! Hail Fauci!"
He then accused the Board of Supervisors in what I believe is San Diego, of violating the Nuremberg Code. That’s a new one. So I looked it up. Here’s article one:
The voluntary consent of the human subject is absolutely essential. This means that the person involved should have legal capacity to give consent; should be so situated as to be able to exercise free power of choice, without the intervention of any element of force, fraud, deceit, duress, overreaching, or other ulterior form of constraint or coercion; and should have sufficient knowledge and comprehension of the elements of the subject matter involved as to enable him to make an understanding and enlightened decision. This latter element requires that before the acceptance of an affirmative decision by the experimental subject there should be made known to him the nature, duration, and purpose of the experiment; the method and means by which it is to be conducted; all inconveniences and hazards reasonably to be expected; and the effects upon his health or person which may possibly come from his participation in the experiment. The duty and responsibility for ascertaining the quality of the consent rests upon each individual who initiates, directs, or engages in the experiment. It is a personal duty and responsibility which may not be delegated to another with impunity.
Huh. Fascinating. No coercion, people who are a part of a medical experiment must give consent, and we must have sufficient knowledge of the subject matter. Because fact-checks are what they are, I need you to decide if we’re in an experiment or not.
Speaking of fact checks, Reuters ran a fact check on the Nuremberg Code being applied to mask mandates and found it false. I wonder what they’d say about vaccine passports.
Regardless, we’re living in dangerous times. Not because of a virus. But of how certain people are using a virus to control all of us. What we need are more people like Mr. Dreadlocks here, willing to scream it so we can all hear the message.
Looking for a fashionable way to support Louder with Crowder? Get your swag at Crowdershop today!
Louder With Crowder
https://i.kinja-img.com/gawker-media/image/upload/c_fill,f_auto,fl_progressive,g_center,h_675,pg_1,q_80,w_1200/d1f6bf2fe69e95776807cd14ec5ecf3f.gif
At this point, Shane Wighton of the YouTube channel Stuff Made Here has gone from being another talented maker sharing their creations on the internet to potentially the next Thomas Edison. Their latest invention, an auto-aiming bow is so accurate it can even shoot a tiny apple off the head of a Lego minifigure.
Previously, Wighton has used their design and engineering expertise to create a custom-shaped backboard that ensures every basketball shot is redirected through the hoop, an updated version that uses object-tracking cameras and motors to reposition the backboard with every shot, and even a baseball bat with a built-in explosive core that could literally blast home runs out of a park. Through science and engineering, Wighton is slowly mastering every sport imaginable, and that now includes archery with a wonderfully over-engineered bow.
Shooting an arrow at a target usually requires relatively simple hardware. Essentially a bent piece of flexible lumber with a string connected to each end. When you pull the string back and release it, the flexed bow returns to its original shape, and energy is transferred to an arrow, sending it flying towards a target. The idea is simple, but mastering the use of a bow so that the arrow actually hits the target can require years of practice… or several weeks of engineering.
The first iteration of the auto-aiming bow consisted of two mechanisms: a hand-held robot that positioned the bow up and down and left or right using a pair of linear axis motors to take care of aiming, and a second robot that would hold and release the drawn string, to take care of the timing. A series of OptiTrack motion capture cameras installed around Wighton’s shop link up with trackable sensors attached to the bow and the target and some custom software to translates what the cameras see to the auto-aiming mechanisms.
The initial results were disappointing with the auto-aiming bow unable to actually accurately hit a target. Wighton eventually realized the type of bow he was using required the arrow to be fired around the bow itself, which introduced slight wobbles in its flight that threw it off target. Archers learn to compensate for these arrow flight deviations over time, but Wighton just threw money at the problem and upgraded to a compound bow with a whisker biscuit that guaranteed the arrow flew straight and true with every shot.
G/O Media may get a commission
The compound bow introduced another problem: the entire rig became too heavy to hold, and the solution to that problem was—you guessed it—more hardware. Hollywood relies on a wearable device called a Steadicam that attaches a heavy film camera to an articulated spring-loaded arm that’s worn by a camera operator allowing them to capture smooth footage even while running. Instead of a heavy camera, Wighton strapped on a Steadicam rig and attached their auto-aiming bow, which from that point on worked almost flawlessly, even tracking and knocking moving targets out of the air.
The auto-aiming bow wasn’t perfect, however. As Wighton points out at the end of the video its ability to compensate for targets farther away—which requires an archer to aim higher to account for the arched trajectory of an arrow—was completely lacking. The contraption isn’t ready for the Olympics just yet, but version two is already in the process of being designed and improved. At some point, we might see Wighton actually splitting arrows just like Robin Hood did in the movies.
Gizmodo
https://laravelnews.imgix.net/images/In0PKsp—Imgur.png?ixlib=php-3.3.1
1<section class="py-24">
2 <div class="grid md:grid-cols-[2fr,3fr] gap-6 md:gap-12 mt-6">
3 <aside>
4 <h2 class="text-xl font-semibold tracking-tight">Personal</h2>
5
6 <p class="mt-1 text-gray-500">
7 Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quibusdam ducimus enim.
8 </p>
9 </aside>
10
11 <form
12 class="block p-2 space-y-2 bg-white shadow rounded-xl"
13 action=""
14 >
15 <div class="grid grid-cols-2 gap-6 px-4 py-4">
16 <div class="col-span-2 space-y-2 md:col-span-1">
17 <label
18 class="inline-block text-sm font-medium text-gray-700"
19 for="first_name"
20 >First name</label>
21
22 <input
23 class="block w-full h-10 transition duration-75 border-gray-300 rounded-lg shadow-sm focus:ring-1 focus:ring-inset focus:ring-blue-600 focus:border-blue-600"
24 id="first_name"
25 type="text"
26 >
27 </div>
28
29 <div class="col-span-2 space-y-2 md:col-span-1">
30 <label
31 class="inline-block text-sm font-medium text-gray-700"
32 for="last_name"
33 >Last name</label>
34
35 <input
36 class="block w-full h-10 transition duration-75 border-gray-300 rounded-lg shadow-sm focus:ring-1 focus:ring-inset focus:ring-blue-600 focus:border-blue-600"
37 id="last_name"
38 type="text"
39 >
40 </div>
41
42 <div class="col-span-2 space-y-2">
43 <label
44 class="inline-block text-sm font-medium text-gray-700"
45 for="email"
46 >Email address</label>
47
48 <input
49 class="block w-full h-10 transition duration-75 border-gray-300 rounded-lg shadow-sm focus:ring-1 focus:ring-inset focus:ring-blue-600 focus:border-blue-600"
50 id="email"
51 type="email"
52 >
53 </div>
54
55 <div class="col-span-2 space-y-2">
56 <label
57 class="inline-block text-sm font-medium text-gray-700"
58 for="about_me"
59 >About me</label>
60
61 <textarea
62 class="block w-full transition duration-75 border-gray-300 rounded-lg shadow-sm focus:border-blue-600 focus:ring-1 focus:ring-inset focus:ring-blue-600"
63 id="about_me"
64 ></textarea>
65 </div>
66 </div>
67
68 <div class="border-t"></div>
69
70 <footer class="flex items-center justify-end px-4 py-2 space-x-4">
71 <button
72 class="inline-flex items-center justify-center h-8 px-3 text-sm font-semibold tracking-tight text-white transition bg-blue-600 rounded-lg shadow hover:bg-blue-500 focus:bg-blue-700 focus:outline-none focus:ring-offset-2 focus:ring-offset-blue-700 focus:ring-2 focus:ring-white focus:ring-inset"
73 type="submit"
74 >Save details</button>
75 </footer>
76 </form>
77 </div>
78</section>
Laravel News
https://1.bp.blogspot.com/-p7A8jY1CTmM/YRmUbCbyBGI/AAAAAAAAtdk/D6JxUBAcdvwuuPlzTh66POWeYpFucrYCACPcBGAsYHg/
Harvested around the Web over the past week. There are not as many as usual, but that’s because fewer of the memes I found made me laugh! It seems comedy is sometimes sparse. At any rate, click any image for a larger view.
More next week.
Peter
Bayou Renaissance Man
https://ralphjsmit.com/wp-content/uploads/2021/08/How-to-use-Browsersync-with-Laravel-Valet-2021.jpg
If you’re a developer in the PHP world, it’s likely that you’ve come across Laravel Valet. Laravel Valet is a very easy way to spin up multiple local development environments, by linking a folder name to a {folderName}.test domain.
Browsersync is a tool to make developing easier. It offers handy tools, of which the most important is automatic browser reloading on file change. Unfortunately Browsersync doesn’t work out-of-the-box with Laravel Valet, but luckily it requires only a little configuration.
There are roughly two ways to install Browsersync: install it via Laravel Mix in a Laravel project (or in an other PHP-project if you required Laravel Mix as a package) or install Browsersync without Laravel Mix, but with npm.
A very handy feature of Laravel Mix is that it supports Browsersync straight out-of-the-box. Open up your webpack.mix.js file, paste the below code in and edit the first line, so that it contains the correct url.
const domain = 'yourdomain.test'; // <= EDIT THIS
const homedir = require('os').homedir();
// The mix script:
mix.browserSync({
proxy: 'https://' + domain,
host: domain,
open: 'external',
https: {
key: homedir + '/.config/valet/Certificates/' + domain + '.key',
cert: homedir + '/.config/valet/Certificates/' + domain + '.crt'
},
notify: true, //Enable or disable notifications
})
This setup assumes an https:// configuration, so you might need to run the following command in order to create an SSL certificate and allow to run the website on https://:
valet secure
To start using Browsersync, just run npm run watch and your browser will automatically open the following url: https://yourdomain.test:3000.
npm run watch
As long as the above command keeps running, your browser will automatically refresh when it detects a file change.
The other way to install Browsersync is without Laravel Mix. This means that it can be installed in almost any project. For example, you could use it to reload your local WordPress website when you make a change to a theme file.
First, you need to install Browsersync. Installation is global and done via npm.
npm install -g browser-sync
Next, navigate to the folder with your website files. When using Laravel Valet, this is the folder that is linked to the domain you visit in your browser, like so: {folderName}.test.
Run the following command to create a configuration file. This will create a bs-config.js configuration file in your directory.
browser-sync init
Open the Browsersync configuration file and add the following code at the top of the file (below the first comment block):
const domain = 'yourdomain.test'; // <= EDIT THIS
const homedir = require('os').homedir();
Next, make sure that the following lines look like this:
"files": "**/*", //Change this line. It means that it watches for file changes in this everywhere.
"proxy": 'https://' + domain, //Change this line
// Add these lines:
"https": {
"key": homedir + '/.config/valet/Certificates/' + domain + '.key',
"cert": homedir + '/.config/valet/Certificates/' + domain + '.crt'
},
"host": domain, // Change this line
Again, this setup assumes an https:// configuration, so you might need to run the following command in order to create an SSL certificate and allow to run the website on https://:
valet secure
And.. you’re done!🎉 Now, run the following command and enjoy✨ Your browser will automatically open after running this command and will automatically reload when you a file is changed. Pure magix
browser-sync start --config bs-config.js
As you can see, installing Browsersync with Laravel Valet is not difficult: you just need the right configuration. Once properly installed, it can save you hours of time.
And did you know that there are many more options to configure, like disabling the Browsersync notification every time the page reloads? Make sure to check out all the available configuration options on the Browsersync website.
Laravel News Links