zephir-lang / zephir

Zephir is a compiled high-level language aimed to ease the creation of C-extensions for PHP
https://zephir-lang.com
MIT License
3.29k stars 466 forks source link

PSR-4 #1888

Open aleksandr-yulin opened 5 years ago

aleksandr-yulin commented 5 years ago

From documentation

In PHP, you can place code in any file, without a specific structure. In Zephir, every file must contain a class (and just one class). Every class must have a namespace, and the directory structure must match the names of the classes and namespaces used. (This is similar to PSR-4 autoloading conventions, except it’s enforced by the language itself.)

How about organize class autoload in config.json like composer.json ? Composer can provide classes from any path:

    "autoload": {
        "psr-4": {
            "VendorName\\ProjectName": "lib"
        },

So why not do something like this:

{
    "extension-name": "my_ext_name",
    "namespace": "VendorName\\ProjectName",
    "[ext|base|extension|etc...]-path": "lib", // don't reserved paths(ext), optional param default use 
namespace 

     // OR like composer
     "extension-name": "my_ext_name",
     "autoload": {
          "VendorName\\ProjectName": "lib",
          "VendorName\\ProjectName2": "lib2" // https://github.com/phalcon/zephir/issues/1883 
     }

Pretty project folder structure

my-lib:
    - ext
    - lib
        class.zep
    config.json

Exist project folder structure

my-lib:
    - ext
    - vendorname
        - projectname
            class.zep
    config.json

If you approve this, i can provide request for this issue in future

sergeyklay commented 5 years ago

/cc @phalcon/zephir-team

danhunsaker commented 5 years ago

I'd probably call the key namespace-paths instead of autoload, since we're not actually implementing an autoloader. But I like this approach:

{
     "extension-name": "my_ext_name",
     "namespace-paths": {
          "VendorName\\ProjectName": "lib",
          "VendorName\\ProjectName2": "lib2" // https://github.com/phalcon/zephir/issues/1883
     }
     ...
}

Any PR should, of course, add the namespace-paths key and logic, not replace the existing namespace key and logic. That probably means refactoring the existing logic to support either convention, but as long as both the new and old ways are supported simultaneously, that's perfectly fine.

sergeyklay commented 5 years ago

I agree with @danhunsaker

aleksandr-yulin commented 5 years ago

Something like that ?

./vendor/bin/zephir init Mylib\\First lib

    "namespace": "",
    "namespace-paths": {
        "Mylib\\First\\": "lib"
    },
    "name": "mylib_first",
lib-path/
    ext/
        kernel/
    lib/
    config.json

But which solution would be good for the name of the extension if there are several?

    "namespace": "",
    "namespace-paths": {
        "Mylib\\First\\": "lib",
        "Mylib\\Second\\": "lib2"
    },
    "name": "mylib_first",

in this case will be created two or one extension?

danhunsaker commented 5 years ago

Always one extension, no matter how many namespaces.

Also, if namespace-paths is used, namespace should not be.

aleksandr-yulin commented 5 years ago

Yep, but how about this issue #1883 ? I thought that they wanted to make two extensions from one project

Also, if namespace-paths is used, namespace should not be.

I thought something like that for compatibility:

    private function checkDirectory()
    {
        $namespace = $this->config->get('namespace');
        $namespaces = $this->config->get('namespace-paths');
        if (empty($namespaces) && !empty($namespace)) {
            if (is_string($namespace) === false) {
                throw new Exception(
                    sprintf('Invalid namespace config param, expected string, got %s',
                        is_object($namespace) ? get_class($namespace) : gettype($namespace)
                    )
                );
            }
            $namespaces[$namespace] = $namespace;
        }
danhunsaker commented 5 years ago

Ah. Missed that detail, somehow. In that case, I have a slightly different idea.

{
    "extensions": {
        "mylib_first": {
            "namespace": "Mylib\\First\\",
            "path": "lib"
        },
        "mylib_second": {
            "namespace": "Mylib\\Second\\",
            "path": "lib2"
        }
    },
    ...
}

And of course if extensions is used, name and namespace are not. Or maybe anything in extensions is used to override values in the root level of the structure? Allowing most/all settings available in the root to be overridden for any one extension, but otherwise apply to everything? Hard to think of a scenario where name and namespace wouldn't need to be unique across extensions...

aleksandr-yulin commented 5 years ago

Hard to think of a scenario where name and namespace wouldn't need to be unique across extensions...

Yep, but it is possible. The simple solution is refuse to using name and namespace on root level. Or we add path on root level too...

    "namespace": "Mylib\\First\\",
    "name": "mylib_first",
    "path": "lib",

    // if extensions not empty then root level was ignored

    "extensions": {
        "mylib_first": {
            "namespace": "Mylib\\First\\",
            "path": "lib"
        },
        "mylib_second": {
            "namespace": "Mylib\\Second\\",
            "path": "lib2"
        }
    },

What the difference extension-name and name into config file? May Be used one?

danhunsaker commented 5 years ago

I'd prefer not adding path to the root options; we can auto populate the value using the current logic internally, if we need to, but it shouldn't be configurable by the user at that level.

aleksandr-yulin commented 5 years ago

and what to do with the configuration of dependencies? such as constants-sources, prototype-dir, extra-sources, requires and etc... they may be different for extensions

have a suggestion: 1) add path to the root level form resolve this issue(for resolve this exception) 2) for multi extensions use its own configuration file

root-path/
    ext/
        kernel/
    lib-first/
       config.json
    lib-second/
       config.json

I have no idea how to do it otherwise

danhunsaker commented 5 years ago

What say we support all config options in extensions.* the same as we do at the root, except of course for the few that don't make sense nested (extensions itself, for example), and use the top-level versions as defaults that propagate down unless overridden? And then, just for the sake of conciseness, allow each extensions value to either be an object ({...}) or a string path to the additional config file for that extension ("lib-first/config.json").

Also, I retract my previous statement about not supporting path at the root level. May as well try to be consistent. (What I said earlier about computing it internally is still valid. Even if we didn't let users set something directly themselves, we would still set it internally in our own data structures. 'Not in the JSON' isn't the same as 'not in the array'.)