Modules: Using Internal

Internal and module level visibility

One goal of using modules is to separate a code unit's public and private APIs. If a file is part of a module, its toplevel functions, classes, interfaces, traits, enums, and typedefs can be marked internal. An internal toplevel symbol cannot be referenced outside of its module. If a symbol is not internal, it is public, and it's part of the public API of the module. You can optionally use the public keyword on toplevel symbols to denote them as public.

module foo;
internal class FooInternal {}
internal trait TFoo {}
internal interface IFoo {}
internal newtype TInternal = FooInternal;
internal function foo_internal(): void {}
module bar;
// error: FooInternal is internal, it cannot be referenced outside of its module
public function foo(): FooInternal {}

Methods and properties within classes also gain a new visibility internal, which means that they can only be accessed or called within the module.

module foo;
public class FooPublic {
    internal function foo(): void {
        echo "foo!\n";
    }
    internal int $prop = 5;
    public function bar(): void {
        echo "bar!\n";
    }
}

An internal method or property can be called anywhere from within a module. internal replaces the visibility keyword of the method or propertiy (i.e. protected, public or private). Note that method and property visibilities do not have to match the visibility of the class itself: an internal class can have public methods, and a public class can have internal methods. You can think of the visibility on a toplevel symbol to represent where a symbol is allowed to be referenced, whereas the visibility of a method or property to represent where that individual member can be accessed.

If you try calling an internal method or accessing a property from outside of the module, you'll get a runtime error.

module bar;
<<__EntryPoint>>
function test(): void {
    $x = new FooPublic(); // ok since Foo is a public class
    $x->bar(); // prints "bar!"
    $x->foo(); // error, foo is an internal method being called from outside the module.
    $x->prop = 5; // error, $x::prop is an internal property being accessed outside of the module.
}

You'll also get an error if you reference an internal symbol in any code outside of the module it's defined in (i.e., in typehints, constructor classnames, is/as statements):

module bar;
<<__EntryPoint>>
function test(IFoo $x): void {
            //^^^^ error, IFoo is internal to module foo
   $a = new FooInternal(); // error
   $b = FooInternal::class; // error
   $c = $x as FooInternal; // error
   $d = foo_internal<>; // error
}

We will go over the inheritance and override rules in a different section.

Referencing internal symbols in your public API

Public symbols within your module generally cannot reference internal symbols directly.

module foo;
internal class Secret {
    internal function mySecret(): int {
        return 42;
    }
}
public function foo(): Secret { // error, public API users wouldn't know what a Secret is
    return new Secret();
}

In order to expose Secret to outside users, you can use a public interface.

module foo;
public interface PublicFoo {
    public function myPublic(): int;
}
internal class Secret implements PublicFoo {
    public function myPublic(): int {
        return $this->mySecret();
    }
    internal function mySecret(): int {
        return 42;
    }
}
public function foo(): PublicFoo { //
    return new Secret();
}

At runtime, if code from another module calls foo, it will receive a Secret object. However, statically, any function that calls foo must respect the defined PublicFoo interface.

module bar;
<<__EntryPoint>>
public function test(): void {
    $x = foo(); // $x has type Secret at runtime
    $x->myPublic(); // returns 42
    $x->mySecret(); // typechecker error, $x is type Public, it does not have a method called mySecret().
}
Was This Page Useful?
Thank You!
Thank You! If you'd like to share more feedback, please file an issue.