How Saeghe says “Hello World”.

Morteza
6 min readOct 29, 2022

--

Saeghe is a new PHP package manager that I created recently. You can check this article if the name is new to you. The current article will explain how it works and how you can use it.

Attention needed

Saeghe is a difficult name to pronounce. Therefore, the Saeghe project has been renamed to phpkg.

Please visit phpkg website at phpkg.com

How does it work?

Any projects have some common characteristics. They may need to have an entry point for starting to work. The index.php file is your entry point if you have a web application is an entry point. You may have another entry point for running commands within CLI. You can have more or fewer entry points based on how you structured your application. If you use a framework, most likely you have two, console and web entry points, in your project. Additionally, if you installed the phpunit, you have another entry point for running your tests. If you use composer, any files that need to add the requirestatement for the composer’s autoload file are your entry points.

They may need some class-structured files to be able to work. These are Classes, Final Classes, Abstract Classes, Interfaces, Enums, Attributes, and Traits. If a project has these files, they have to get imported to your project wherever you need to use them. Having a spl_autoload_register function that tells PHP what file should get imported when a specific class gets used is a common practice to import these files. If you use composer, this is what composer does for you. The composer makes a class map for your project classes and their dependency classes and adds a spl_autoload_register function containing this class map.

They can have some files containing functions and constants. These files must also get imported to your project’s files, just like class-structured files, where they get used. Again, if you work with the composer, these are files that you need to add in the autoload.filessection in the composer.jsonfile.

There might be some files or directories that are not related to your PHP project and you probably want to exclude them from your main project. Like the node_modulesdirectory or your package.jsonand package.lockif your project uses them.

You might want to have some executable files that are like entry points but they can get called directly from your console without thephpprefix. For example, if you install the phpstan in your project, there will be a file named phpstan that you can directly call it using ./vendor/bin/phpstan instead of the traditional php ./vendor/bin/phpstan call.

Projects can have dependencies on other libraries. The other libraries also have the characteristics mentioned above.

Now, Saeghe reads your project files and adds the required import statements to them. If the required file is class-structured, Saeghe uses the spl_autoload_register function for it, and if not, it adds a require statement for the file. In addition to class mapping, it can detect used functions and constants, so you don’t need to register them anywhere. You don’t need to do anything for your entry points and executable files since Saeghe will automatically add the required code.

Show me some code dude!

OK, I hear you. Let’s add some codes here. Consider a simple demo project containing the following files. For the sake of simplicity, I’m going to assume your project’s root directory is /var/www/demo but of course, you can put your project wherever you want.

An entry point file:

/var/www/demo/index.php

<?phpuse Application\Controllers\LoginController;$email = ‘email@example.com’;$password = ‘password’;var_dump((new LoginController)->login($email, $password));

The controller file:

/var/www/demo/app/Controllers/LoginController.php

<?phpnamespace Application\Controllers;use Application\Models\User;use \Exception;use function Hash\Password\verify;class LoginController{    public function login(string $email, string $password): User    {       $user = User::firstByEmail($email);        if (! verify($password, $user->password)) {            throw new Exception(‘We don\’t have a user with these email and password!’);        }        return $user;    }}

The User class:

/var/www/demo/app/Models/User.php

<?phpnamespace App\Models;use Hash\Password;class User{    public static function firstByEmail($email): static    {        $user = new static();        $user->email = $email;        $user->password = Password\hash(‘password’);        return $user;    }}

And finally:

/var/www/demo/helpers/Hash/Password.php

<?phpnamespace Hash\Password;function hash(string $password): string{    return password_hash($password);}function verify(string $pasword, string $hash): bool{    return password_verify($password, $hash);}

This code is your development code, and you can not run it. You need to build your project first to run the code. You need a saeghe.config.json file to build your project that can get initialized on a new project by running the init command on Saeghe.

Imagine having the below content insaeghe.config.json file in your project’s root directory:

/var/www/demo/saeghe.config.json

{  “map”: {    “Application”: “app”,    “Hash”: “helpers/Hash”,  },  “entry-points”: [“index.php”],  “excludes”: [],  “executables”: [],  “packages-directory”: “Packages”,  “packages”: []}

Now, if you run the build command on Saeghe, you will see a builds directory added to your project’s root. The builds directory also will contain a directory named development, and under this directory, you should see a copy of your project like so:

/var/www/demo/builds/development/index.php

<?phpspl_autoload_register(function ($class) {    $classes = [
‘Application\Controllers\LoginController’ => ‘/var/www/demo/app/Controllers/LoginController.php’,
‘Application\Models\User’ => ‘/var/www/demo/app/Models/User.php’, ]; if (array_key_exists($class, $classes)) { require $classes[$class]; }}, true, true);spl_autoload_register(function ($class) { $namespaces = [ ‘Application’ => ‘/var/www/demo/app’, ]; $realPath = null; foreach ($namespaces as $namespace => $path) { if (str_startrs_with($class, $namespace)) { $pos = strpos($class, $namespace); if ($pos !== false) { $realPath = substr($class, $path, $pos, strlen($namespace)); } $realpath = str_replace(“\\”, DIRECTORY_SEPARATOR, $realPath) . ‘.php’; require $realpath; return; } }});use Application\Controllers\LoginController;$email = ‘email@example.com’;$password = ‘password’;var_dump((new LoginController)->login($email, $password));

Built controller:

/var/www/demo/app/Controllers/LoginController.php

<?phpnamespace Application\Controllers;require_once ‘/var/www/demo/builds/development/Packages/hash-library-owner/hash-library-repo/src/Password.php’;use Application\Models\User;use \Exception;use function HashLibrary\Password\verify;class LoginController{    public function login(string $email, string $password): User    {        $user = User::firstByEmail($email);        if (! verify($password, $user->password)) {            throw new Exception(‘We don\’t have a user with these email and password!’);        }        return $user;    }}

Built User model:

/var/www/demo/builds/development/app/Models/User.php

<?phpnamespace App\Models;require_once ‘/var/www/demo/helpers/Hash/Password.php’;use Hash\Password;class User{    public static function firstByEmail($email): static    {        $user = new static();        $user->email = $email;        $user->password = Password\hash(‘password’);        return $user;    }}

And finally, the built Password.php file:

/var/www/demo/builds/development/helpers/Hash/Password.php

<?phpnamespace Hash\Password;function hash(string $password): string{    return password_hash($password);}function verify(string $pasword, string $hash): bool{    return password_verify($password, $hash);}

Now you can run your project inside the builds/development directory. If you use PHP’s built-in server, by running php -S localhost:8000 -t /var/www/demo/builds/development and visiting http://localhost:8000 you should see the output from the var_dump.

Despite this simple demo, I think you got the idea. You can have as many maps as you want, add many entry points and executable files, or install as many libraries as you need and you will see the same process for all of them.

Saeghe can add imports, so what?

Saeghe is a package manager, you can manage your required packages using their git URLs directly using Saeghe, and Saeghe imports them into your files where they get used, so you don’t need to import them manually. It builds your project, so you end up having a divided environment for your development and your executable project. You can develop using OOP and Functional programming and be sure the required files will get imported wherever needed. The ability to import functions using namespaces can make our PHP development experiences drastically different. You can also make two individual processes for building your project in the development environment and the production environment that opens new doors to a bunch of cool stuff, more on this later.

Sounds interesting, but how can I play with it?

If you think it is interesting, you can visit the documentation and start using it by following the documents. There are two ready-to-use Saeghe packages that you can install and play with them. The CLI package contains some useful functions to work with CLI and the test-runner is a simple package to help you write tests for your project and run them. Of course, there will be more supported packages from existing libraries for PHP soon and I’m working to send some PR for their maintainers to support installation using Saeghe.

You can also read this tutorial article.

Have fun.

--

--