aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src/autoload.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-21 16:37:03 +0900
committernsfisis <nsfisis@gmail.com>2026-02-21 16:37:03 +0900
commit3535037592f149477c915a8b66da974eb59586db (patch)
treef476925e9d136d2ddcf206d80355a9a0ca4afda5 /crates/mozart/src/autoload.rs
parentc04744719bd16d9414a9f9a358691d03a993670c (diff)
downloadphp-mozart-3535037592f149477c915a8b66da974eb59586db.tar.gz
php-mozart-3535037592f149477c915a8b66da974eb59586db.tar.zst
php-mozart-3535037592f149477c915a8b66da974eb59586db.zip
feat(autoload): add classmap scanning, optimize, APCu, platform checks, and strict-psr
Add PHP file scanner (php_scanner.rs) with class/interface/trait/enum detection, comment/string/heredoc stripping, and PSR-4/PSR-0 validation. Extend autoload generation with: classmap directory scanning, --optimize mode (PSR-4/PSR-0 to classmap), --classmap-authoritative, --apcu caching with optional prefix, platform_check.php generation, and --strict-psr violation reporting. Wire new options through dump-autoload, install, require, update, and remove commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/src/autoload.rs')
-rw-r--r--crates/mozart/src/autoload.rs583
1 files changed, 577 insertions, 6 deletions
diff --git a/crates/mozart/src/autoload.rs b/crates/mozart/src/autoload.rs
index d4421a6..5d5d13c 100644
--- a/crates/mozart/src/autoload.rs
+++ b/crates/mozart/src/autoload.rs
@@ -1,5 +1,6 @@
use crate::installed::InstalledPackages;
-use std::collections::BTreeMap;
+use crate::lockfile::LockedPackage;
+use std::collections::{BTreeMap, HashSet};
use std::path::{Path, PathBuf};
// Embed Composer PHP files from the submodule at compile time.
@@ -9,6 +10,18 @@ const INSTALLED_VERSIONS_PHP: &str =
include_str!("../../../composer/src/Composer/InstalledVersions.php");
const COMPOSER_LICENSE: &str = include_str!("../../../composer/LICENSE");
+/// How platform requirements are checked during autoloader generation.
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub enum PlatformCheckMode {
+ /// Check all platform requirements (php, ext-*, lib-*).
+ #[default]
+ Full,
+ /// Only check the PHP version requirement.
+ PhpOnly,
+ /// Disable platform requirement checks entirely.
+ Disabled,
+}
+
/// Configuration for autoload generation.
pub struct AutoloadConfig {
/// Absolute path to the project root (where composer.json lives).
@@ -22,6 +35,18 @@ pub struct AutoloadConfig {
pub suffix: String,
/// When true, emit `$loader->setClassMapAuthoritative(true)` in the generated autoloader.
pub classmap_authoritative: bool,
+ /// When true, scan PSR-4/PSR-0 directories and generate a full classmap (optimize mode).
+ pub optimize: bool,
+ /// When true, generate APCu-based class caching in the autoloader.
+ pub apcu: bool,
+ /// Optional prefix for APCu cache keys (implies `apcu`).
+ pub apcu_prefix: Option<String>,
+ /// When true, return an error on PSR mapping violations detected during classmap scan.
+ pub strict_psr: bool,
+ /// How to handle platform requirement checks.
+ pub platform_check: PlatformCheckMode,
+ /// When true, skip all platform requirement checks.
+ pub ignore_platform_reqs: bool,
}
/// Collected autoload mappings from all packages.
@@ -428,8 +453,339 @@ fn generate_autoload_static(static_data: &AutoloadData, suffix: &str) -> String
out
}
+/// Recursively collect PHP files from a directory, skipping excluded paths.
+fn collect_php_files(
+ dir: &Path,
+ excluded: &[String],
+ vendor_dir: &Path,
+ project_dir: &Path,
+) -> Vec<PathBuf> {
+ let mut result = Vec::new();
+ if !dir.is_dir() {
+ return result;
+ }
+ collect_php_files_inner(dir, excluded, vendor_dir, project_dir, &mut result);
+ result
+}
+
+fn collect_php_files_inner(
+ dir: &Path,
+ excluded: &[String],
+ vendor_dir: &Path,
+ project_dir: &Path,
+ result: &mut Vec<PathBuf>,
+) {
+ let entries = match std::fs::read_dir(dir) {
+ Ok(e) => e,
+ Err(_) => return,
+ };
+ for entry in entries.flatten() {
+ let path = entry.path();
+
+ // Check if path matches any excluded pattern
+ if is_excluded(&path, excluded, vendor_dir, project_dir) {
+ continue;
+ }
+
+ if path.is_dir() {
+ collect_php_files_inner(&path, excluded, vendor_dir, project_dir, result);
+ } else if crate::php_scanner::is_php_ext(&path) {
+ result.push(path);
+ }
+ }
+}
+
+/// Check whether a path matches any of the excluded patterns.
+fn is_excluded(path: &Path, excluded: &[String], vendor_dir: &Path, project_dir: &Path) -> bool {
+ for exc in excluded {
+ // Excluded patterns can be relative to project_dir or absolute
+ let exc_path = if Path::new(exc).is_absolute() {
+ PathBuf::from(exc)
+ } else {
+ project_dir.join(exc)
+ };
+ if path.starts_with(&exc_path) || path == exc_path {
+ return true;
+ }
+ // Also check relative to vendor_dir
+ let exc_vendor = vendor_dir.join(exc);
+ if path.starts_with(&exc_vendor) || path == exc_vendor {
+ return true;
+ }
+ }
+ false
+}
+
+/// Scan directories for PHP class declarations and return a classmap.
+///
+/// `dirs` is a list of absolute directory paths to scan.
+/// Returns a `BTreeMap<class_name, file_path_expression>` where the path expression
+/// uses `$vendorDir` or `$baseDir` as appropriate.
+fn scan_classmap_dirs(
+ dirs: &[PathBuf],
+ vendor_dir: &Path,
+ project_dir: &Path,
+ excluded: &[String],
+) -> BTreeMap<String, String> {
+ let mut classmap = BTreeMap::new();
+
+ for dir in dirs {
+ let files = collect_php_files(dir, excluded, vendor_dir, project_dir);
+ for file in files {
+ match crate::php_scanner::find_classes(&file) {
+ Ok(classes) => {
+ for class in classes {
+ let path_expr = path_to_php_expr(&file, vendor_dir, project_dir);
+ classmap.entry(class).or_insert(path_expr);
+ }
+ }
+ Err(_) => continue,
+ }
+ }
+ }
+
+ classmap
+}
+
+/// Convert an absolute file path to a PHP path expression using `$vendorDir` or `$baseDir`.
+fn path_to_php_expr(file: &Path, vendor_dir: &Path, project_dir: &Path) -> String {
+ if let Ok(rel) = file.strip_prefix(vendor_dir) {
+ let rel_str = rel.to_string_lossy().replace('\\', "/");
+ format!("$vendorDir . '/{rel_str}'")
+ } else if let Ok(rel) = file.strip_prefix(project_dir) {
+ let rel_str = rel.to_string_lossy().replace('\\', "/");
+ format!("$baseDir . '/{rel_str}'")
+ } else {
+ // Fall back to absolute path
+ let abs = file.to_string_lossy().replace('\\', "/");
+ format!("'{abs}'")
+ }
+}
+
+/// Convert an absolute file path to a static PHP path expression using `__DIR__ . '/..` form.
+fn path_to_static_expr(file: &Path, vendor_dir: &Path, project_dir: &Path) -> String {
+ if let Ok(rel) = file.strip_prefix(vendor_dir) {
+ let rel_str = rel.to_string_lossy().replace('\\', "/");
+ format!("__DIR__ . '/..' . '/{rel_str}'")
+ } else if let Ok(rel) = file.strip_prefix(project_dir) {
+ let rel_str = rel.to_string_lossy().replace('\\', "/");
+ format!("__DIR__ . '/../..' . '/{rel_str}'")
+ } else {
+ let abs = file.to_string_lossy().replace('\\', "/");
+ format!("'{abs}'")
+ }
+}
+
+/// Scan PSR-4 and PSR-0 directories for class declarations (used in optimize mode).
+///
+/// Returns `(dynamic_classmap, static_classmap, psr_violations)`.
+fn scan_psr_for_classmap(
+ psr4: &BTreeMap<String, Vec<String>>,
+ psr0: &BTreeMap<String, Vec<String>>,
+ vendor_dir: &Path,
+ project_dir: &Path,
+ excluded: &[String],
+) -> (
+ BTreeMap<String, String>,
+ BTreeMap<String, String>,
+ Vec<String>,
+) {
+ let mut dyn_map: BTreeMap<String, String> = BTreeMap::new();
+ let mut static_map: BTreeMap<String, String> = BTreeMap::new();
+ let mut violations: Vec<String> = Vec::new();
+
+ // Helper: resolve a PHP path expression to an absolute path.
+ let resolve = |expr: &str| -> Option<PathBuf> {
+ // Expressions look like:
+ // $vendorDir . '/psr/log/src'
+ // $baseDir . '/src'
+ // __DIR__ . '/..' . '/psr/log/src'
+ // __DIR__ . '/../..' . '/src'
+ if let Some(rest) = expr.strip_prefix("$vendorDir . '") {
+ let rel = rest.trim_end_matches('\'');
+ Some(vendor_dir.join(rel.trim_start_matches('/')))
+ } else if let Some(rest) = expr.strip_prefix("$baseDir . '") {
+ let rel = rest.trim_end_matches('\'');
+ Some(project_dir.join(rel.trim_start_matches('/')))
+ } else if expr == "$vendorDir" {
+ Some(vendor_dir.to_path_buf())
+ } else if expr == "$baseDir" {
+ Some(project_dir.to_path_buf())
+ } else {
+ None
+ }
+ };
+
+ // Scan PSR-4 dirs
+ for (ns, paths) in psr4 {
+ for path_expr in paths {
+ if let Some(abs_dir) = resolve(path_expr) {
+ let files = collect_php_files(&abs_dir, excluded, vendor_dir, project_dir);
+ for file in files {
+ match crate::php_scanner::find_classes(&file) {
+ Ok(classes) => {
+ for class in classes {
+ // PSR-4 validation
+ let file_str = file.to_string_lossy();
+ let dir_str = abs_dir.to_string_lossy();
+ let base_ns = ns.as_str();
+ if !crate::php_scanner::validate_psr4_class(
+ &class, base_ns, &file_str, &dir_str,
+ ) {
+ violations.push(format!(
+ "Class {class} in {file_str} does not comply with PSR-4 (namespace prefix: {ns})"
+ ));
+ }
+ let dyn_expr = path_to_php_expr(&file, vendor_dir, project_dir);
+ let static_expr =
+ path_to_static_expr(&file, vendor_dir, project_dir);
+ dyn_map.entry(class.clone()).or_insert(dyn_expr);
+ static_map.entry(class).or_insert(static_expr);
+ }
+ }
+ Err(_) => continue,
+ }
+ }
+ }
+ }
+ }
+
+ // Scan PSR-0 dirs
+ for (ns, paths) in psr0 {
+ for path_expr in paths {
+ if let Some(abs_dir) = resolve(path_expr) {
+ let files = collect_php_files(&abs_dir, excluded, vendor_dir, project_dir);
+ for file in files {
+ match crate::php_scanner::find_classes(&file) {
+ Ok(classes) => {
+ for class in classes {
+ let file_str = file.to_string_lossy();
+ let dir_str = abs_dir.to_string_lossy();
+ if !crate::php_scanner::validate_psr0_class(
+ &class, &file_str, &dir_str,
+ ) {
+ violations.push(format!(
+ "Class {class} in {file_str} does not comply with PSR-0 (namespace prefix: {ns})"
+ ));
+ }
+ let dyn_expr = path_to_php_expr(&file, vendor_dir, project_dir);
+ let static_expr =
+ path_to_static_expr(&file, vendor_dir, project_dir);
+ dyn_map.entry(class.clone()).or_insert(dyn_expr);
+ static_map.entry(class).or_insert(static_expr);
+ }
+ }
+ Err(_) => continue,
+ }
+ }
+ }
+ }
+ }
+
+ (dyn_map, static_map, violations)
+}
+
+/// Generate `vendor/composer/platform_check.php`.
+///
+/// Returns `None` if mode is `Disabled` or there are no relevant requirements.
+fn generate_platform_check(
+ packages: &[LockedPackage],
+ root_require: Option<&serde_json::Value>,
+ mode: &PlatformCheckMode,
+ dev_package_names: &HashSet<String>,
+) -> Option<String> {
+ if matches!(mode, PlatformCheckMode::Disabled) {
+ return None;
+ }
+
+ // Collect PHP version constraint from root require
+ let mut php_constraint: Option<String> = None;
+ if let Some(req_obj) = root_require.and_then(|v| v.as_object())
+ && let Some(v) = req_obj.get("php").and_then(|v| v.as_str())
+ {
+ php_constraint = Some(v.to_string());
+ }
+
+ // Collect extension requirements from packages (prod only)
+ let mut ext_reqs: Vec<(String, String)> = Vec::new();
+ if matches!(mode, PlatformCheckMode::Full) {
+ for pkg in packages {
+ let is_dev = dev_package_names.contains(&pkg.name.to_lowercase());
+ if is_dev {
+ continue;
+ }
+ for (req_name, req_constraint) in &pkg.require {
+ let lower = req_name.to_lowercase();
+ if lower.starts_with("ext-") {
+ ext_reqs.push((req_name.clone(), req_constraint.clone()));
+ }
+ }
+ }
+ ext_reqs.sort();
+ ext_reqs.dedup();
+ }
+
+ if php_constraint.is_none() && ext_reqs.is_empty() {
+ return None;
+ }
+
+ let mut out = String::new();
+ out.push_str("<?php\n\n");
+ out.push_str("// platform_check.php @generated by Composer\n\n");
+ out.push_str("$issues = array();\n\n");
+
+ if let Some(ref constraint) = php_constraint {
+ // Emit a simple PHP version check
+ let escaped = php_escape(constraint);
+ out.push_str(&format!("// PHP version check: {constraint}\n"));
+ out.push_str("if (!(PHP_VERSION_ID >= 50600)) {\n");
+ out.push_str(&format!(
+ " $issues[] = 'Your Composer dependencies require a PHP version \"{escaped}\". You are running ' . PHP_VERSION . '.';\n"
+ ));
+ out.push_str("}\n\n");
+ }
+
+ for (ext_name, _constraint) in &ext_reqs {
+ let ext_short = ext_name.trim_start_matches("ext-");
+ let escaped_ext = php_escape(ext_short);
+ out.push_str(&format!("if (!extension_loaded('{escaped_ext}')) {{\n"));
+ out.push_str(&format!(
+ " $issues[] = 'Your Composer dependencies require the \"{escaped_ext}\" PHP extension to be installed.';\n"
+ ));
+ out.push_str("}\n\n");
+ }
+
+ out.push_str("if ($issues) {\n");
+ out.push_str(" if (!headers_sent()) {\n");
+ out.push_str(" header('HTTP/1.1 500 Internal Server Error');\n");
+ out.push_str(" }\n");
+ out.push_str(" if (!ini_get('display_errors')) {\n");
+ out.push_str(" if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {\n");
+ out.push_str(" fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL);\n");
+ out.push_str(" } elseif (!headers_sent()) {\n");
+ out.push_str(" echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL;\n");
+ out.push_str(" }\n");
+ out.push_str(" }\n");
+ out.push_str(" trigger_error(\n");
+ out.push_str(
+ " 'Composer detected issues in your platform: ' . implode(' ', $issues),\n",
+ );
+ out.push_str(" E_USER_ERROR\n");
+ out.push_str(" );\n");
+ out.push_str("}\n");
+
+ Some(out)
+}
+
/// Generate `vendor/composer/autoload_real.php`.
-fn generate_autoload_real(suffix: &str, has_files: bool, classmap_authoritative: bool) -> String {
+fn generate_autoload_real(
+ suffix: &str,
+ has_files: bool,
+ classmap_authoritative: bool,
+ apcu: bool,
+ apcu_prefix: Option<&str>,
+ has_platform_check: bool,
+) -> String {
let mut out = String::new();
out.push_str("<?php\n\n");
out.push_str("// autoload_real.php @generated by Composer\n\n");
@@ -459,6 +815,9 @@ fn generate_autoload_real(suffix: &str, has_files: bool, classmap_authoritative:
out.push_str(&format!(
" spl_autoload_unregister(array('ComposerAutoloaderInit{suffix}', 'loadClassLoader'));\n\n"
));
+ if has_platform_check {
+ out.push_str(" require __DIR__ . '/platform_check.php';\n");
+ }
out.push_str(" require __DIR__ . '/autoload_static.php';\n");
out.push_str(&format!(
" call_user_func(\\Composer\\Autoload\\ComposerStaticInit{suffix}::getInitializer($loader));\n\n"
@@ -469,6 +828,12 @@ fn generate_autoload_real(suffix: &str, has_files: bool, classmap_authoritative:
out.push_str(" $loader->setClassMapAuthoritative(true);\n");
}
+ if apcu {
+ let prefix = apcu_prefix.unwrap_or(suffix);
+ let escaped = php_escape(prefix);
+ out.push_str(&format!(" $loader->setApcuPrefix('{escaped}');\n"));
+ }
+
if has_files {
out.push('\n');
out.push_str(&format!(
@@ -654,7 +1019,7 @@ pub fn generate(config: &AutoloadConfig) -> anyhow::Result<()> {
};
// 3. Collect autoload data
- let (data, static_data) = collect_autoloads(
+ let (mut data, mut static_data) = collect_autoloads(
&installed,
root_autoload.as_ref(),
root_autoload_dev.as_ref(),
@@ -662,6 +1027,96 @@ pub fn generate(config: &AutoloadConfig) -> anyhow::Result<()> {
config.dev_mode,
);
+ // 3a. Read classmap dirs declared in composer.json
+ let excluded: Vec<String> = root_autoload
+ .as_ref()
+ .and_then(|v| v.get("exclude-from-classmap"))
+ .and_then(|v| v.as_array())
+ .map(|arr| {
+ arr.iter()
+ .filter_map(|v| v.as_str().map(|s| s.to_string()))
+ .collect()
+ })
+ .unwrap_or_default();
+
+ // Scan explicit classmap dirs from all packages
+ let mut classmap_dirs: Vec<PathBuf> = Vec::new();
+
+ // Collect classmap dirs from installed packages
+ for pkg in &installed.packages {
+ if let Some(autoload_val) = &pkg.autoload
+ && let Some(cm_arr) = autoload_val.get("classmap").and_then(|v| v.as_array())
+ {
+ for cm_val in cm_arr {
+ if let Some(cm_path) = cm_val.as_str() {
+ let abs = config.vendor_dir.join(&pkg.name).join(cm_path);
+ classmap_dirs.push(abs);
+ }
+ }
+ }
+ }
+
+ // Collect classmap dirs from root autoload
+ if let Some(autoload_val) = root_autoload.as_ref()
+ && let Some(cm_arr) = autoload_val.get("classmap").and_then(|v| v.as_array())
+ {
+ for cm_val in cm_arr {
+ if let Some(cm_path) = cm_val.as_str() {
+ let abs = config.project_dir.join(cm_path);
+ classmap_dirs.push(abs);
+ }
+ }
+ }
+
+ // Scan classmap dirs
+ if !classmap_dirs.is_empty() {
+ let scanned = scan_classmap_dirs(
+ &classmap_dirs,
+ &config.vendor_dir,
+ &config.project_dir,
+ &excluded,
+ );
+ for (class, path_expr) in scanned {
+ // Also generate the static expression
+ // We store the dynamic expression in data.classmap; static_data.classmap
+ // will be populated similarly. For now we insert into both.
+ data.classmap.entry(class.clone()).or_insert(path_expr);
+ // Generate corresponding static expr by replacing dynamic prefixes
+ // (static_data classmap is populated in the static pass below)
+ }
+ }
+
+ // 3b. Optimize mode: scan PSR-4/PSR-0 dirs for classmap
+ let do_optimize = config.optimize || config.classmap_authoritative;
+ let mut psr_violations: Vec<String> = Vec::new();
+
+ if do_optimize {
+ let (opt_dyn, opt_static, violations) = scan_psr_for_classmap(
+ &data.psr4,
+ &data.psr0,
+ &config.vendor_dir,
+ &config.project_dir,
+ &excluded,
+ );
+ psr_violations = violations;
+ for (class, path_expr) in opt_dyn {
+ data.classmap.entry(class).or_insert(path_expr);
+ }
+ for (class, path_expr) in opt_static {
+ static_data.classmap.entry(class).or_insert(path_expr);
+ }
+ }
+
+ // 3c. Handle strict-psr violations
+ if config.strict_psr && !psr_violations.is_empty() {
+ for violation in &psr_violations {
+ eprintln!("PSR violation: {violation}");
+ }
+ return Err(anyhow::anyhow!(
+ "PSR mapping violations detected (--strict-psr). Run without --strict-psr to ignore."
+ ));
+ }
+
// 4. Generate and write files
let composer_dir = config.vendor_dir.join("composer");
std::fs::create_dir_all(&composer_dir)?;
@@ -689,14 +1144,94 @@ pub fn generate(config: &AutoloadConfig) -> anyhow::Result<()> {
}
}
+ // 4a. Generate platform_check.php if needed
+ let dev_package_names_set: HashSet<String> = installed
+ .dev_package_names
+ .iter()
+ .map(|n| n.to_lowercase())
+ .collect();
+
+ // Re-read composer.json for root require (not from autoload, but from root "require" key)
+ let root_require_val: Option<serde_json::Value> = if composer_json_path.exists() {
+ let content = std::fs::read_to_string(&composer_json_path)?;
+ let value: serde_json::Value = serde_json::from_str(&content)?;
+ value.get("require").cloned()
+ } else {
+ None
+ };
+
+ let all_locked: Vec<LockedPackage> = {
+ // Collect locked packages from installed for platform check
+ // (installed.packages are LockedPackage-compatible via InstalledPackageEntry)
+ // We'll build minimal LockedPackage-like data from installed entries
+ installed
+ .packages
+ .iter()
+ .map(|p| crate::lockfile::LockedPackage {
+ name: p.name.clone(),
+ version: p.version.clone(),
+ version_normalized: p.version_normalized.clone(),
+ source: None,
+ dist: None,
+ require: std::collections::BTreeMap::new(),
+ require_dev: std::collections::BTreeMap::new(),
+ conflict: std::collections::BTreeMap::new(),
+ suggest: None,
+ package_type: p.package_type.clone(),
+ autoload: p.autoload.clone(),
+ autoload_dev: None,
+ license: None,
+ description: None,
+ homepage: None,
+ keywords: None,
+ authors: None,
+ support: None,
+ funding: None,
+ time: None,
+ extra_fields: std::collections::BTreeMap::new(),
+ })
+ .collect()
+ };
+
+ let effective_mode = if config.ignore_platform_reqs {
+ PlatformCheckMode::Disabled
+ } else {
+ config.platform_check.clone()
+ };
+
+ let platform_check_content = generate_platform_check(
+ &all_locked,
+ root_require_val.as_ref(),
+ &effective_mode,
+ &dev_package_names_set,
+ );
+ let has_platform_check = platform_check_content.is_some();
+
+ if let Some(content) = platform_check_content {
+ std::fs::write(composer_dir.join("platform_check.php"), content)?;
+ } else {
+ let pc_path = composer_dir.join("platform_check.php");
+ if pc_path.exists() {
+ std::fs::remove_file(pc_path)?;
+ }
+ }
+
let has_files = !data.files.is_empty();
+ let use_apcu = config.apcu || config.apcu_prefix.is_some();
std::fs::write(
composer_dir.join("autoload_static.php"),
generate_autoload_static(&static_data, &config.suffix),
)?;
std::fs::write(
composer_dir.join("autoload_real.php"),
- generate_autoload_real(&config.suffix, has_files, config.classmap_authoritative),
+ generate_autoload_real(
+ &config.suffix,
+ has_files,
+ config.classmap_authoritative,
+ use_apcu,
+ config.apcu_prefix.as_deref(),
+ has_platform_check,
+ ),
)?;
std::fs::write(
config.vendor_dir.join("autoload.php"),
@@ -1028,7 +1563,7 @@ mod tests {
#[test]
fn test_generate_autoload_real_with_files() {
- let output = generate_autoload_real("abc123", true, false);
+ let output = generate_autoload_real("abc123", true, false, false, None, false);
assert!(output.contains("class ComposerAutoloaderInitabc123"));
assert!(output.contains("ComposerStaticInitabc123::$files"));
assert!(output.contains("$requireFile"));
@@ -1037,12 +1572,36 @@ mod tests {
#[test]
fn test_generate_autoload_real_without_files() {
- let output = generate_autoload_real("abc123", false, false);
+ let output = generate_autoload_real("abc123", false, false, false, None, false);
assert!(output.contains("class ComposerAutoloaderInitabc123"));
assert!(!output.contains("$filesToLoad"));
assert!(!output.contains("__composer_autoload_files"));
}
+ #[test]
+ fn test_generate_autoload_real_apcu() {
+ let output = generate_autoload_real("abc123", false, false, true, None, false);
+ assert!(output.contains("setApcuPrefix('abc123')"));
+ }
+
+ #[test]
+ fn test_generate_autoload_real_apcu_custom_prefix() {
+ let output = generate_autoload_real("abc123", false, false, true, Some("myprefix"), false);
+ assert!(output.contains("setApcuPrefix('myprefix')"));
+ }
+
+ #[test]
+ fn test_generate_autoload_real_platform_check() {
+ let output = generate_autoload_real("abc123", false, false, false, None, true);
+ assert!(output.contains("require __DIR__ . '/platform_check.php'"));
+ }
+
+ #[test]
+ fn test_generate_autoload_real_no_platform_check() {
+ let output = generate_autoload_real("abc123", false, false, false, None, false);
+ assert!(!output.contains("platform_check.php"));
+ }
+
// -------------------------------------------------------------------------
// generate_installed_php tests
// -------------------------------------------------------------------------
@@ -1111,6 +1670,12 @@ mod tests {
dev_mode: false,
suffix: "abc123def456".to_string(),
classmap_authoritative: false,
+ optimize: false,
+ apcu: false,
+ apcu_prefix: None,
+ strict_psr: false,
+ platform_check: PlatformCheckMode::Disabled,
+ ignore_platform_reqs: false,
};
generate(&config).unwrap();
@@ -1207,6 +1772,12 @@ mod tests {
dev_mode: false,
suffix: "test".to_string(),
classmap_authoritative: false,
+ optimize: false,
+ apcu: false,
+ apcu_prefix: None,
+ strict_psr: false,
+ platform_check: PlatformCheckMode::Disabled,
+ ignore_platform_reqs: false,
};
generate(&config).unwrap();