Como uso namespaces PHP com autoload?

104

Recebo este erro quando tento usar autoload e namespaces:

Erro fatal: Classe 'Class1' não encontrada em /usr/local/www/apache22/data/public/php5.3/test.php na linha 10

Alguém pode me dizer o que estou fazendo de errado?

Aqui está o meu código:

Class1.php:

<?php

namespace Person\Barnes\David
{
    class Class1
    {
        public function __construct()
        {
            echo __CLASS__;
        }
    }
}

?>

test.php:

<?php

function __autoload($class)
{
    require $class . '.php';
}

use Person\Barnes\David;

$class = new Class1();

?>
David Barnes
fonte

Respostas:

119

Class1 não está no escopo global.

Veja abaixo um exemplo de trabalho:

<?php

function __autoload($class)
{
    $parts = explode('\\', $class);
    require end($parts) . '.php';
}

use Person\Barnes\David as MyPerson;

$class = new MyPerson\Class1();

Editar (14/12/2009):

Só para esclarecer, meu uso de "usar ... como" foi para simplificar o exemplo.

A alternativa era a seguinte:

$class = new Person\Barnes\David\Class1();

ou

use Person\Barnes\David\Class1;

// ...

$class = new Class1();
Tanerkay
fonte
1
Você não tem que usar AS. Não é por isso que essa solução funciona. Você poderia facilmente fazer: use Person\Barnes\David\Class1;(que é equivalente a use Person\Barnes\David\Class1 as Class1;).
cartbeforehorse,
1
Obrigado, isso funciona. Mas não consigo entender por que podemos apenas usar $ class = new Class1 (); quando já definimos "use Person \ Barnes \ David;" antes?
user345602
4
@ user346665 você deve usar use Person\Barnes\David\Class1;para fazer $class = new Class1();. Com use Person\Barnes\David;você deve fazer $class = new David\Class1();. A usepalavra-chave por si só é equivalente a use Person\Barnes\David\Class1 as Class1;ou use Person\Barnes\David as David;, respectivamente, para cada exemplo.
Justin C
Para quem lê em 2018, use a solução @prince-billy-graham com spl_autoload_register
Bruno de Oliveira
26

Conforme mencionado Pascal MARTIN, você deve substituir o '\' por DIRECTORY_SEPARATOR por exemplo:

$filename = BASE_PATH . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
include($filename);

Também sugiro que você reorganize a estrutura do diretório, para tornar o código mais legível. Esta pode ser uma alternativa:

Estrutura do diretório:

ProjectRoot
 |- lib

Arquivo: /ProjectRoot/lib/Person/Barnes/David/Class1.php

<?php
namespace Person\Barnes\David
class Class1
{
    public function __construct()
    {
        echo __CLASS__;
    }
}
?>
  • Crie o subdiretório para cada namespace definido.

Arquivo: /ProjectRoot/test.php

define('BASE_PATH', realpath(dirname(__FILE__)));
function my_autoloader($class)
{
    $filename = BASE_PATH . '/lib/' . str_replace('\\', '/', $class) . '.php';
    include($filename);
}
spl_autoload_register('my_autoloader');

use Person\Barnes\David as MyPerson;
$class = new MyPerson\Class1();
  • Eu usei a recomendação do php 5 para a declaração do autoloader. Se você ainda estiver com o PHP 4, substitua-o pela sintaxe antiga: function __autoload ($ class)
Kostanos
fonte
18

Sua __autoloadfunção receberá o nome completo da classe, incluindo o nome do namespace.

Isso significa que, no seu caso, a __autoloadfunção receberá ' Person\Barnes\David\Class1', e não apenas ' Class1'.

Portanto, você tem que modificar seu código de carregamento automático para lidar com esse tipo de nome "mais complicado"; uma solução frequentemente usada é organizar seus arquivos usando um nível de diretório por "nível" de namespaces e, ao carregar automaticamente, substituir ' \' no nome do namespace por DIRECTORY_SEPARATOR.

Pascal MARTIN
fonte
1
Não foi isso que encontrei. Quando coloquei a instrução die ($ class); na função __autoload, imprimiu 'Class1 "', não 'Person \ Barnes \ David \ Class1'
David Barnes
Verdade. O parâmetro $ class do autoload é o nome da classe conforme escrito na chamada do construtor.
tishma de
1
Downvote para "Sua __autoloadfunção receberá o nome completo da classe, incluindo o nome do namespace" - isso só é verdade se você tiver explicitamente used a classe que está tentando fazer referência, não se você simplesmente used o namespace ao qual ela pertence. O erro do OP foi ter useremovido o namespace que contém uma classe e, então, esperar que sua função de carregamento automático passasse magicamente pelo caminho de classe completo de alguma forma. Essa resposta realmente não resolve o erro do OP.
Mark Amery
15

Eu faço algo assim: Veja este exemplo do GitHub

spl_autoload_register('AutoLoader');

function AutoLoader($className)
{
    $file = str_replace('\\',DIRECTORY_SEPARATOR,$className);

    require_once 'classes' . DIRECTORY_SEPARATOR . $file . '.php'; 
    //Make your own path, Might need to use Magics like ___DIR___
}
tika
fonte
3
Bom e simples. Se alguém estiver procurando por isso.)
dennis
3

Eu encontrei esta joia do Flysystem

spl_autoload_register(function($class) {
    $prefix = 'League\\Flysystem\\';

    if ( ! substr($class, 0, 17) === $prefix) {
        return;
    }

    $class = substr($class, strlen($prefix));
    $location = __DIR__ . 'path/to/flysystem/src/' . str_replace('\\', '/', $class) . '.php';

    if (is_file($location)) {
        require_once($location);
    }
});
Boksiora
fonte
3

Vejo que as funções de carregamento automático recebem apenas o nome de classe "completo" - com todos os namespaces que o precedem - nos dois casos a seguir:

[a] $a = new The\Full\Namespace\CoolClass();

[b] use The\Full\Namespace as SomeNamespace; (at the top of your source file) followed by $a = new SomeNamespace\CoolClass();

Vejo que as funções de carregamento automático NÃO recebem o nome de classe completo no seguinte caso:

[c] use The\Full\Namespace; (at the top of your source file) followed by $a = new CoolClass();

ATUALIZAÇÃO: [c] é um erro e não é assim que os namespaces funcionam. Posso relatar que, em vez de [c], os dois casos a seguir também funcionam bem:

[d] use The\Full\Namespace; (at the top of your source file) followed by $a = new Namespace\CoolClass();

[e] use The\Full\Namespace\CoolClass; (at the top of your source file) followed by $a = new CoolClass();

Espero que isto ajude.

Daniel Rhodes
fonte
Como uma observação lateral, a usepalavra - chave não funciona corretamente na interface de linha de comando interativa do PHP ( php --interactive);
Andrew Larsson,
3

Eu uso este hack simples em uma linha:

spl_autoload_register(function($name){
        require_once 'lib/'.str_replace('\\','/',$name).'.php';
    });
princebillyGK
fonte
1

teve o mesmo problema e acabei de descobrir:

Quando você cria uma estrutura de subpasta correspondendo aos namespaces das classes que as contêm, você nunca terá que definir um autoloader.

    spl_autoload_extensions(".php"); // comma-separated list
    spl_autoload_register();

Funcionou como um encanto

Mais informações aqui: http://www.php.net/manual/en/function.spl-autoload-register.php#92514

EDIT: isso causa problema no Linux por causa da barra invertida ... Veja aqui a solução de trabalho por immeëmosol

O namespace Autoload funciona no Windows, mas não no Linux

JohnWolf
fonte
1

Usar tem um problema, embora seja de longe o método mais rápido, ele também espera que todos os seus nomes de arquivo estejam em minúsculas.

spl_autoload_extensions(".php");
spl_autoload_register();

Por exemplo:

Um arquivo contendo a classe SomeSuperClass precisaria ser nomeado somesuperclass.php, isso é um problema quando se usa um sistema de arquivos com distinção entre maiúsculas e minúsculas como o Linux, se seu arquivo se chama SomeSuperClass.php mas não é um problema no Windows.

Usar __autoload em seu código ainda pode funcionar com as versões atuais do PHP, mas espera que esse recurso se torne obsoleto e, finalmente, seja removido no futuro.

Então, quais são as opções restantes:

Esta versão funcionará com PHP 5.3 e superior e permite nomes de arquivo SomeSuperClass.php e somesuperclass.php. Se você estiver usando 5.3.2 e superior, este autoloader funcionará ainda mais rápido.

<?php

if ( function_exists ( 'stream_resolve_include_path' ) == false ) {
    function stream_resolve_include_path ( $filename ) {
        $paths = explode ( PATH_SEPARATOR, get_include_path () );
        foreach ( $paths as $path ) {
            $path = realpath ( $path . PATH_SEPARATOR . $filename );
            if ( $path ) {
                return $path;
            }
        }
        return false;
    }
}

spl_autoload_register ( function ( $className, $fileExtensions = null ) {
    $className = str_replace ( '_', '/', $className );
    $className = str_replace ( '\\', '/', $className );
    $file = stream_resolve_include_path ( $className . '.php' );
    if ( $file === false ) {
        $file = stream_resolve_include_path ( strtolower ( $className . '.php' ) );
    }
    if ( $file !== false ) {
        include $file;
        return true;
    }
    return false;
});
Michael Bush
fonte
2
como observação lateral, str_replace ([ '_','\\'] '/', $className );é duas vezes mais rápido do que dois str_replace
Itay Moav -Malimovka
Contanto que não importe se o arquivo php é maiúsculo / minúsculo, os diretórios ainda diferenciam maiúsculas de minúsculas
Mike
1

Recentemente, achei a resposta de tanerkuc muito útil! Só queria acrescentar que usar strrpos()+ substr()é um pouco mais rápido do que explode()+ end():

spl_autoload_register( function( $class ) {
    $pos = strrpos( $class, '\\' );
    include ( $pos === false ? $class : substr( $class, $pos + 1 ) ).'.php';
});
Alan
fonte
1

Vou apostar meus dois centavos para iniciantes ou qualquer outra coisa querendo uma configuração simples de spl_autoload_register () sem toda a teoria: Basta criar um arquivo php para cada classe, nomear esse arquivo php igual ao nome da sua classe e manter os arquivos da sua classe no mesmo diretório do arquivo php em questão, isso funcionará:

spl_autoload_register(function ($class_name) {
    require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . $class_name . '.php';
});

Pesquisar as peças dentro desta função deve responder como ela funciona. PS: Eu uso Linux, e isso funciona no Linux. O pessoal do Windows deve testá-lo primeiro.


fonte
1

https://thomashunter.name/blog/simple-php-namespace-friendly-autoloader-class/

Você vai querer colocar seus arquivos de classe em uma pasta chamada Classes, que está no mesmo diretório que o ponto de entrada em seu aplicativo PHP. Se as classes usarem namespaces, os namespaces serão convertidos na estrutura de diretório.

Ao contrário de muitos outros carregadores automáticos, os sublinhados não serão convertidos em estruturas de diretório (é complicado fazer PHP <5.3 pseudo namespaces junto com PHP> = 5.3 namespaces reais).

<?php
class Autoloader {
    static public function loader($className) {
        $filename = "Classes/" . str_replace("\\", '/', $className) . ".php";
        if (file_exists($filename)) {
            include($filename);
            if (class_exists($className)) {
                return TRUE;
            }
        }
        return FALSE;
    }
}
spl_autoload_register('Autoloader::loader');

Você vai querer colocar o seguinte código em seu script PHP principal (ponto de entrada):

require_once("Classes/Autoloader.php");

Aqui está um exemplo de layout de diretório:

index.php
Classes/
  Autoloader.php
  ClassA.php - class ClassA {}
  ClassB.php - class ClassB {}
  Business/
    ClassC.php - namespace Business; classC {}
    Deeper/
      ClassD.php - namespace Business\Deeper; classD {}
Yuvraj Singh Shekhawat
fonte
0
<?php
spl_autoload_register(function ($classname){
   // for security purpose
   //your class name should match the name of your class "file.php"
   $classname = str_replace("..", "", $classname);
   require_once __DIR__.DIRECTORY_SEPARATOR.("classes/$classname.class.php");
});
try {
  $new = new Class1();
} catch (Exception $e) {
   echo "error = ". $e->getMessage();
}
?>
Nevil Saul Blemuss
fonte
1
Embora este código possa responder à pergunta, fornecer contexto adicional sobre por que e / ou como este código responde à pergunta melhora seu valor a longo prazo.
Ethan