phpkg Concepts
Understanding how phpkg works under the hood.
Core Philosophy
phpkg is built on three core principles:
- Git-First: Direct Git repository access, no central registry
- Function-First: Autoload namespaced functions alongside classes
- Build-First: Explicit build step creates optimized, portable artifacts
How phpkg Works
1. Package Management
Package Identification
phpkg supports multiple ways to identify packages:
Simplified Formats:
- owner/repo → GitHub packages
- repo → php-repos packages (assumes php-repos/owner)
Full URLs:
- https://github.com/owner/repo.git
- git@github.com:owner/repo.git
Package Resolution
When you run phpkg add owner/repo:
- Parse identifier: Determines if it's simplified or full URL
- Resolve URL: Converts simplified format to full Git URL
- Fetch metadata: Queries Git repository for tags/releases
- Select version: Chooses version (latest, specific, or development)
- Download: Clones repository to
Packages/owner/repo/ - Read config: Parses package's
phpkg.config.json(if exists) - Resolve dependencies: Uses CSP to find possible solutions, then SAT to select the best (lowest high version)
- Update config: Writes to
phpkg.config.jsonandphpkg.config-lock.json
Version Selection
- Latest: Fetches highest semantic version tag (when version is omitted)
- Specific: Uses exact tag (e.g.,
v1.2.3) - must be the complete version number - Development: Locks to the latest commit hash at the time of adding/updating (no tag required). When updating with
development, it moves to the newest commit hash.
2. Autoloading System
Namespace Mapping
phpkg uses namespace-to-directory mapping:
{
"map": {
"App": "src",
"App\\Utils": "src/utils"
}
}
This means:
App\namespace →src/directoryApp\Utils\namespace →src/utils/directory
Function Autoloading
Unlike Composer (PSR-4), phpkg autoloads both classes and functions:
// src/Utils.php
namespace App\Utils;
// This function is autoloaded!
function format($text) {
return strtoupper($text);
}
// This class is also autoloaded
class Formatter {
// ...
}
How it works:
- phpkg scans mapped directories
- Finds all PHP files
- Extracts namespace declarations
- Adds
require_oncestatements for functions and constants directly in files where they're used - Generates autoload file with class mappings
- Includes the import file in entry points
How Functions, Constants, and Classes Are Handled
Functions and Constants: require_once statements are added directly in the files where they are used. For example, if you use use function App\Utils\format; in src/Controller.php, phpkg adds require_once '/path/to/src/Utils.php'; directly in that file.
Classes: Autoloading is set up in the import file (phpkg.imports.php or custom import-file):
<?php
// Auto-generated by phpkg
// Classes only - functions and constants are handled inline
spl_autoload_register(function ($class) {
if ($class === 'App\Formatter') {
require_once __DIR__ . '/src/Formatter.php';
}
// ... more class mappings
});
This import file is automatically included in all entry points.
3. Build System
Build Process
When you run phpkg build:
- Read config: Loads
phpkg.config.json - Resolve packages: Reads installed packages from
phpkg.config-lock.json - Create build directory:
build/ - Copy files: Copies source files respecting
excludes - Copy packages: Copies package files to build
- Add function/constant requires: Adds
require_oncestatements directly in files where functions and constants are used - Generate class autoload: Creates
phpkg.imports.phpwith class mappings - Inject imports: Adds
requirestatement for the import file to all entry points - Create executables: Creates symlinks for
executables(Unix) or copies (Windows)
Build Output
Build (phpkg build):
- Creates
build/directory - Includes all source files (respecting
excludes) - Preserves directory structure
- Copies installed packages
- Generates autoload file
- Injects autoloading into entry points
Entry Point Injection
phpkg automatically injects autoloading into entry points:
Before build (public/index.php):
<?php
echo "Hello World";
After build (build/public/index.php):
<?php
require __DIR__ . '/../phpkg.imports.php';
echo "Hello World";
4. Dependency Resolution
How Dependency Resolution Works
phpkg uses a two-step process to resolve dependencies:
- CSP (Constraint Satisfaction Problem): Finds all possible solutions that satisfy the dependency requirements
- SAT (Boolean Satisfiability Problem): Selects the best solution from the possible options
Version Selection Strategy
When resolving dependencies, phpkg:
- Always selects the lowest high version that satisfies all requirements
- Does not use wildcards like Composer (no
^,~,>=, etc.) - Requires exact version specifications or uses the latest available version
Lock File
phpkg.config-lock.json stores:
- Exact versions installed
- Commit hashes
- Content checksums
- Owner and repository information
This ensures reproducible builds by locking exact package versions and commit hashes.
5. Standalone Execution
Run Command
phpkg run executes packages without installation:
- Download: Clones repository to temp directory
- Build: Creates temporary build
- Execute: Runs entry point
- Cleanup: Removes temp files (on reboot)
Use cases:
- CLI tools (no project needed)
- One-off scripts
- Testing packages
Serve Command
phpkg serve runs packages as web apps:
- Download: Clones repository
- Build: Creates temporary build
- Start server: Runs
php -S localhost:8000 - Handle signals: Graceful shutdown on Ctrl+C
- Cleanup: Removes temp files
Use cases:
- Web dashboards
- Local testing servers
- Demo applications
File Structure
Project Structure
my-project/
├── phpkg.config.json # Project configuration
├── phpkg.config-lock.json # Locked versions
├── src/ # Source code
│ └── App/
│ └── Utils.php
├── Packages/ # Installed packages
│ └── php-repos/
│ └── observer/
└── build/ # Build outputs
├── phpkg.imports.php
└── src/
└── public/ # Entry points
└── index.php
Config File Structure
{
"map": {
"App": "src"
},
"autoloads": ["src/helpers.php"],
"excludes": ["node_modules"],
"entry-points": ["public/index.php"],
"executables": {
"my-tool": "Packages/tool/run.php"
},
"import-file": "phpkg.imports.php",
"packages-directory": "Packages",
"packages": {
"https://github.com/owner/repo.git": "v1.0.0"
},
"aliases": {
"observer": "https://github.com/php-repos/observer.git"
}
}
Comparison with Composer
| Aspect | phpkg | Composer |
|---|---|---|
| Registry | None (Git direct) | Packagist (central) |
| Function Autoloading | ✅ Yes | ❌ No (PSR-4 classes only) |
| Build Step | ✅ Required | ❌ Not needed |
| Standalone Execution | ✅ Yes (run, serve) |
❌ No |
| Lock File | phpkg.config-lock.json |
composer.lock |
| Config File | phpkg.config.json |
composer.json |
| Vendor Directory | Packages/ (customizable) |
vendor/ (fixed) |
| Dependency Resolution | CSP + SAT solvers (lowest high version) | Semantic versioning with wildcards |
Workflow Patterns
Active Development Workflow
1. phpkg init # Initialize project
2. phpkg add packages # Add dependencies
3. phpkg build # Build project
4. phpkg watch # Auto-rebuild on changes
5. Edit code # Make changes
6. phpkg build # Rebuild before deployment
Standalone Tool Workflow
1. phpkg run tool # Run without installation
2. Or: phpkg serve app # Serve as web app
Team Collaboration Workflow
1. Developer A: phpkg add package
2. Commit phpkg.config.json and phpkg.config-lock.json
3. Developer B: git pull
4. Developer B: phpkg install # Gets same versions
Advanced Concepts
Package Configuration
Packages can have their own phpkg.config.json:
{
"map": {
"Package": "src"
},
"entry-points": ["bin/console"],
"executables": {
"console": "bin/console"
}
}
This allows packages to:
- Define their own namespace mappings
- Specify entry points
- Create executables
Package Dependencies
When a package declares dependencies in its phpkg.config.json, phpkg uses CSP and SAT solvers to resolve them:
- CSP solver: Finds all possible version combinations that satisfy requirements
- SAT solver: Selects the optimal solution (lowest high version that satisfies all constraints)
- Dependencies are automatically resolved and installed when you add a package
Build Optimization
You can optimize builds by:
- Excluding unnecessary files in
excludesconfig - Excluding test files, documentation, etc.
- Using
excludesto reduce build size
Understanding the Magic
Why Function Autoloading?
PHP's autoloading (PSR-4) only works for classes. phpkg extends this to functions by:
- Scanning: Finding all functions in namespaced files
- Mapping: Creating function-to-file mappings
- Lazy loading: Loading functions on first use
- Conflict detection: Warning on function name conflicts
Why Git-First?
- No registry: No single point of failure
- Direct access: Faster, more reliable
- Version control: Git tags provide versioning
- Flexibility: Works with any Git host
Why Build Step?
- Optimization: Creates optimized, production-ready files
- Portability: Build artifacts are self-contained
- Performance: Pre-generated autoloads are faster
- Control: Explicit control over what's included
Next Steps
- Getting Started - Put these concepts into practice
- Best Practices - Learn recommended patterns
- Troubleshooting - Solve common issues