+16

Hiểu thêm về composer install(P2)

Trong bài viết lần trước, chúng ta đã tìm hiểu về requirerequire-dev cũng như cách cài đặt một package hợp lí trong các môi trường khác nhau. Hôm nay ta tiếp tục đào sâu thêm một chút về một số kiến thức xung quanh những câu lệnh tưởng như rất đơn giản này nhé.

1. Install và update

Ngày mới đi làm, mình chạy composer installcomposer update loạn lên, dẫn đến thi thoảng bị một đống conflict trong file composer.lock. Để hiểu đơn giản thì khi chúng ta chạy 2 câu lệnh này thì nó sẽ hoạt động như sau.

Composer install

  • Khi chạy câu lệnh trên, nó sẽ kiểm tra sự tồn tại của file composer.lock trước. Nếu không tồn tại, sẽ đọc các dependencies từ file composer.json ra. Sau đó tiến hành tạo file composer.lock.
  • Nếu file composer.lock tồn tại. Nó sẽ tải về chính xác version của các dependencies được cài đặt trong project.

Composer update

  • Nếu file composer.json không tồn tại. Thông báo không có sự tồn tại của file này và yêu cầu tạo file composer.json.
  • Nếu file composer.json tồn tại. Cài đặt phiên bản mới nhất được định nghĩa trong file composer.json sau đó cập nhật version đã cài đặt vào file composer.lock.

Cẩn thận khi chạy lệnh composer update vì nó không thực sự an toàn cho project của bạn

Nhìn vào nguyên tắc hoạt động của câu lệnh composer update. Chúng ta có thể thấy sự nguy hiểm tiềm ẩn đằng sau câu lệnh này. Ví dụ dự án đang có 100 dependencies. Khi chạy câu lệnh này, toàn bộ các dependencies sẽ được update lên phiên bản mới nhất. Nó sẽ là một breaking changes trong dự án của bạn. Ví dụ một trong những version mới được update của các package kia không tương thích với môi trường hiện tại có thể gây lỗi. Do vậy, khi muốn update package nào đó, thì cần thỉ định chính xác tên package theo cú pháp composer update #tên_package đó nhé. Các bạn có thể theo dõi bài viết chi tiết tại đây

2. Version

Trong quá trình phát triển, các package thường xuyên có những bản cập nhật các tính năng mới, hay sửa các lỗi cũ. Các bản cập nhập này được đánh theo các version. Do đó, khi khai báo các dependencies định nghĩa trong file composer.json chúng ta thường sẽ thấy các kí hiệu như sau.

"require": {
    "vendor/package": "1.3.2", // Chính xác phiên bản 1.3.2

    // >, <, >=, <= | Giới hạn trên / Giới hạn dưới
    "vendor/package": ">=1.3.2", // Các package có phiên bản lớn hơn hoặc bằng 1.3.2
    "vendor/package": "<1.3.2", // Các package nhỏ hơn 1.3.2

    // * | Kí tự đại diện
    "vendor/package": "1.3.*", // >=1.3.0 <1.4.0

    // ~ | Cho phép chữ số cuối cùng được chỉ định đi lên
    "vendor/package": "~1.3.2", // >=1.3.2 <1.4.0
    "vendor/package": "~1.3", // >=1.3.0 <2.0.0

    // ^ | Không cho phép thay đổi lớn(breaking changes)
    "vendor/package": "^1.3.2", // >=1.3.2 <2.0.0
    "vendor/package": "^0.3.2", // >=0.3.2 <0.4.0 // Ngoại trừ version 0
}

Với mỗi cách khai báo version đều có ưu và nhược điểm riêng. Vì vậy khó có thể khẳng định đâu là cách khai báo hiệu quả và an toàn nhất. Nhưng hiểu về các nguyên tắc khai báo version sẽ giúp bạn rất nhiều trong quá trình chỉnh sửa, cập nhật các dependencies.

3. Hiệu năng

PHP Fatal error: Allowed memory size of XXXXXX bytes exhausted <...>

Hồi mình còn dùng con server cùi bắp, khi chạy composer install mình thường xuyên gặp lỗi này. Và sau đó mình phát hiện nguyên nhân là do không commit file composer.lock lên git dẫn đến khi kéo code của project về server, phải tiến hành đọc các dependencies từ file composer.json và tạo lại file lock dẫn đến tốn nhiều bộ nhớ hơn.

Sử dụng file composer.lock tiết kiệm memory hơn so với composer.json khi chạy composer install

Theo kinh nghiệm của mình, lỗi memory từ composer thường có nghĩa là nó đang tiêu tốn quá nhiều bộ nhớ để tìm kiếm các package kết hợp phù hợp để cài đặt, đặc biệt là các ràng buộc về phiên bản không đủ cụ thể. Ví dụ: ^5.2.4 khớp với 5.3 đến 5.3.29, 5.4 đến 5.4.45, v.v. Đối với từng phiên bản và hoán vị cụ thể, composer phải lấy các dependencies của package để kiểm tra xem tất cả các ràng buộc có được đáp ứng hay không. Điều này thường xảy ra khi mức tiêu thụ bộ nhớ trở nên lớn.

Khi các phiên bản đã được tìm ra hoặc đọc chính xác version từ file composer.lock, giai đoạn cài đặt sẽ sử dụng ít bộ nhớ hơn nhiều. Các phiên bản đã giải quyết cho từng package cũng được lưu trữ trong file composer.lock để có thể sao chép hoán vị cụ thể đã cài đặt trong các môi trường khác. Vì vậy hãy nhớ luôn commit file composer.lock lên git nhé các bạn.

4. Composer dump-autoload

Anh ơi, em tạo file seeder trong Laravel bằng command. Nhưng khi F5 màn hình báo lỗi class vừa tạo không tồn tại ?

Em đã chạy composer dump-autoload chưa?

Nghe quen quen đúng không nào ? Vậy chính xác composer dump-autoload đã làm gì ?

Đầu tiên khi chạy composer dump-autoload xong các bạn hãy để ý thư mục vendor giúp mình

vendor/composer
├── autoload_classmap.php
├── autoload_files.php
├── autoload_namespaces.php
├── autoload_psr4.php
├── autoload_real.php
├── autoload_static.php
├── ClassLoader.php
├── installed.json
└── LICENSE

Sau đó các bạn phải hiểu qua về khái niệm autoloading là gì?

Về cơ bản, composer cung cấp 4 phương thức khác nhau để thực hiện autoloading

  • file autoloading
  • classmap autoloading
  • PSR-0 autoloading
  • PSR-4 autoloading

File autoloading hoạt động tương tự như includerequire cho phép bạn tải toàn bộ soure files. Tất cả các source files được tham chiếu với đường dẫn file sẽ tải mỗi khi ứng dụng chạy. Loại này hay được áp dụng với các files không sử dụng class(Ví dụ như các helper trong Laravel)

    "autoload": {
        "files": [
            "app/Helpers.php" // Sử dụng autoloading với file helper trong Laravel
        ]
    },

Classmap là đơn giản và dễ hiểu nhất, nó ghi toàn bộ logic trong file vendor/composer/autoload_classmap.php. Khi composer đọc được rằng chúng ta sử dụng classmap, nó sẽ quét toàn bộ các file trong thư mục được đề cập trong files composer.json và tạo ra một mảng namespaces và đường dẫn tương ứng.

Mỗi khi thêm thêm files, cần chạy composer dump-autoload để generate lại các đường dẫn đã mapping

Ví dụ trong Laravel, thường 2 thư mục seeds, factories sẽ được autoloading theo kiểu này. Đó cũng là lí do mỗi khi tạo ra 1 file mới, ta phải chạy lại câu lệnh composer dump-autoload.

    "autoload": {
        "classmap": [
            "database/seeds",
            "database/factories"
        ],
    }

Với phương pháp này, tuy mỗi lần thêm file phải chạy lại composer dump-autoload nhưng với ưu điểm có thể sử dụng cache trong opcahetìm kiếm theo dạng key-value trong file autoload_classmap.php khiến việc load class có thể chạy nhanh hơn.

PSR-0 và PSR-4: Với PSR-0 logic tồn tại ở vendor/composer/autoload_namespaces.php còn PSR-4 tồn tại ở đường dẫn vendor/composer/autoload_psr4.php. Cả 2 đều gần như tuân thủ chung các quy tắc như sau

  • Không cần chạy composer dump-autoload mỗi khi tạo thêm file php mới bởi vì quá trình searching for file path là linh động
  • Bạn bắt buộc phải sử dụng namespaces

Sự khác biệt giữa PSR-0 và PSR-4 chỉ về việc tạo đường dẫn file.

{
    "autoload": {
        "psr-0": {
            "Tutsplus\\Library": "src"
        },
        "psr-4": {
            "Tutsplus\\Library\\": "src"
        }
    }
}

Với PSR-0 nếu bạn muốn định nghĩa class Book trong thư mục src\Tutsplus\Library, bạn cần tạo file src\Tutsplus\Library\Book.php và khai báo namespace như sau

<?php
namespace Tutsplus\Library;

class Book 
{
    //... 
}
?>

Bạn có thể thấy bắt buộc cả đường dẫnnamespace đều phải là Tutsplus\Library. Còn với PSR-4 bạn có thể định nghĩa class Book trong đường dẫn src\Book.php. Lưu ý vẫn phải follow namespace theo cái đã định nghĩa trong file composer.json

<?php
namespace Tutsplus\Library;

class Book 
{
    //... 
}
?>

5. Autoloader optimization

Mặc định, Autoloader Composer chạy tương đối nhanh. Tuy nhiên, do cách thiết lập autoloading PSR-4PSR-0, nó cần kiểm tra filesystem trước khi giải quyết classname một cách chính xác. Điều này khiến hệ thống chậm hơn, nhưng nó thuận tiện trong môi trường phát triển vì khi bạn thêm một class mới, nó có thể được phát hiện/sử dụng ngay lập tức mà không cần phải rebuild lại. Tuy nhiên trên môi trường production, mọi thứ cần phải diễn ra nhanh hơn, do đó Composer có vài chiến lược để optimize.

Optimization Level 1: Class map generation

Hướng dẫn config

  • Thiết lập trong file composer.json với thuộc tính "optimize-autoloader": true
  • Chạy install hoặc update với -o / --optimize-autoloader
  • Chạy dump-autoload với -o / --optimize

Cách thức hoạt động

Composer sẽ convert các rule PSR-4/PSR-0 sang classmap rule. Các bạn có thể kiểm tra file vendor/composer/autoload_classmap.php . File này sẽ return một array, có key là tên class đầy đủ và value là đường dẫn đến class. Composer đã check sự tồn tại của các class này trước và generate ra classmap, do đó, khi tham chiếu đến class thì autoloader chỉ việc require file vào không cần phải kiểm tra filesystem nữa.

Với PHP 5.6+, Composer còn thực hiện cache classmap trong opcache, làm cho việc load class có thể chạy nhanh nhất có thể.

Optimization Level 2/A: Authoritative class maps

Hướng dẫn config

  • Thiết lập trong file composer.json với thuộc tính "classmap-authoritative": true
  • Chạy install hoặc update với -a / --classmap-authoritative
  • Chạy dump-autoload với -a / --classmap-authoritative

Cách thức hoạt động

Khi bật tính năng này sẽ tự động kích hoạt Level 1: class map generation

Cách này nói rằng nếu thứ gì đó không được tìm thấy trong class map, thì nó không tồn tại và autoloader không nên cố tìm trên filesystem theo quy tắc PSR-4.

Optimization Level 2/B: APCu cache

Hướng dẫn config

  • Thiết lập trong file composer.json với thuộc tính "apcu-autoloader": true
  • Chạy install hoặc update với --apcu-autoloader
  • Chạy dump-autoload với --apcu

Cách thức hoạt động

Cách này thêm cache APCu như một fallback cho class map. Tuy nhiên, nó sẽ không tự động tạo class map, vì vậy bạn vẫn nên thiết lập class map generation level 1 theo cách thủ công nếu muốn.

Cho dù một class có được tìm thấy hay không, dữ liệu đó luôn được lưu trong bộ nhớ cache của APCu, do đó, nó có thể được trả về nhanh chóng trong request tiếp theo.

Không thể kết hợp giữa Level 2/A: Authoritative class maps và Level 2/B: APCu cache cùng một lúc

6. Tổng kết

Vậy là chúng ta đã biết thêm được một số kiến thức về Composer dường như rất đơn giản nhưng vô cùng quan trọng này mà lại dễ bị bỏ qua như là

  • Luôn luôn commit file composer.lock lên git
  • Đừng sử dụng composer update vì tiềm ẩn nhiều nguy hiểm
  • Câu lệnh dump-autoload là gì và một số cách optimize

Ngoài ra mình đang viết bài với tư cách một content creator của Viblo nên rất mong các bạn upvote cũng như share bài viết nếu thấy hữu ích để mình có thêm động lực tạo ra nhiều nội dung hay hơn.

Đọc những bài viết khác của tác giả: Chillwithsu.com

Donate cho tác giả : Buy me a coffee

Chúc các bạn code vui, khỏe, giải trí !!!

Tham khảo:


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí