This trait provides a temporary($relationship) method on your model for you to access relationships without adding them to the list of loaded relationships.
If the relationship is already loaded, it is returned directly. If the relationship is not loaded, it will be loaded, removed from the list of loaded relationships, and then returned.
The function is wrapped in Spatie’s once package to add memoization. This allows the same temporary relationship to be called multiple times without multiple DB queries.
<?php namespaceApp\Models\Traits; traitTemporaryRelationships { /** * Returns the value of the given relationship from the current model. * * If the relation is already loaded, it is returned directly. * * If the relation is not loaded, it is loaded, then removed * from the list of loaded relations, and then returned. * * The function is memoized so accessing the same temporary * relation within a request will only make one query. */ publicfunctiontemporary(string$relation) { return once(function () use ($relation) { if ($this->relationLoaded($relation)) { return$this->{$relation}; } $relationValue = $this->getRelationValue($relation); $this->unsetRelation($relation); return$relationValue; }); } }
// App\Events\NotifiableEvent.php <?php namespaceApp\Events; interfaceNotifiableEvent { /** * Returns the display name of the event. * * @return string */ publicfunctiongetEventName(): string; /** * Returns the description of the event. * * @return string */ publicfunctiongetEventDescription(): string; }
And here’s an example event, that implements the interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// App\Events\CreatedApplicationMember.php <?php namespaceApp\Events; classCreatedApplicationMemberimplementsNotifiableEvent { publicfunctiongetEventName(): string { return'Created Application Member'; } publicfunctiongetEventDescription(): string { return'Fired whenever a new Application Member is added to your Application.'; } // constructor and stuff goes here...
Then in EventServiceProvider, you can listen for the interface instead of the specific event classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php classEventServiceProviderextendsServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected$listen = [ NotifiableEvent::class => [ SendEventNotification::class ], ]; }
Now anytime an event that implements the NotifiableEvent interface is dispatched, the SendEventNotification listener will be called.
If you’re serving any sort of content site with Laravel, you’ve probably looked into setting up caching for your page responses. For this site owenconti.com, I’m letting Cloudflare handle the caching for me. I do this by setting up a Page Rule to “cache everything”:
The above page rule will cache everything that the Laravel app returns. However, Cloudflare does not cache responses that modify cookies. By default, Laravel’s web middleware is setup to handle sessions for you.
Removing Session Middleware
Since this site is purely for anonymous visitors, I’ll never need to use sessions or cookies of any sort. Because of that, I am able to remove all of the session and cookie middlewares from the web middleware group:
wget https://www.php.net/distributions/php-7.4.28.tar.bz2 tar xf php-7.4.28.tar.bz2 cd php-7.4.28 cd ext/gd/ phpize ## Run the actual configure ./configure --with-php-config=/usr/local/php/bin/php-config --with-gd --enable-gd --with-jpeg=/usr/include --with-webp=/usr/include/webp
make && make install ## Copy new gd.so file in place, backup old one cp /usr/lib64/php/modules/gd.so ~/gd.so.bak cp /var/www/php-7.4.28/ext/gd/modules/gd.so /usr/lib64/php/modules/gd.so # Right after, otherwise server goes down. sudo service httpd restart # Check if WebP support is enabled: php -r "print_r( gd_info() );"
The math/rand package provided by the Go Standard Library gives us pseudo-random number generators (PRNG), also called deterministic random bit generators.
As with all pseudo number generators, any number generated through math/rand is not really random by default, as being deterministic it will always print the same value each time.
As an example, try running this code which introduces rand.Intn(n), which returns a random number between 0 and n - 1.
You’ll always see the same sequence every time you run the program. The random number changes inside the program, but every time you run it, you’ll get the same output:
1 2 3 4 5
11 27 17 29 1
This is because by default the seed is always the same, the number 1. To actually get a random number, you need to provide a unique seed for your program. You really want to not forget seeding, and instead properly seed our pseudonumber generator. How?
Use rand.Seed() before calling any math/rand method, passing an int64 value. You just need to seed once in your program, not every time you need a random number. The most used seed is the current time, converted to int64 by UnixNano with rand.Seed(time.Now().UnixNano()):
Remember that due to its sandboxing, the Go Playground always begins with the same time, so this code won’t work as expected. Try it on a real environment, and the numbers that previosly didn’t change, they will now print differently each time you run the program.
Some common examples are listed below for ease of reuse:
Generate a random integer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
package main
import ( "fmt" "math/rand" "time" )
// Returns an int >= min, < max funcrandomInt(min, max int)int { return min + rand.Intn(max-min) }
funcmain() { rand.Seed(time.Now().UnixNano()) fmt.Println(randomInt(1, 11)) //get an int in the 1...10 range }
// Returns an int >= min, < max funcrandomInt(min, max int)int { return min + rand.Intn(max-min) }
// Generate a random string of A-Z chars with len = l funcrandomString(lenint)string { bytes := make([]byte, len) for i := 0; i < len; i++ { bytes[i] = byte(randomInt(65, 90)) } returnstring(bytes) }
// Generate a random string of A-Z chars with len = l funcrandomString(l int)string { bytes := make([]byte, l) for i := 0; i < l; i++ { bytes[i] = pool[rand.Intn(len(pool))] } returnstring(bytes) }
Go also provides a Cryptographically secure pseudorandom number generator (CSPRNG) in the standard library package crypto.rand
So you might question, why should I even use the pseudo-number random generator library provided by math/rand instead? Well, it depends on the use case. math/rand is much faster for applications that don’t need crypto-level or security-related random data generation. crypto.rand is suited for secure and crypto-ready usage, but it’s slower.
What it should be used for? For example, generating passwords, CSRF tokens, session keys, or anything remotely related to security.
It does not rely on the current time, like we did in the previous examples in math/rand, but instead it uses the operating system CSPRNG APIs: the CryptGenRandom API on Windows, and /dev/urandom/ on all the others (Linux, OSX, *nix)
I’m taking a code sample from Matt Silverlock: you can make it more general and create a random bytes generation function
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// GenerateRandomBytes returns securely generated random bytes. // It will return an error if the system's secure random // number generator fails to function correctly, in which // case the caller should not continue. funcGenerateRandomBytes(n int)([]byte, error) { b := make([]byte, n) _, err := rand.Read(b) // Note that err == nil only if we read len(b) bytes. if err != nil { returnnil, err }
return b, nil }
and using this, a random string generation function,
1 2 3 4 5 6 7 8 9
// GenerateRandomString returns a URL-safe, base64 encoded // securely generated random string. // It will return an error if the system's secure random // number generator fails to function correctly, in which // case the caller should not continue. funcGenerateRandomString(s int)(string, error) { b, err := GenerateRandomBytes(s) return base64.URLEncoding.EncodeToString(b), err }
Hi user_a! You've successfully authenticated, but GitHub does not provide shell access. Hi user_b! You've successfully authenticated, but GitHub does not provide shell access.
测试用把仓库提交到第二个 Github 账号
新建一个文件夹,初始化为仓库,创建第一个 commit
1 2 3 4 5 6 7
$ mkdir Test $ cd Test $ git init $ git commit -am "first commit" $ git remote add origin git@user_b-github.com:user_b/testing.git $ git branch -M main $ git push -u origin main
The idiomatic way of handling abnormal conditions in a Go program is using errors. Errors are sufficient for most of the abnormal conditions arising in the program.
But there are some situations where the program cannot continue execution after an abnormal condition. In this case, we use panic to prematurely terminate the program. When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates. This concept will be more clear when we write an example program.
It is possible to regain control of a panicking program using recover which we will discuss later in this tutorial.
panic and recover can be considered similar to try-catch-finally idiom in other languages such as Java except that they are rarely used in Go.
When Should Panic Be Used?
One important factor is that you should avoid panic and recover and use errors where ever possible. Only in cases where the program just cannot continue execution should panic and recover mechanism be used.
There are two valid use cases for panic.
An unrecoverable error where the program cannot simply continue its execution. One example is a web server that fails to bind to the required port. In this case, it’s reasonable to panic as there is nothing else to do if the port binding itself fails.
A programmer error. Let’s say we have a method that accepts a pointer as a parameter and someone calls this method using a nil argument. In this case, we can panic as it’s a programmer error to call a method with nil argument which was expecting a valid pointer.
Panic Example
The signature of the built-in panic function is provided below,
1
funcpanic(interface{})
The argument passed to the panic function will be printed when the program terminates. The use of this will be clear when we write a sample program. So let’s do that right away.
We will start with a contrived example which shows how panic works.
funcfullName(firstName *string, lastName *string) { if firstName == nil { panic("runtime error: first name cannot be nil") } if lastName == nil { panic("runtime error: last name cannot be nil") } fmt.Printf("%s %s\n", *firstName, *lastName) fmt.Println("returned normally from fullName") }
The above is a simple program to print the full name of a person. The fullName function in line no. 7 prints the full name of a person. This function checks whether the firstName and lastName pointers are nil in line nos. 8 and 11 respectively. If it is nil the function calls panic with a corresponding message. This message will be printed when the program terminates.
Running this program will print the following output,
Let’s analyze this output to understand how panic works and how the stack trace is printed when the program panics.
In line no. 19 we assign Elon to firstName. We call fullName function with lastName as nil in line no. 20. Hence the condition in line no. 11 will be satisfied and the program will panic. When panic is encountered, the program execution terminates, the argument passed to the panic function is printed followed by the stack trace. Since the program terminates following the panic function call in line no. 12, the code in line nos. 13, 14, and 15 will not be executed.
This program first prints the message passed to the panic function,
1
panic: runtime error: last name cannot be nil
and then prints the stack trace.
The program panicked in line no. 12 of fullName function and hence,
will be printed first. Then the next item in the stack will be printed. In our case, line no. 20 where the fullName is called is the next item in the stack trace. Hence it is printed next.
Now we have reached the top level function which caused the panic and there are no more levels above, hence there is nothing more to print.
One More Example
Panics can also be caused by errors that happen during the runtime such as trying to access an index that is not present in a slice.
Let’s write a contrived example which creates a panic due to out of bounds slice access.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
package main
import ( "fmt" )
funcslicePanic() { n := []int{5, 7, 4} fmt.Println(n[4]) fmt.Println("normally returned from a") } funcmain() { slicePanic() fmt.Println("normally returned from main") }
In the program above, in line no. 9 we are trying to access n[4] which is an invalid index in the slice. This program will panic with the following output,
1 2 3 4 5 6 7
panic: runtime error: index out of range [4] with length 3
Let’s recollect what panic does. When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates.
In the example above, we did not defer any function calls. If a deferred function call is present, it is executed and then the control returns to its caller.
Let’s modify the example above a little and use a defer statement.
funcfullName(firstName *string, lastName *string) { defer fmt.Println("deferred call in fullName") if firstName == nil { panic("runtime error: first name cannot be nil") } if lastName == nil { panic("runtime error: last name cannot be nil") } fmt.Printf("%s %s\n", *firstName, *lastName) fmt.Println("returned normally from fullName") }
funcmain() { defer fmt.Println("deferred call in main") firstName := "Elon" fullName(&firstName, nil) fmt.Println("returned normally from main") }
The only changes made are the addition of the deferred function calls in line nos. 8 and 20.
This program prints,
1 2 3 4 5 6 7 8 9
deferred call in fullName deferred call in main panic: runtime error: last name cannot be nil
When the program panics in line no. 13, any deferred function calls are first executed and then the control returns to the caller whose deferred calls are executed and so on until the top level caller is reached.
In our case, defer statement in line no. 8 of fullName function is executed first. This prints the following message.
1
deferred call in fullName
And then the control returns to the main function whose deferred calls are executed and hence this prints,
1
deferred call in main
Now the control has reached the top level function and hence the program prints the panic message followed by the stack trace and then terminates.
Recovering from a Panic
recover is a builtin function that is used to regain control of a panicking program.
The signature of recover function is provided below,
1
funcrecover()interface{}
Recover is useful only when called inside deferred functions. Executing a call to recover inside a deferred function stops the panicking sequence by restoring normal execution and retrieves the error message passed to the panic function call. If recover is called outside the deferred function, it will not stop a panicking sequence.
Let’s modify our program and use recover to restore normal execution after a panic.
funcrecoverFullName() { if r := recover(); r!= nil { fmt.Println("recovered from ", r) } }
funcfullName(firstName *string, lastName *string) { defer recoverFullName() if firstName == nil { panic("runtime error: first name cannot be nil") } if lastName == nil { panic("runtime error: last name cannot be nil") } fmt.Printf("%s %s\n", *firstName, *lastName) fmt.Println("returned normally from fullName") }
funcmain() { defer fmt.Println("deferred call in main") firstName := "Elon" fullName(&firstName, nil) fmt.Println("returned normally from main") }
The recoverFullName() function in line no. 7 calls recover() which returns the value passed to panic function call. Here we are just printing the value returned by recover in line no. 9. recoverFullName() is being deferred in line no. 14 of the fullName function.
When fullName panics, the deferred function recoverName() will be called which uses recover() to stop the panicking sequence.
This program will print,
1 2 3
recovered from runtime error: last name cannot be nil returned normally from main deferred call in main
When the program panics in line no. 19, the deferred recoverFullName function is called which in turn calls recover() to regain control of the panicking sequence. The call to recover() in line no. 8 returns the argument passed to panic() and hence it prints,
1
recovered from runtime error: last name cannot be nil
After execution of recover(), the panicking stops and the control returns to the caller, in this case, the main function. The program continues to execute normally from line 29 in main since the panic has been recovered 😃. It prints returned normally from main followed by deferred call in main
Let’s look at one more example where we recover from a panic caused by accessing an invalid index of a slice.