John's Technical Blog
Setting Defaults in Laravel Eloquent Models: 5 Powerful Techniques
2023-08-12
Introduction
When working with Laravel Eloquent Models, setting default values for various attributes can be an essential requirement. In this post, we'll explore five different ways to set default values in a Laravel 10 Eloquent Model, each with unique characteristics and use cases.
- Use of the
$attributes
Array
The simplest and most straightforward way to set default values for your Eloquent model's attributes. Only accepts scalar defaults and applies only to the initial object. - Using Accessors
Allows considering other attributes of the Model and applies dynamically throughout the lifecycle of the Model. It enables manipulation of attribute values dynamically. - Using Database Defaults
Best for attributes that have little to do with data retrieved by the application, like timestamps and IDs. It may also be used as a last resort in some cases. - Use of Protected
static function boot()
Allows using a method or service to set a default value at initialization. Useful when scalar defaults are not sufficient. - Using
CastsAttributes
Casting allows converting attribute values and setting defaults through custom casting. Requires that the attribute already be set when retrieving and may require setting an initial value (typically null) in the$attributes
array.
Example: Product Model
Let's create a Product
model that utilizes all the mentioned techniques on different fields:
use Illuminate\Database\Eloquent\CastsAttributes;
class Product extends Model
{
protected $attributes = [
'status' => 'active',
'metadata' => null, // Initial value for custom cast
];
protected $casts = [
'metadata' => MetadataCast::class, // Custom cast for metadata
];
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->slug = Str::slug($model->name);
});
}
public function getPriceAttribute($value)
{
// Apply a discount or format the price based on other attributes
return $value * 0.9; // Example: 10% discount
}
public function getStatusAttribute($value)
{
return $value ?: 'active';
}
}
class MetadataCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return json_decode($value) ?: ['default_key' => 'default_value'];
}
public function set($model, $key, $value, $attributes)
{
return json_encode($value);
}
}
// Migration example for database defaults
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug');
$table->decimal('price');
$table->string('status')->default('active');
$table->timestamps(); // created_at will be set by default
});
In this example, different attributes of a product are handled using different techniques, demonstrating the flexibility and power of Laravel's Eloquent.
Conclusion
Setting default values in Laravel Eloquent Models can be achieved in various ways, each with its own strengths and use cases. From the simplest $attributes
array to more complex boot methods and custom casting, Laravel provides a rich set of tools for managing default values. The provided example illustrates how these can be combined in a cohesive manner to create a robust and flexible model.
I hope this has been helpful in understanding the different techniques available. If you have any questions or need further clarification on any of the methods discussed, please feel free to leave a comment below. Additionally, if you have discovered or utilized other ways to set default values in Laravel that were not covered in this post, we encourage you to share them with the community. Your insights and contributions can help others in their development journey.
Setting Defaults in Laravel Eloquent Models: 5 Powerful Techniques
2023-08-12
Introduction
When working with Laravel Eloquent Models, setting default values for various attributes can be an essential requirement. In this post, we'll explore five different ways to set default values in a Laravel 10 Eloquent Model, each with unique characteristics and use cases.
- Use of the
$attributes
Array
The simplest and most straightforward way to set default values for your Eloquent model's attributes. Only accepts scalar defaults and applies only to the initial object. - Using Accessors
Allows considering other attributes of the Model and applies dynamically throughout the lifecycle of the Model. It enables manipulation of attribute values dynamically. - Using Database Defaults
Best for attributes that have little to do with data retrieved by the application, like timestamps and IDs. It may also be used as a last resort in some cases. - Use of Protected
static function boot()
Allows using a method or service to set a default value at initialization. Useful when scalar defaults are not sufficient. - Using
CastsAttributes
Casting allows converting attribute values and setting defaults through custom casting. Requires that the attribute already be set when retrieving and may require setting an initial value (typically null) in the$attributes
array.
Example: Product Model
Let's create a Product
model that utilizes all the mentioned techniques on different fields:
use Illuminate\Database\Eloquent\CastsAttributes;
class Product extends Model
{
protected $attributes = [
'status' => 'active',
'metadata' => null, // Initial value for custom cast
];
protected $casts = [
'metadata' => MetadataCast::class, // Custom cast for metadata
];
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->slug = Str::slug($model->name);
});
}
public function getPriceAttribute($value)
{
// Apply a discount or format the price based on other attributes
return $value * 0.9; // Example: 10% discount
}
public function getStatusAttribute($value)
{
return $value ?: 'active';
}
}
class MetadataCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return json_decode($value) ?: ['default_key' => 'default_value'];
}
public function set($model, $key, $value, $attributes)
{
return json_encode($value);
}
}
// Migration example for database defaults
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug');
$table->decimal('price');
$table->string('status')->default('active');
$table->timestamps(); // created_at will be set by default
});
In this example, different attributes of a product are handled using different techniques, demonstrating the flexibility and power of Laravel's Eloquent.
Conclusion
Setting default values in Laravel Eloquent Models can be achieved in various ways, each with its own strengths and use cases. From the simplest $attributes
array to more complex boot methods and custom casting, Laravel provides a rich set of tools for managing default values. The provided example illustrates how these can be combined in a cohesive manner to create a robust and flexible model.
I hope this has been helpful in understanding the different techniques available. If you have any questions or need further clarification on any of the methods discussed, please feel free to leave a comment below. Additionally, if you have discovered or utilized other ways to set default values in Laravel that were not covered in this post, we encourage you to share them with the community. Your insights and contributions can help others in their development journey.
Dynamically Resizing Cross-Domain IFrames
2023-08-07
In the age of embedded content and third-party integrations, iframes have become an essential tool for web developers. They allow us to embed content from one site into another seamlessly. However, one common challenge is resizing the iframe to fit its content, especially when dealing with cross-domain iframes, and framed content that should change the size of the page over time.
Note that to use this technique, both the parent and the third-party child web site must have code to communicate with each other. All modern browsers enforce this.
Step 1: Embed the Iframe
<iframe id="myIframe" src="https://example.com/iframe-content" width="420" height="1024" style="width:100%;border:0;overflow:hidden;" scrolling="no" allowfullscreen="allowfullscreen"></iframe>
Note that the scroll bar is hidden in this iframe to prevent it flashing when the internal content changes size.
Step 2: Set Up the Parent Window
<script>
window.addEventListener('message', function(event) { if (event.origin !== 'https://example.com') return; var iframe = document.getElementById('myIframe'); iframe.style.height = event.data.height + 'px'; }, false);
</script>
Step 3: Implement the Iframe Content
<script>
window.addEventListener("DOMContentLoaded", function () { setInterval(function () { var height = document.body.scrollHeight; window.parent.postMessage({height: height}, document.referrer); }, 250); });
</script>
Note that this script posts an update every 1/4 second to handle dynamic page size changes in a responsive manner at a reasonable rate.
Conclusion
Dynamically resizing cross-domain iframes is a common challenge that can be effectively addressed using the postMessage method. By combining this method with some simple JavaScript code, we can create a responsive and user-friendly experience, allowing the iframe to adjust its height based on its content.
This approach ensures a seamless integration of third-party content and enhances the overall usability of the website. It demonstrates the flexibility and power of modern web development techniques, providing a robust solution for one of the more nuanced challenges in front-end development.
Dynamically Resizing Cross-Domain IFrames
2023-08-07
In the age of embedded content and third-party integrations, iframes have become an essential tool for web developers. They allow us to embed content from one site into another seamlessly. However, one common challenge is resizing the iframe to fit its content, especially when dealing with cross-domain iframes, and framed content that should change the size of the page over time.
Note that to use this technique, both the parent and the third-party child web site must have code to communicate with each other. All modern browsers enforce this.
Step 1: Embed the Iframe
<iframe id="myIframe" src="https://example.com/iframe-content" width="420" height="1024" style="width:100%;border:0;overflow:hidden;" scrolling="no" allowfullscreen="allowfullscreen"></iframe>
Note that the scroll bar is hidden in this iframe to prevent it flashing when the internal content changes size.
Step 2: Set Up the Parent Window
<script>
window.addEventListener('message', function(event) { if (event.origin !== 'https://example.com') return; var iframe = document.getElementById('myIframe'); iframe.style.height = event.data.height + 'px'; }, false);
</script>
Step 3: Implement the Iframe Content
<script>
window.addEventListener("DOMContentLoaded", function () { setInterval(function () { var height = document.body.scrollHeight; window.parent.postMessage({height: height}, document.referrer); }, 250); });
</script>
Note that this script posts an update every 1/4 second to handle dynamic page size changes in a responsive manner at a reasonable rate.
Conclusion
Dynamically resizing cross-domain iframes is a common challenge that can be effectively addressed using the postMessage method. By combining this method with some simple JavaScript code, we can create a responsive and user-friendly experience, allowing the iframe to adjust its height based on its content.
This approach ensures a seamless integration of third-party content and enhances the overall usability of the website. It demonstrates the flexibility and power of modern web development techniques, providing a robust solution for one of the more nuanced challenges in front-end development.
Dynamically Registering Service Classes in Laravel 10
2023-08-03
Introduction
In the process of migrating a proprietary PHP application to the Laravel 10 framework, I faced an interesting challenge. The application I was working on had 44 service classes. In Laravel, service classes are typically registered manually in the AppServiceProvider.php file. However, the thought of manually registering all these services seemed daunting and inefficient. Plus, it would be a hassle to update the AppServiceProvider.php every time a service is added, renamed, or removed. Therefore, I decided to find a way to register these service classes dynamically. This post outlines the solution I came up with.
Problem Statement
When migrating a large PHP application into Laravel, manually registering a large number of service classes is not only tedious but also prone to errors. Moreover, it's not scalable: every time we add, rename, or remove a service, we need to manually update the AppServiceProvider.php file. We need a solution that allows us to dynamically register all service classes in the application and automatically adjust when changes are made.
Solution
The solution I came up with involves using Laravel's File::allFiles() method to get all PHP files in the service classes directory and then dynamically register these classes in the AppServiceProvider.php file. The service classes are registered using Laravel's service container, which allows Laravel to automatically resolve these classes when they're needed in other parts of the application.
Here is a code snippet used to find all the classes in any package. I happen to have it in a class called ClassUtil
:
/**
* This method is used to recursively retrieve all the classes within a package that have been configured in the Composer autoloader.
*
* @param string $package The package name
*
* @return string[] The classes
*/
public function getPackageClasses(string $package): array
{
/* Get all PHP files in the package directory */
try {
$basePath = base_path($package);
$files = File::allFiles($basePath);
} catch (Exception) {
/* Catches the case where the default Laravel "App" package resides in the "app" folder and the server is case-sensitive. */
$basePath = base_path(lcfirst($package));
$files = File::allFiles($basePath);
}
error_log($basePath);
$rootPath = str_replace(str_replace('/', DIRECTORY_SEPARATOR, $package), '', $basePath);
$classes = [];
foreach ($files as $file) {
/* Convert the file path to a namespaced class name */
$class = str_replace(
['/', '.php'],
['\\', ''],
Str::after($file->getRealPath(), $rootPath)
);
/* Check if the class exists and is not abstract */
if (class_exists($class) && !(new ReflectionClass($class))->isAbstract()) {
$classes[] = $class;
}
}
return $classes;
}
And this is how I use it in AppServiceProvider.php
:
/**
* Register any application services.
*
* @return void
*/
public function register(): void
{
parent::register();
foreach ((new ClassUtil())->getPackageClasses('App/Services') as $class) {
$this->app->singleton($class, function () use ($class) {
return new $class();
});
}
}
Benefits
This technique has several benefits:
- Simplicity: It saves us the trouble of manually registering each service class.
- Scalability: It allows us to easily add, rename, or remove services without having to manually update the AppServiceProvider.php file.
- Flexibility: It provides a flexible way of managing service classes, especially in large applications.
Conclusion
Dynamically registering service classes in Laravel is a handy trick that can save you a lot of time and effort, especially when dealing with large applications. I hope this post helps you in your Laravel development journey. If you have any questions or comments, feel free to leave them below!
Dynamically Registering Service Classes in Laravel 10
2023-08-03
Introduction
In the process of migrating a proprietary PHP application to the Laravel 10 framework, I faced an interesting challenge. The application I was working on had 44 service classes. In Laravel, service classes are typically registered manually in the AppServiceProvider.php file. However, the thought of manually registering all these services seemed daunting and inefficient. Plus, it would be a hassle to update the AppServiceProvider.php every time a service is added, renamed, or removed. Therefore, I decided to find a way to register these service classes dynamically. This post outlines the solution I came up with.
Problem Statement
When migrating a large PHP application into Laravel, manually registering a large number of service classes is not only tedious but also prone to errors. Moreover, it's not scalable: every time we add, rename, or remove a service, we need to manually update the AppServiceProvider.php file. We need a solution that allows us to dynamically register all service classes in the application and automatically adjust when changes are made.
Solution
The solution I came up with involves using Laravel's File::allFiles() method to get all PHP files in the service classes directory and then dynamically register these classes in the AppServiceProvider.php file. The service classes are registered using Laravel's service container, which allows Laravel to automatically resolve these classes when they're needed in other parts of the application.
Here is a code snippet used to find all the classes in any package. I happen to have it in a class called ClassUtil
:
/**
* This method is used to recursively retrieve all the classes within a package that have been configured in the Composer autoloader.
*
* @param string $package The package name
*
* @return string[] The classes
*/
public function getPackageClasses(string $package): array
{
/* Get all PHP files in the package directory */
try {
$basePath = base_path($package);
$files = File::allFiles($basePath);
} catch (Exception) {
/* Catches the case where the default Laravel "App" package resides in the "app" folder and the server is case-sensitive. */
$basePath = base_path(lcfirst($package));
$files = File::allFiles($basePath);
}
error_log($basePath);
$rootPath = str_replace(str_replace('/', DIRECTORY_SEPARATOR, $package), '', $basePath);
$classes = [];
foreach ($files as $file) {
/* Convert the file path to a namespaced class name */
$class = str_replace(
['/', '.php'],
['\\', ''],
Str::after($file->getRealPath(), $rootPath)
);
/* Check if the class exists and is not abstract */
if (class_exists($class) && !(new ReflectionClass($class))->isAbstract()) {
$classes[] = $class;
}
}
return $classes;
}
And this is how I use it in AppServiceProvider.php
:
/**
* Register any application services.
*
* @return void
*/
public function register(): void
{
parent::register();
foreach ((new ClassUtil())->getPackageClasses('App/Services') as $class) {
$this->app->singleton($class, function () use ($class) {
return new $class();
});
}
}
Benefits
This technique has several benefits:
- Simplicity: It saves us the trouble of manually registering each service class.
- Scalability: It allows us to easily add, rename, or remove services without having to manually update the AppServiceProvider.php file.
- Flexibility: It provides a flexible way of managing service classes, especially in large applications.
Conclusion
Dynamically registering service classes in Laravel is a handy trick that can save you a lot of time and effort, especially when dealing with large applications. I hope this post helps you in your Laravel development journey. If you have any questions or comments, feel free to leave them below!
Creating and Nesting SimpleXMLElement Objects in PHP
2023-06-15
Introduction
When working with XML data in PHP, the SimpleXMLElement
class provides a convenient way to create and manipulate XML structures. One common requirement is to create a SimpleXMLElement
and add it as a child to another SimpleXMLElement
. In this blog post, we'll explore the correct solution to achieve this.
Creating the Parent and Child Elements
/* Create the parent SimpleXMLElement */
$parent = new SimpleXMLElement('<parent></parent>');
/* Create the child SimpleXMLElement */
$child = new SimpleXMLElement('<child></child>');
Adding Content to the Child Element
$child->addChild('name', 'John');
$child->addChild('age', 25);
Importing and Nesting the Child Element
Now comes the crucial part: importing and nesting the child element within the parent element. We'll use the dom_import_simplexml()
function to convert the SimpleXMLElement
objects to DOMNode
objects. Here's how it's done:
$domChild = dom_import_simplexml($child);
$domParent = dom_import_simplexml($parent);
$domChild = $domParent->ownerDocument->importNode($domChild, true);
$domParent->appendChild($domChild);
In the above code, we import the child element into the parent element's document using the importNode()
method. The second argument true
indicates that we want to import the entire subtree of the child node.
Printing the Final XML Structure
To see the final XML structure, we can call the asXML()
method on the parent element:
echo $parent->asXML();
This will output the complete XML structure, including the parent and nested child elements.
The xmlAdopt()
Method
To simplify the process of adopting a child element into a parent element, we can use a helper method called xmlAdopt()
:
/**
* Adopt a SimpleXMLElement into another SimpleXMLElement.
*
* @param SimpleXMLElement $parent the parent element
* @param SimpleXMLElement $child the child element to add to the parent
*/
private function xmlAdopt(SimpleXMLElement $parent, SimpleXMLElement $child): void
{
$domChild = dom_import_simplexml($child);
$domParent = dom_import_simplexml($parent);
$domChild = $domParent->ownerDocument->importNode($domChild, true);
$domParent->appendChild($domChild);
}
Using the xmlAdopt()
Method:
$this->xmlAdopt($parent, $child);
Conclusion:
Creating and nesting SimpleXMLElement
objects in PHP requires careful handling
Creating and Nesting SimpleXMLElement Objects in PHP
2023-06-15
Introduction
When working with XML data in PHP, the SimpleXMLElement
class provides a convenient way to create and manipulate XML structures. One common requirement is to create a SimpleXMLElement
and add it as a child to another SimpleXMLElement
. In this blog post, we'll explore the correct solution to achieve this.
Creating the Parent and Child Elements
/* Create the parent SimpleXMLElement */
$parent = new SimpleXMLElement('<parent></parent>');
/* Create the child SimpleXMLElement */
$child = new SimpleXMLElement('<child></child>');
Adding Content to the Child Element
$child->addChild('name', 'John');
$child->addChild('age', 25);
Importing and Nesting the Child Element
Now comes the crucial part: importing and nesting the child element within the parent element. We'll use the dom_import_simplexml()
function to convert the SimpleXMLElement
objects to DOMNode
objects. Here's how it's done:
$domChild = dom_import_simplexml($child);
$domParent = dom_import_simplexml($parent);
$domChild = $domParent->ownerDocument->importNode($domChild, true);
$domParent->appendChild($domChild);
In the above code, we import the child element into the parent element's document using the importNode()
method. The second argument true
indicates that we want to import the entire subtree of the child node.
Printing the Final XML Structure
To see the final XML structure, we can call the asXML()
method on the parent element:
echo $parent->asXML();
This will output the complete XML structure, including the parent and nested child elements.
The xmlAdopt()
Method
To simplify the process of adopting a child element into a parent element, we can use a helper method called xmlAdopt()
:
/**
* Adopt a SimpleXMLElement into another SimpleXMLElement.
*
* @param SimpleXMLElement $parent the parent element
* @param SimpleXMLElement $child the child element to add to the parent
*/
private function xmlAdopt(SimpleXMLElement $parent, SimpleXMLElement $child): void
{
$domChild = dom_import_simplexml($child);
$domParent = dom_import_simplexml($parent);
$domChild = $domParent->ownerDocument->importNode($domChild, true);
$domParent->appendChild($domChild);
}
Using the xmlAdopt()
Method:
$this->xmlAdopt($parent, $child);
Conclusion:
Creating and nesting SimpleXMLElement
objects in PHP requires careful handling
MySQL AES Encryption in Ruby
2018-05-14
MySQL AES Encryption in Ruby
2018-05-14