Thursday, 19 September 2024

DTOs (Data Transfer Objects) in Laravel

Data Transfer Objects (DTOs) are simple, immutable objects used to transfer data between different layers or parts of an application. They serve as containers that hold data but do not contain any business logic or behavior. DTOs are especially useful in applications with layered architectures, microservices, or when communicating over a network, such as in web APIs.

Key Characteristics of DTOs:

  1. Lightweight and Simple:

    • No Business Logic: DTOs contain only fields (properties) and getter/setter methods. They do not include methods that implement business logic.
    • Immutable (Preferably): Once created, the data within a DTO should not change. This immutability enhances thread safety and predictability.
  2. Purpose-Built:

    • Specific Use Cases: DTOs are tailored to the specific data transfer needs between layers or services. They include only the necessary data required for a particular operation.
    • Not Tied to Database Models: They differ from entities or models that map directly to database tables. DTOs are decoupled from the persistence layer.
  3. Serialization-Friendly:

    • Ease of Transmission: DTOs are designed to be easily serialized and deserialized when transferring data over a network or between processes.

Benefits of Using DTOs:

  1. Performance Improvement:

    • Reduced Data Transfer: By including only necessary data, DTOs minimize the amount of data sent over the network or between layers, leading to better performance.
    • Optimized Payloads: Smaller payloads result in faster transmission times and reduced resource consumption.
  2. Enhanced Security:

    • Controlled Exposure: DTOs expose only the data that is safe and necessary, preventing sensitive internal data from being inadvertently shared.
    • Data Validation: They can enforce data validation rules, ensuring that only valid data is accepted and processed.
  3. Loose Coupling:

    • Decoupling Layers: DTOs help in separating concerns between different layers (e.g., presentation, business logic, data access), promoting a cleaner architecture.
    • Flexibility in Changes: Changes in one layer (like the database schema) do not necessarily impact other layers, as DTOs act as a buffer.
  4. Improved Maintainability:

    • Clear Contracts: DTOs define clear data contracts between services or layers, making the system easier to understand and maintain.
    • Testability: They simplify unit testing by providing straightforward data structures without complex dependencies.

Usage Scenarios:

  1. Web APIs and Microservices:

    • Data Exchange: When exposing RESTful APIs, DTOs define the request and response bodies, ensuring clients receive only the necessary data.
    • Versioning: DTOs can help manage API versions by providing different DTOs for different versions.
  2. Layered Architectures:

    • Between Layers: DTOs transfer data from the data access layer to the business logic layer and then to the presentation layer.
    • Mapping Entities to DTOs: Tools or manual mapping convert entities (database models) to DTOs before sending data to the upper layers.
  3. Distributed Systems:

    • Inter-Process Communication: In systems where components communicate over a network, DTOs serialize data into formats like JSON or XML.

Relation to SOLID Principles:

  1. Single Responsibility Principle (SRP):

    • Focused Purpose: DTOs have a single responsibility: carrying data. This adherence to SRP makes them easier to manage and reduces complexity.
  2. Open/Closed Principle (OCP):

    • Extensibility: New DTOs can be created for new requirements without modifying existing ones, keeping the system open for extension but closed for modification.
  3. Interface Segregation Principle (ISP):

    • Specific Interfaces: DTOs promote the use of specific interfaces for data transfer, avoiding large, generalized interfaces that include unnecessary data.
  4. Dependency Inversion Principle (DIP):

    • Abstraction Over Implementation: Higher-level modules depend on abstractions (DTO interfaces), not concrete implementations, facilitating loose coupling.

Best Practices:

  1. Keep DTOs Simple:

    • No Logic: Avoid adding methods that perform logic or manipulate other objects.
    • Use Plain Data Structures: Stick to basic data types and collections.
  2. Validation:

    • Data Integrity: Validate data before populating DTOs or within the service layer before processing.
  3. Mapping Tools:

    • Automate Mapping: Use libraries like AutoMapper (in .NET) or custom mappers to convert between entities and DTOs efficiently.
  4. Consistency:

    • Naming Conventions: Use clear and consistent naming for DTOs, often suffixing with "DTO" (e.g., UserDTO).
  5. Documentation:

    • API Contracts: Document DTOs thoroughly, especially when used in public APIs, to ensure consumers understand the data structures.

Example in Context:

Suppose we have an e-commerce application similar to the WolfShop service you refactored earlier. The application has various items (Item objects) that need to be displayed on a website and manipulated through an API.

Without DTOs:

  • Exposing the Item entities directly might reveal sensitive information, such as cost prices or supplier details.
  • The Item class might contain methods and properties irrelevant to the client, increasing the payload size unnecessarily.

With DTOs:

  • ItemDTO: Create a DTO that includes only the fields necessary for the client, such as name, quality, and sellIn.
  • Security: Sensitive fields are omitted, protecting internal data.
  • Performance: The payload size is reduced, improving load times and responsiveness.
  • Decoupling: Changes to the Item class do not directly impact the API contract, as the DTO acts as a mediator.

Sample ItemDTO Class:

<?php namespace WolfShop\DTO; class ItemDTO { private string $name; private int $quality; private int $sellIn; public function __construct(string $name, int $quality, int $sellIn) { $this->name = $name; $this->quality = $quality; $this->sellIn = $sellIn; } // Getter methods public function getName(): string { return $this->name; } public function getQuality(): int { return $this->quality; } public function getSellIn(): int { return $this->sellIn; } }

Mapping from Entity to DTO:

$itemDTO = new ItemDTO($item->name, $item->quality, $item->sellIn);

Using DTOs in the Service Layer:

  • The service layer can accept and return DTOs, ensuring that controllers or API endpoints work with data structures designed for their specific needs.
  • Any changes in the data access layer (Item entities) require minimal changes in the service layer, as long as the DTOs remain consistent.

Common Misconceptions:

  1. DTOs Are the Same as Entities:

    • Clarification: Entities represent the data model and may include business logic, while DTOs are simple data carriers without logic.
  2. DTOs Add Unnecessary Complexity:

    • Clarification: While DTOs introduce additional classes, they simplify data transfer and maintenance, especially in large applications.
  3. All Layers Should Use DTOs:

    • Clarification: DTOs are most beneficial between layers where data exposure needs to be controlled or where data transformation is necessary.

Potential Drawbacks:

  1. Overhead in Mapping:

    • Solution: Use automated mapping tools to reduce boilerplate code and potential errors in manual mapping.
  2. Maintenance Effort:

    • Solution: Keep DTOs minimal and focused. Regularly review and refactor DTOs as application requirements evolve.

Conclusion:

DTOs play a crucial role in modern software development by facilitating efficient, secure, and maintainable data transfer between different parts of an application. They help enforce a clear separation of concerns, adhering to SOLID principles and promoting a clean architecture. By understanding and implementing DTOs effectively, developers can build systems that are robust, scalable, and easier to maintain.

#################################################################################
#################################################################################

Let's explore how data handling in the WolfShop application would look without using DTOs. In this scenario, the Item entities are used directly throughout the application, including in controllers, views, and when exposing data through APIs. This means that the same Item class used for database operations and business logic is also used for communication with clients or the presentation layer.


Item Class Used Throughout the Application

<?php namespace WolfShop; class Item { public string $name; public int $quality; public int $sellIn; public float $costPrice; // Internal data not meant for clients public Supplier $supplier; // Complex object private string $internalCode; // Sensitive internal code public function __construct( string $name, int $quality, int $sellIn, float $costPrice, Supplier $supplier, string $internalCode ) { $this->name = $name; $this->quality = $quality; $this->sellIn = $sellIn; $this->costPrice = $costPrice; $this->supplier = $supplier; $this->internalCode = $internalCode; } // Business logic methods public function updateQuality(): void { // ... complex logic ... } // Other methods... }
  • Note: The Item class contains additional properties like costPrice, supplier, and internalCode, which are internal details not meant for client exposure.

Controller Directly Using Item Entities

<?php namespace WolfShop\Controller; use WolfShop\Item; use WolfShop\Repository\ItemRepository; class ItemController { private ItemRepository $itemRepository; public function __construct(ItemRepository $itemRepository) { $this->itemRepository = $itemRepository; } // Endpoint to get all items public function getAllItems(): array { // Fetch items from the repository (could be a database) $items = $this->itemRepository->findAll(); // Return items directly to the client return $items; } }
  • Explanation: The getAllItems method fetches Item entities and returns them directly, without any transformation or filtering.

API Response Sent to Clients

When the client makes a request to getAllItems, they receive the serialized Item objects, which include all public properties.

Example JSON Response:

[ { "name": "Apple iPad Air", "quality": 45, "sellIn": 10, "costPrice": 499.99, "supplier": { "name": "Apple Inc.", "contact": "contact@apple.com", "address": "One Apple Park Way, Cupertino, CA" }, "internalCode": "APL-IPD-AIR-2021" }, { "name": "Samsung Galaxy S23", "quality": 80, "sellIn": 0, "costPrice": 799.99, "supplier": { "name": "Samsung Electronics", "contact": "contact@samsung.com", "address": "129 Samsung-Ro, Suwon-si, South Korea" }, "internalCode": "SMS-GLX-S23-2023" } // ... more items ]

Issues with This Approach

  1. Exposure of Sensitive Internal Data:

    • Cost Price: Clients receive the costPrice, which might be confidential and not intended for public knowledge.
    • Supplier Details: Detailed supplier information is exposed, which might include confidential business relationships.
    • Internal Codes: The internalCode property is an internal reference not meant for client use.
  2. Security Risks:

    • Data Leakage: Sensitive information could be misused if intercepted or accessed by unauthorized parties.
    • Compliance Violations: Exposing personal or sensitive data might violate data protection regulations like GDPR.
  3. Unnecessary Data Transfer:

    • Increased Payload Size: Transferring additional data increases the size of the response, leading to higher bandwidth usage and slower client performance.
    • Client Processing Overhead: Clients need to parse and handle unnecessary data, which can lead to inefficiencies.
  4. Tight Coupling Between Layers:

    • Fragile Client Code: Any changes to the Item class (e.g., renaming properties, adding new fields) can break client applications that depend on the current structure.
    • Difficult Maintenance: The presentation layer is tightly coupled to the data access layer, making refactoring and updates challenging.
  5. Serialization Challenges:

    • Complex Objects: The supplier property is an object that may have nested objects or circular references, leading to serialization errors.
    • Methods and Private Properties: If not carefully managed, methods or private properties might be unintentionally serialized.
  6. Violation of Single Responsibility Principle:

    • Mixed Concerns: The Item class combines data representation, business logic, and persistence concerns, making it harder to test and maintain.

Potential Consequences

  • Security Breaches: Exposed sensitive data can lead to financial loss, reputational damage, or legal penalties.
  • Poor Performance: Larger payloads and unnecessary data can degrade the performance of both the server and the client application.
  • Reduced Flexibility: Difficulty in updating or refactoring the Item class without affecting clients hinders the ability to evolve the application.

Illustration of Issues

Client-Side Handling:

A client application consuming the API needs to parse the response and extract the necessary data.

fetch('/api/items') .then(response => response.json()) .then(items => { items.forEach(item => { // Client is only interested in name and quality console.log(`Item: ${item.name}, Quality: ${item.quality}`); // But receives and needs to ignore other data }); });
  • Problem: The client must know to ignore the costPrice, supplier, and internalCode fields, which adds unnecessary complexity.

Contrast with DTOs

If DTOs were used, the ItemDTO would include only the necessary fields:

<?php namespace WolfShop\DTO; class ItemDTO { public string $name; public int $quality; public int $sellIn; public function __construct(string $name, int $quality, int $sellIn) { $this->name = $name; $this->quality = $quality; $this->sellIn = $sellIn; } }

The controller would map Item entities to ItemDTOs before returning them:

public function getAllItems(): array { $items = $this->itemRepository->findAll(); // Map entities to DTOs $itemDTOs = array_map(function (Item $item) { return new ItemDTO($item->name, $item->quality, $item->sellIn); }, $items); return $itemDTOs; }

Benefits with DTOs:

  • Security: Sensitive data is not exposed.
  • Performance: Smaller payloads improve efficiency.
  • Loose Coupling: Clients depend on the DTO structure, which can remain stable even if the internal Item class changes.

Summary of the Issues Without DTOs

  1. Security Vulnerabilities:

    • Exposes confidential data unintentionally.
    • Increases risk of data breaches and compliance issues.
  2. Performance Degradation:

    • Larger response sizes lead to increased latency.
    • Inefficient use of network resources.
  3. Maintenance Difficulties:

    • Tight coupling makes refactoring risky.
    • Changes in internal models propagate to clients.
  4. Poor Separation of Concerns:

    • Blurs the lines between data access, business logic, and presentation.
    • Violates SOLID principles, particularly the Single Responsibility Principle.

Conclusion

Using entities directly without DTOs can lead to significant problems in terms of security, performance, and maintainability. By exposing the internal data structures of the application, you risk leaking sensitive information and create tight coupling between layers of your application. This approach makes your system fragile and difficult to evolve.

Recommendation:

  • Adopt DTOs: Implement DTOs to transfer only the necessary data between layers or over the network.
  • Implement Mapping: Use manual or automated mapping between entities and DTOs to control data exposure.
  • Enforce Encapsulation: Keep internal data and logic within the appropriate layers, exposing only what's needed.

Final Thoughts:

Understanding the pitfalls of not using DTOs emphasizes their importance in building secure, efficient, and maintainable applications. DTOs act as a protective layer that shields the internal workings of your application from the outside world, ensuring that only the intended data is shared and that your system remains robust against changes and potential security threats.

#################################################################################
#################################################################################

In Laravel, API Resources (also known as Resource classes) function similarly to Data Transfer Objects (DTOs). They both serve the purpose of transforming and transferring data between different layers of an application while controlling data exposure and improving maintainability.


How Laravel API Resources Are Similar to DTOs

  1. Data Transformation and Representation:

    • DTOs: Act as containers to hold and transfer data, often transforming it into a format suitable for the receiving layer or system.
    • Laravel API Resources: Transform Eloquent models and collections into JSON representations, shaping the data that is sent to API clients.
  2. Controlled Data Exposure:

    • DTOs: Include only necessary fields, omitting sensitive or irrelevant data to enhance security.
    • Laravel API Resources: Allow you to specify which attributes of a model are included in the API response, protecting sensitive information.
  3. Separation of Concerns:

    • DTOs: Decouple the data transfer mechanism from business logic and data access layers.
    • Laravel API Resources: Separate the presentation logic (formatting data for the client) from the business logic and data models.
  4. Improved Maintainability and Flexibility:

    • DTOs: Changes in internal data structures do not directly impact external interfaces, as DTOs act as a buffer.
    • Laravel API Resources: Provide a layer of abstraction that allows you to modify the underlying models without affecting the API response format.

Laravel API Resources in Detail

Laravel's API Resources are classes that transform your models and model collections into JSON, controlling what data is sent in API responses.

Example of an API Resource:

<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class ItemResource extends JsonResource { public function toArray($request) { return [ 'name' => $this->name, 'quality' => $this->quality, 'sell_in' => $this->sell_in, // Exclude sensitive fields like 'cost_price' or 'internal_code' ]; } }

Usage in a Controller:

<?php namespace App\Http\Controllers; use App\Models\Item; use App\Http\Resources\ItemResource; class ItemController extends Controller { public function show($id) { $item = Item::findOrFail($id); return new ItemResource($item); } public function index() { $items = Item::all(); return ItemResource::collection($items); } }

Explanation:

  • Transformation Logic: The toArray method defines how the model's data is transformed into the API response.
  • Selective Exposure: Only the fields specified in the toArray method are included in the response, hiding any sensitive or internal data.
  • Consistency: Ensures that all API responses follow a consistent structure, regardless of changes in the underlying models.

Benefits of Using Laravel API Resources

  1. Security:

    • Data Protection: Prevents exposure of sensitive fields like cost_price, supplier_details, or internal_code.
    • Controlled Access: You can conditionally include fields based on user roles or permissions.
  2. Performance:

    • Optimized Responses: By excluding unnecessary data, API responses are smaller and faster to transmit.
    • Lazy Loading: Laravel API Resources can handle relationships efficiently, preventing the N+1 query problem.
  3. Maintainability:

    • Loose Coupling: Decouples the API response format from the internal model structure.
    • Easier Refactoring: Changes to models or business logic don't require changes in the API endpoints.
  4. Customization:

    • Flexible Formatting: You can format data as needed, including renaming fields or formatting dates.
    • Conditional Attributes: Include or exclude fields based on certain conditions.

Conditional Attributes and Relationships

Laravel API Resources allow you to include attributes and relationships conditionally.

Example with Conditional Attributes:

public function toArray($request) { return [ 'name' => $this->name, 'quality' => $this->quality, 'sell_in' => $this->sell_in, 'supplier' => $this->when($request->user()->isAdmin(), function() { return new SupplierResource($this->supplier); }), ]; }
  • Explanation:
    • The supplier attribute is only included if the authenticated user is an admin.
    • The SupplierResource is another API Resource that formats the supplier data.

Differences Between DTOs and Laravel API Resources

While they serve similar purposes, there are some differences:

  1. Framework Integration:

    • DTOs: Framework-agnostic, can be used in any application architecture.
    • Laravel API Resources: Specifically designed to work within the Laravel framework, leveraging its features.
  2. Additional Features:

    • Laravel Resources: Provide helper methods for pagination, conditional relationships, and inclusion of meta data.
    • DTOs: Typically simple data containers without additional features.
  3. Use Cases:

    • DTOs: Used for transferring data between any layers of an application, including services, repositories, and external systems.
    • Laravel API Resources: Primarily used for formatting data for API responses.

Implementing DTOs in Laravel

While Laravel API Resources are often sufficient, there may be cases where you still want to use DTOs, especially for:

  • Data Transfer Between Services: When you need to pass data between different parts of your application internally.
  • Complex Transformations: When the transformation logic is complex and better handled outside of the resource classes.
  • Decoupling from Eloquent Models: If you want to completely decouple your data transfer objects from the ORM models.

Example of a DTO in Laravel:

<?php namespace App\DataTransferObjects; class ItemDTO { public function __construct( public string $name, public int $quality, public int $sellIn ) { } // Optionally include methods for validation or transformation }

Usage in a Service Layer:

$item = Item::find($id); $itemDTO = new ItemDTO( name: $item->name, quality: $item->quality, sellIn: $item->sell_in ); // Pass the DTO to other services or return it from a controller

Conclusion

  • Similar Purpose: Laravel's API Resources and DTOs both aim to control and format the data that is transferred between different parts of an application or to external clients.
  • Enhanced Control: Both methods provide a way to include only the necessary data, enhancing security and performance.
  • Adherence to SOLID Principles: By separating concerns and promoting loose coupling, they help maintain a clean and maintainable codebase.

Final Thoughts

Using Laravel API Resources is an effective way to implement the concept of DTOs within the Laravel framework. They provide powerful tools to transform and present your data exactly as needed, ensuring that your application's internal structures remain encapsulated and your API remains consistent and secure.

Recommendation:

  • For API Responses: Use Laravel API Resources to format and control the data sent to clients.
  • For Internal Data Transfer: Consider using DTOs when passing data between different layers or services within your application, especially if you need to decouple from Eloquent models or perform complex transformations.

By leveraging these tools, you can build robust, secure, and maintainable Laravel applications that adhere to best practices and architectural principles.

Thank you

Apply SOLID principles and design patterns Strategy Pattern

<?php

declare(strict_types=1);

namespace WolfShop;

final class WolfService
{
    /**
     * @param Item[] $items
     */
    public function __construct(
        private array $items
    ) { }

    public function updateQuality(): void
    {
        foreach ($this->items as $item) {
            if ($item->name != 'Apple AirPods' and $item->name != 'Apple iPad Air') {
                if ($item->quality > 0) {
                    if ($item->name != 'Samsung Galaxy S23') {
                        $item->quality = $item->quality - 1;
                    }
                }
            } else {
                if ($item->quality < 50) {
                    $item->quality = $item->quality + 1;
                    if ($item->name == 'Apple iPad Air') {
                        if ($item->sellIn < 11) {
                            if ($item->quality < 50) {
                                $item->quality = $item->quality + 1;
                            }
                        }
                        if ($item->sellIn < 6) {
                            if ($item->quality < 50) {
                                $item->quality = $item->quality + 1;
                            }
                        }
                    }
                }
            }

            if ($item->name != 'Samsung Galaxy S23') {
                $item->sellIn = $item->sellIn - 1;
            }

            if ($item->sellIn < 0) {
                if ($item->name != 'Apple AirPods') {
                    if ($item->name != 'Apple iPad Air') {
                        if ($item->quality > 0) {
                            if ($item->name != 'Samsung Galaxy S23') {
                                $item->quality = $item->quality - 1;
                            }
                        }
                    } else {
                        $item->quality = $item->quality - $item->quality;
                    }
                } else {
                    if ($item->quality < 50) {
                        $item->quality = $item->quality + 1;
                    }
                }
            }
        }
    }
}
 

##############################################

To refactor the provided WolfService class using SOLID principles and the Strategy Pattern, we'll create an ItemUpdateStrategy interface and implement different strategies for each item type. This approach adheres to the Single Responsibility Principle by separating the update logic into individual classes and the Open/Closed Principle by allowing new strategies to be added without modifying existing code.

Below is the refactored code:

ItemUpdateStrategy Interface:

<?php namespace WolfShop; interface ItemUpdateStrategy { public function update(Item $item): void; }

DefaultItemUpdateStrategy Class:

<?php namespace WolfShop; class DefaultItemUpdateStrategy implements ItemUpdateStrategy { public function update(Item $item): void { if ($item->quality > 0) { $item->quality--; } $item->sellIn--; if ($item->sellIn < 0 && $item->quality > 0) { $item->quality--; } } }

AppleAirPodsUpdateStrategy Class:

<?php namespace WolfShop; class AppleAirPodsUpdateStrategy implements ItemUpdateStrategy { public function update(Item $item): void { if ($item->quality < 50) { $item->quality++; } $item->sellIn--; if ($item->sellIn < 0 && $item->quality < 50) { $item->quality++; } } }

AppleIPadAirUpdateStrategy Class:

<?php namespace WolfShop; class AppleIPadAirUpdateStrategy implements ItemUpdateStrategy { public function update(Item $item): void { if ($item->quality < 50) { $item->quality++; if ($item->sellIn < 11 && $item->quality < 50) { $item->quality++; } if ($item->sellIn < 6 && $item->quality < 50) { $item->quality++; } } $item->sellIn--; if ($item->sellIn < 0) { $item->quality = 0; } } }

SamsungGalaxyS23UpdateStrategy Class:

<?php namespace WolfShop; class SamsungGalaxyS23UpdateStrategy implements ItemUpdateStrategy { public function update(Item $item): void { // Samsung Galaxy S23 does not change in quality or sellIn } }

ItemUpdateStrategyFactory Class:

<?php namespace WolfShop; class ItemUpdateStrategyFactory { public static function getStrategy(Item $item): ItemUpdateStrategy { return match ($item->name) { 'Apple AirPods' => new AppleAirPodsUpdateStrategy(), 'Apple iPad Air' => new AppleIPadAirUpdateStrategy(), 'Samsung Galaxy S23' => new SamsungGalaxyS23UpdateStrategy(), default => new DefaultItemUpdateStrategy(), }; } }

Refactored WolfService Class:

<?php declare(strict_types=1); namespace WolfShop; final class WolfService { /** * @param Item[] $items */ public function __construct( private array $items ) { } public function updateQuality(): void { foreach ($this->items as $item) { $strategy = ItemUpdateStrategyFactory::getStrategy($item); $strategy->update($item); } } }

This refactored code uses the Strategy Pattern to encapsulate the varying behavior of different items. Each strategy class implements the ItemUpdateStrategy interface, allowing WolfService to remain simple and focused on iterating over items and applying the appropriate strategy. This design adheres to the SOLID principles, making the code more maintainable and extensible.

Thank you

Tuesday, 17 September 2024

Create a custom Flysystem adapter for Cloudinary in Laravel

1. Install Cloudinary SDK

 composer require cloudinary/cloudinary_php

2. Create a Custom Flysystem Adapter

// app/Services/CloudinaryAdapter.php

namespace App\Services;

use Cloudinary\Cloudinary;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\UnableToReadFile;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UnableToDeleteFile;

class CloudinaryAdapter implements FilesystemAdapter
{
    protected $cloudinary;

    public function __construct(Cloudinary $cloudinary)
    {
        $this->cloudinary = $cloudinary;
    }

    public function write(string $path, string $contents, array $config): void
    {
        try {
            $this->cloudinary->uploadApi()->upload($contents, ['public_id' => $path]);
        } catch (\Exception $e) {
            throw UnableToWriteFile::atLocation($path, $e->getMessage());
        }
    }

    public function read(string $path): string
    {
        try {
            $result = $this->cloudinary->image($path)->toUrl();
            return file_get_contents($result);
        } catch (\Exception $e) {
            throw UnableToReadFile::atLocation($path, $e->getMessage());
        }
    }

    public function delete(string $path): void
    {
        try {
            $this->cloudinary->uploadApi()->destroy($path);
        } catch (\Exception $e) {
            throw UnableToDeleteFile::atLocation($path, $e->getMessage());
        }
    }

    // Implement other required methods (copy, move, etc.) following the same pattern.
}

3. Register the Custom Driver 

// app/Providers/AppServiceProvider.php

use App\Services\CloudinaryAdapter;
use Cloudinary\Cloudinary;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Storage::extend('cloudinary', function ($app, $config) {
            $cloudinary = new Cloudinary([
                'cloud_name' => $config['cloud_name'],
                'api_key'    => $config['api_key'],
                'api_secret' => $config['api_secret'],
            ]);

            $adapter = new CloudinaryAdapter($cloudinary);

            return new Filesystem($adapter);
        });
    }
}

4. Add Cloudinary Disk to config/filesystems.php

// config/filesystems.php

'disks' => [

    // Other disks...

    'cloudinary' => [
        'driver'    => 'cloudinary',
        'cloud_name' => env('CLOUDINARY_CLOUD_NAME'),
        'api_key'    => env('CLOUDINARY_API_KEY'),
        'api_secret' => env('CLOUDINARY_API_SECRET'),
    ],

],

.env file:

CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret

5. Usage Example

 // Upload file to Cloudinary
Storage::disk('cloudinary')->put('image_name', $fileContent);

// Retrieve file URL from Cloudinary
$url = Storage::disk('cloudinary')->url('image_name');

// Delete file from Cloudinary
Storage::disk('cloudinary')->delete('image_name');

Thank you

Sunday, 15 September 2024

Path module in Nodejs

 

Key Methods

Here are some of the commonly used methods in the path module:

  1. path.join([...paths]):

    • Joins all given path segments together, normalizing the resulting path.
    • Example:
      const path = require('path'); const filePath = path.join('/home', 'user', 'docs', 'file.txt'); console.log(filePath); // Outputs: /home/user/docs/file.txt
  2. path.resolve([...paths]):

    • Resolves a sequence of paths or path segments into an absolute path.
    • Example:
      const path = require('path'); const absolutePath = path.resolve('docs', 'file.txt'); console.log(absolutePath); // Outputs the absolute path to 'file.txt'
  3. path.basename(p, [ext]):

    • Returns the last portion of a path, optionally removing a file extension.
    • Example:
      const path = require('path'); const baseName = path.basename('/home/user/docs/file.txt'); console.log(baseName); // Outputs: file.txt const baseNameWithoutExt = path.basename('/home/user/docs/file.txt', '.txt'); console.log(baseNameWithoutExt); // Outputs: file
  4. path.dirname(p):

    • Returns the directory name of a path.
    • Example:
      const path = require('path'); const dirName = path.dirname('/home/user/docs/file.txt'); console.log(dirName); // Outputs: /home/user/docs
  5. path.extname(p):

    • Returns the extension of the path, including the leading dot (.).
    • Example:
      const path = require('path'); const extName = path.extname('/home/user/docs/file.txt'); console.log(extName); // Outputs: .txt
  6. path.parse(p):

    • Parses a path into an object with root, dir, base, ext, and name properties.
    • Example:
      const path = require('path'); const parsed = path.parse('/home/user/docs/file.txt'); console.log(parsed); /* Outputs: { root: '/', dir: '/home/user/docs', base: 'file.txt', ext: '.txt', name: 'file' } */
  7. path.format(p):

    • Formats a path object into a string path.
    • Example:
      const path = require('path'); const formatted = path.format({ root: '/', dir: '/home/user/docs', base: 'file.txt', ext: '.txt', name: 'file' }); console.log(formatted); // Outputs: /home/user/docs/file.txt
  8. path.normalize(p):

    • Normalizes a path, resolving .. and . segments.
    • Example:
      const path = require('path'); const normalized = path.normalize('/home/user/docs/../file.txt'); console.log(normalized); // Outputs: /home/user/file.txt
  9. path.relative(from, to):

    • Returns the relative path from from to to.
    • Example:
      const path = require('path'); const relative = path.relative('/home/user', '/home/user/docs/file.txt'); console.log(relative); // Outputs: docs/file.txt
  10. path.win32 and path.posix:

    • Provide platform-specific implementations of path operations. Use path.win32 for Windows paths and path.posix for POSIX paths.
    • Example:
      const path = require('path'); const posixPath = path.posix.join('/home', 'user', 'docs', 'file.txt'); console.log(posixPath); // Outputs: /home/user/docs/file.txt const win32Path = path.win32.join('C:\\', 'Users', 'Docs', 'file.txt'); console.log(win32Path); // Outputs: C:\Users\Docs\file.txt

Usage Examples

Constructing Paths

const path = require('path'); // Joining paths const fullPath = path.join('folder', 'subfolder', 'file.txt'); console.log(fullPath); // Outputs the joined path // Resolving paths const absolutePath = path.resolve('folder', 'file.txt'); console.log(absolutePath); // Outputs the absolute path

Parsing and Formatting Paths

const path = require('path'); // Parsing a path const parsedPath = path.parse('/home/user/file.txt'); console.log(parsedPath); // Outputs: { root: '/', dir: '/home/user', base: 'file.txt', ext: '.txt', name: 'file' } // Formatting a path object const formattedPath = path.format(parsedPath); console.log(formattedPath); // Outputs: /home/user/file.txt

Thank you.

Event Module in Node.js

Key Concepts:

  • EventEmitter: This class is the core of the events module, allowing you to create, listen for, and trigger events.
  • emit(): Emits an event, triggering all the listeners attached to that event.
  • on(): Attaches a listener function to a specific event.

Usage of the Event Module

Here’s a basic example demonstrating how to use the events module:

  1. Import the EventEmitter class:

    const EventEmitter = require('events');
  2. Create an instance of EventEmitter:

    const eventEmitter = new EventEmitter();
  3. Register an event listener using on():

    eventEmitter.on('greet', () => { console.log('Hello, world!'); });
  4. Trigger an event using emit():

    eventEmitter.emit('greet'); // Outputs: Hello, world!

Example: Passing Data with Events

You can also pass data when emitting an event:

const EventEmitter = require('events'); const eventEmitter = new EventEmitter(); // Register listener with arguments eventEmitter.on('greet', (name) => { console.log(`Hello, ${name}!`); }); // Emit the event with a parameter eventEmitter.emit('greet', 'John'); // Outputs: Hello, John!

Removing Event Listeners

You can remove event listeners using removeListener() or removeAllListeners():

const greet = () => { console.log('Hello, world!'); }; eventEmitter.on('greet', greet); // Remove the listener eventEmitter.removeListener('greet', greet);

EventEmitter Methods:

  • on(event, listener): Attaches a listener for the specified event.
  • emit(event, [arg1], [arg2], [...]): Emits the specified event, calling all attached listeners.
  • once(event, listener): Attaches a listener that is called at most once.
  • removeListener(event, listener): Removes the specified listener.
  • removeAllListeners([event]): Removes all listeners for the event.
  • listeners(event): Returns an array of listeners for the event.

Example: Using once()

The once() method attaches a listener that is called only once:

eventEmitter.once('greetOnce', () => { console.log('This will only be logged once'); }); eventEmitter.emit('greetOnce'); // Outputs: This will only be logged once eventEmitter.emit('greetOnce'); // No output

Inheriting from EventEmitter

In Node.js, it's common to extend the EventEmitter class to make custom classes emit events:

const EventEmitter = require('events'); class MyEmitter extends EventEmitter {} const myEmitter = new MyEmitter(); // Register event listener myEmitter.on('event', () => { console.log('An event occurred!'); }); // Emit the event myEmitter.emit('event'); // Outputs: An event occurred!

Real-World Usage

The events module is widely used in Node.js for handling things like:

  • Streams: Both readable and writable streams are event emitters.
  • HTTP Requests: The request and response objects in HTTP are also event emitters.
  • Timers: You can listen to setTimeout, setInterval, etc., via events.
Thank you

Module wrapper function in Node.js

1. Module wrapper function in Node.js

When you write a module in Node.js, the code is internally wrapped in this function:

(function (exports, require, module, __filename, __dirname) { // Your module code here });

Purpose of the Wrapper Function

Node.js uses this wrapper function to provide certain key variables and functionalities to each module. These variables include exports, require, module, __filename, and __dirname, which are essential for the module system in Node.js.

Let’s break it down:

1. exports

  • Type: Object
  • Purpose: This is a shortcut for module.exports and is used to export properties or methods from a module.
  • Example:
    exports.myFunction = () => { console.log('Hello'); };
    Equivalent to:
    module.exports.myFunction = () => { console.log('Hello'); };

2. require

  • Type: Function
  • Purpose: This function is used to import modules. It can load both core modules (like fs, path) and custom modules.
  • Example:
    const fs = require('fs'); // Core module const myModule = require('./myModule'); // Custom module

3. module

  • Type: Object
  • Purpose: The module object represents the current module, and module.exports is the object that gets returned when a module is required. You can assign anything to module.exports to define what will be exported from the module.
  • Example:
    module.exports = function() { console.log('Exporting a function directly.'); };

4. __filename

  • Type: String
  • Purpose: This variable contains the full path to the current module file, including the file name. It’s useful for getting the file’s location.
  • Example:
    console.log(__filename); // Outputs the full path to the current file

5. __dirname

  • Type: String
  • Purpose: This variable contains the directory name of the current module, i.e., the folder path without the file name. It's useful for creating absolute paths relative to the current file.
  • Example:
    console.log(__dirname); // Outputs the full directory path of the current file

Why is the Module Wrapped?

Node.js wraps modules in this function to:

  1. Provide Scope Isolation: It ensures that each module has its own private scope, preventing variable collisions between different modules.
  2. Pass Essential Variables: This is how Node.js passes the exports, require, module, __filename, and __dirname to each module automatically.
  3. Implement the Module System: It allows Node.js to manage how modules are imported/exported and run.

Example of a Full Module in Action

Imagine you write the following code in myModule.js:

console.log(__filename); // Full path to myModule.js console.log(__dirname); // Directory path to myModule.js exports.hello = () => { console.log('Hello from myModule'); };

When Node.js executes this module, it internally wraps the file's content like this:

(function (exports, require, module, __filename, __dirname) { console.log(__filename); // Full path to myModule.js console.log(__dirname); // Directory path to myModule.js exports.hello = () => { console.log('Hello from myModule'); }; });

This function is executed, and the appropriate values for exports, require, module, __filename, and __dirname are provided.

Summary

  • The function (function (exports, require, module, __filename, __dirname) { ... }) is an internal mechanism in Node.js that wraps every module.
  • It gives each module the necessary tools (exports, require, module, etc.) to export and import functionality and access module-specific paths.
  • This mechanism allows Node.js to maintain modularity, scope isolation, and provide essential information like the file and directory path.
2. Two different module systems for importing and exporting: CommonJS and ES6 Modules.

1. CommonJS Module System (Default in Node.js)

CommonJS is the default module system in Node.js (before Node.js added support for ES6 modules). It uses require() for importing and module.exports or exports for exporting.

Exporting in CommonJS

// file: math.js const add = (a, b) => a + b; const subtract = (a, b) => a - b; // Exporting functions module.exports = { add, subtract };

Importing in CommonJS

// file: app.js const { add, subtract } = require('./math'); console.log(add(2, 3)); // Output: 5 console.log(subtract(5, 3)); // Output: 2

2. ES6 Modules (ECMAScript Modules)

Starting from Node.js 12+, you can use ES6 modules, which use import and export syntax. To enable this, you need to either:

  • Use the .mjs file extension.
  • Add "type": "module" in your package.json.

Exporting in ES6 Modules

// file: math.mjs or with package.json "type": "module" export const add = (a, b) => a + b; export const subtract = (a, b) => a - b;

Importing in ES6 Modules

// file: app.mjs or with package.json "type": "module" import { add, subtract } from './math.JMS'; console.log(add(2, 3)); // Output: 5 console.log(subtract(5, 3)); // Output: 2

Key Differences

  1. Syntax:

    • CommonJS: require() and module.exports.
    • ES6 Modules: import and export.
  2. Default Exports:
    In CommonJS, you can export a single value using module.exports:

    // CommonJS default export module.exports = add; const add = require('./math');

    In ES6, you use the default keyword:

    // ES6 default export export default add; import add from './math.mjs';
  3. Asynchronous Loading:
    ES6 modules support asynchronous loading natively, while CommonJS is synchronous.


Choosing Between CommonJS and ES6 Modules

  • If you're working in modern environments or front-end codebases, use ES6 modules (import/export).
  • For older Node.js projects or where compatibility is required, use CommonJS (require/module.exports).

Thank you

Saturday, 14 September 2024

DevOps tools

Classification of DevOps tools, including:

1. Version Control

  • Git: Distributed version control system.
  • GitHub: Git repository hosting with collaboration features.
  • GitLab: Git repository management with integrated CI/CD.
  • Bitbucket: Git repository hosting with built-in CI/CD and collaboration.

2. Continuous Integration/Continuous Deployment (CI/CD)

  • Jenkins: Open-source automation server for building and deploying applications.
  • GitLab CI/CD: Integrated CI/CD pipelines within GitLab.
  • CircleCI: CI/CD platform for automating workflows.
  • Travis CI: CI service for GitHub repositories.
  • Azure Pipelines: CI/CD service from Microsoft Azure.
  • GitHub Actions: CI/CD and automation tool integrated with GitHub repositories.

3. Configuration Management

  • Ansible: Agentless automation tool using YAML playbooks.
  • Chef: Configuration management tool using Ruby-based DSL.
  • Puppet: Configuration management tool with a declarative language.
  • SaltStack: Configuration management and orchestration tool.

4. Infrastructure as Code (IaC)

  • Terraform: Tool for defining and provisioning infrastructure using a declarative language.
  • AWS CloudFormation: AWS service for defining infrastructure using JSON or YAML templates.
  • Pulumi: IaC tool using general-purpose programming languages.

5. Containerization

  • Docker: Platform for creating, distributing, and running containers.
  • Podman: Daemonless container engine compatible with Docker.
  • Containerd: Core container runtime for managing container lifecycle.
  • CRI-O: Lightweight container runtime for Kubernetes.
  • LXC (Linux Containers): OS-level virtualization for running multiple Linux distributions.
  • OpenVZ: OS-level virtualization technology for Linux.
  • Singularity: Container platform for HPC and scientific computing.
  • Docker Compose: Tool for defining and running multi-container Docker applications.

6. Container Orchestration

  • Kubernetes: Open-source platform for managing containerized applications.
  • Docker Swarm: Native clustering and orchestration for Docker containers.
  • Apache Mesos: Distributed systems kernel supporting container orchestration.
  • Marathon: Container orchestration on Apache Mesos.
  • Nomad: HashiCorp tool for scheduling and managing containers.
  • OpenShift: Kubernetes-based container platform with additional enterprise features.
  • Rancher: Platform for managing multiple Kubernetes clusters.
  • Docker Enterprise: Docker's enterprise solution with Kubernetes and Docker Swarm support.
  • Amazon ECS (Elastic Container Service): Managed container orchestration service from AWS.
  • Amazon EKS (Elastic Kubernetes Service): Managed Kubernetes service from AWS.
  • Google Kubernetes Engine (GKE): Managed Kubernetes service from Google Cloud.
  • Azure Kubernetes Service (AKS): Managed Kubernetes service from Microsoft Azure.

7. Monitoring and Logging

  • Prometheus: Monitoring and alerting toolkit.
  • Grafana: Platform for visualizing metrics and logs.
  • ELK Stack (Elasticsearch, Logstash, Kibana): Tools for analyzing and visualizing log data.
  • Splunk: Platform for searching, monitoring, and analyzing machine data.

8. Collaboration and Communication

  • Slack: Team communication tool.
  • Microsoft Teams: Collaboration platform with chat and video meetings.
  • JIRA: Issue and project tracking tool.
  • Confluence: Collaboration and documentation tool.

9. Testing and Quality Assurance

  • JUnit: Testing framework for Java applications.
  • Selenium: Framework for automated web application testing.
  • SonarQube: Code quality and security analysis tool.

10. Security and Compliance

  • Snyk: Security vulnerability scanning tool.
  • Aqua Security: Security solutions for containerized applications.
  • HashiCorp Vault: Secrets management and data protection.

11. Cloud Services

  • AWS (Amazon Web Services): Comprehensive cloud platform.
  • Microsoft Azure: Cloud computing service offering various cloud solutions.
  • Google Cloud Platform (GCP): Cloud services platform providing compute, storage, and data analytics.

This updated classification reflects a broader set of tools for containerization and container orchestration, covering a wide range of functionalities and use cases in the DevOps ecosystem.

Publish npm package

  Để publish   pav-kit  lên NPM, bạn hãy làm theo các bước dưới đây. Tôi đã tạo thêm file  index.js  để đảm bảo gói tin hợp lệ. Bước 1: Tạo ...