0

I've read this:

We have this error:

 PHP Fatal error:  Cannot redeclare fcXXX()
 (previously declared in /xx.class.php:3007) in /xx.class.php on line 3014

Yes. line 3014. And in the code source of /xx.class.php there are some class declarations before the function fcXXX(), something like (in short):

class P1 {
...
}

class P2 {
...
}

function fcXXX() {
}

So what I dont get is that there should be an error of type "PHP Fatal error: Cannot redeclare class" before the fatal "Cannot redeclare function php".

What is more strange, is that if you read carefully, the problem arises at the end of the declaration of the function. Here's the code with the lines:

3007 function fcXXX($p) {
3008     $a = strpos($p, '.');
3009     if ($a) {
3010         return trim(substr($p, $a+1));
3011     } else {
3012         return trim($p);
3013     }
3014 }

Two strange things:

  1. in the function there's no "include" or "require" or whatever so theorically there could not be an error of this kind
  2. line 3014 raises the error in line 3007 (watch carefully the detailed error before).

Has anyone ever had this problem, and where to check/test to solve it? (Please dont comment on how to optimize / how bad the code is, because it's not mine and I wont modify it)

Community
  • 1
  • 1
Olivier Pons
  • 15,363
  • 26
  • 117
  • 213
  • 1
    How is this file being included? Is it being included from somewhere else, autoloaded from somewhere, cached somewhere, loaded by a framework, etc? Could do with seeing a little more about the implementation detail really. – nealio82 Aug 06 '13 at 11:18
  • 1
    first idea that comes to mind: **kill it with fire, before it lays eggs!** – tereško Aug 06 '13 at 11:19
  • are you trying to load this file using `include` or `require`? if so change it to `require_once` or `include_once` – bansi Aug 06 '13 at 11:19
  • Is it possibly a namespace issue? – JohnnyFaldo Aug 06 '13 at 11:23
  • It always errors on the last line as that's where PHP finishes processing the item (I think) – MDEV Aug 06 '13 at 11:29
  • @SmokeyPHP Thank you, this should solve 50% of the question – Olivier Pons Aug 06 '13 at 11:38
  • @OlivierPons Try putting `echo '
    '.print_r(debug_backtrace(),true),'
    ';` at the top of the file. Should show you any includes.
    – MDEV Aug 06 '13 at 11:41
  • @SmokeyPHP It's a production environment. We didn't manage to reproduce the problem neither in dev environment nor in qualif. environment. And of course, like all production environments, we just can "read" files. – Olivier Pons Aug 06 '13 at 11:52
  • @OlivierPons FYI, I've tested locally and it does indeed throw a dupe function error instead of a duped class error, even when the function is declared after the class. Strange behaviour indeed.. – MDEV Aug 06 '13 at 12:02

1 Answers1

2

You're probably recursively include or require-ing the definitions. Have did you set an autoloader? Do you know how they work, because you might want to create a file for each class definition (ASAP).
Consider this:

//index.php
include('someFile.php');
include('xx.class.php');

//someFile:
include('xx.class.php');//ERROR

//or even worse, in xx.Class.php:
include('someFile.php');//includes file that includes this file, which will include someFile again, which includes this file, which...

To avoid this, use include_once or require_once. This avoids your including the same file twice.

As to what causes the order of the errors:
The main reason why you get the error "cannot redeclare function" before you get the error cannot redeclare class is, I think, simply because of how PHP compiles the code.
PHP doesn't leave your code as is anymore. Your code is compiled to bytecode, so it's... shall we say... changed quite a bit in the process
functions just have to be parsed before PHP can move on to parsing the classes. Why? that's quite simply because PHP class methods are, essentially, functions, too.
They're called using the zend_object_handlers pointer, when calling a method, the zval is passed, allong with the zend_class_entry, in order for the zend_function to have access to all the data in scope (all the object properties and methods and what have you). I don't know how your C knowledge is, but if you want to test it:

ZEND_API zval* zend_call_method(
    zval **object_pp,
    zend_class_entry *obj_ce,
    zend_function **fn_proxy,
    const char *function_name,
    int function_name_len,
    zval **retval_ptr_ptr,
    int param_count,
    zval* arg1,
    zval* arg2 TSRMLS_DC
) {
  int result;
  zend_fcall_info fci;
  zval z_fname;
  zval *retval;
  HashTable *function_table;

  zval **params[2];

  params[0] = &arg1;
  params[1] = &arg2;

  fci.size = sizeof(fci);
  /* fci.function_table = NULL;
   * will be read form zend_class_entry of object if needed
   */
  fci.object_ptr = object_pp ? *object_pp : NULL;
  fci.function_name = &z_fname;
  fci.retval_ptr_ptr = retval_ptr_ptr ? retval_ptr_ptr : &retval;
  fci.param_count = param_count;
  fci.params = params;
  fci.no_separation = 1;
  fci.symbol_table = NULL;

  if (!fn_proxy && !obj_ce) {
      /* no interest in caching and no information
       * already present that is needed later inside
       * zend_call_function.
       */
      ZVAL_STRINGL(&z_fname, function_name, function_name_len, 0);
      fci.function_table = !object_pp ? EG(function_table) : NULL;
      result = zend_call_function(&fci, NULL TSRMLS_CC);
  } else {
      zend_fcall_info_cache fcic;

      fcic.initialized = 1;
      if (!obj_ce) {
          obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL;
      }
      if (obj_ce) {
          function_table = &obj_ce->function_table;
      } else {
          function_table = EG(function_table);
      }
      if (!fn_proxy || !*fn_proxy) {
          if (zend_hash_find(
              function_table, function_name,
              function_name_len+1,
              (void **) &fcic.function_handler) == FAILURE
          ) {
              /* error at c-level */
              zend_error(
                  E_CORE_ERROR,
                  "Couldn't find implementation for method %s%s%s",
                  obj_ce ? obj_ce->name : "",
                  obj_ce ? "::" : "", function_name
              );
          }
          if (fn_proxy) {
              *fn_proxy = fcic.function_handler;
          }
      } else {
          fcic.function_handler = *fn_proxy;
      }
      fcic.calling_scope = obj_ce;
      if (object_pp) {
          fcic.called_scope = Z_OBJCE_PP(object_pp);
      } else if (obj_ce &&
                 !(EG(called_scope) &&
                   instanceof_function(EG(called_scope), obj_ce TSRMLS_CC))) {
          fcic.called_scope = obj_ce;
      } else {
          fcic.called_scope = EG(called_scope);
      }
      fcic.object_ptr = object_pp ? *object_pp : NULL;
      result = zend_call_function(&fci, &fcic TSRMLS_CC);
  }
  if (result == FAILURE) {
       /* error at c-level */
       if (!obj_ce) {
           obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL;
       }
       if (!EG(exception)) {
           zend_error(
               E_CORE_ERROR,
               "Couldn't execute method %s%s%s",
               obj_ce ? obj_ce->name : "",
               obj_ce ? "::" : "", function_name
           );
       }
   }
   if (!retval_ptr_ptr) {
       if (retval) {
           zval_ptr_dtor(&retval);
       }
       return NULL;
   }
   return *retval_ptr_ptr;
}

As you can see here, in order for a method to be callable, all zend_function's must be defined. Since a constructor is a function, too, I suspect all the functions in your xx.Class.php file are parsed. Since the methods are defined within their own (class) scope, there can't be a name conflict (unless there are duplicate methods), before the zend_object is actually registered.

Besides, any decent bit of OO code registers an autoloader function. What would an object be if the parser didn't know what to make of that function keyword?
There's a lot more too it than just that, but I figure that must be why PHP notices why you're attemting to redefine a function before it notices you're redifining a class.

A couple of useful links, containing a more detailed description of how the zend engine works with functions/methods can be found here
It's always useful to keep a tab with the actual source at hand, too, so you can actually see the code they're talking about

Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • Worth noting that performance will take a hit when using the `..._once` functions – BenLanc Aug 06 '13 at 12:12
  • @MrJamin: just as it is worth noting that premature optimization is the root of all evil. The performance hit will be minimal (checking if the file has been required already or not is done at compile time), and can be reduced to an insignificant impact by caching (eg APC) – Elias Van Ootegem Aug 06 '13 at 12:15
  • "Have did you set an autoloader?" => This code is (1) prehistoric (8y.o.) (2) made by a boss who wanted to make 15e+89% profit (=> hire only students going out of school) (3) based on a framework done by someone whose first job was archeologist (no joke at all). So the answer is: in this context, there can't be clean code, and using autoloader is often part of well thought code. So in short: "no". – Olivier Pons Aug 06 '13 at 12:22
  • @OlivierPons: My deepest sympathies to you and your loved ones. I've noticed you do know some C, too. Would you agree that it's quite likely that the cause of the dupe function error being raised first is to be found because function definitions need to be processed first, or should I keep looking for an explanation? Lmao when I read about the archeologist's framework... what did he call it? _Death by Trowell?_ – Elias Van Ootegem Aug 06 '13 at 12:24
  • 1
    @EliasVanOotegem Thank you for your answer, and for the nice summry you've just done. Incredible as it may seem, this guy was the most skilled of all the people I've seen who worked on his "framework" (let's call it like that). – Olivier Pons Aug 06 '13 at 12:52
  • @OlivierPons: Funny how the world works, I trained as a journalist myself, only to find myself coding for a living. I might not be the best developer, but I have seen worse who were more _"clasically"_ schooled for the job :D Good luck anyways! – Elias Van Ootegem Aug 06 '13 at 12:55