<commit_before>Index: skeleton/lang.base.php
===================================================================
--- skeleton/lang.base.php	(revision 14768)
+++ skeleton/lang.base.php	(working copy)
@@ -125,6 +125,20 @@
     }
     // }}}
 
+    // {{{ public static void extensions(string class, string scope)
+    //     Registers extension methods for a certain scope
+    static function extensions($class, $scope) {
+      foreach (create(new XPClass($class))->getMethods() as $method) {
+        if (MODIFIER_STATIC & $method->getModifiers() && $method->numParameters() > 0) {
+          $param= $method->getParameter(0);
+          if ('self' === $param->getName()) {
+            self::$registry['ext'][$scope][xp::reflect($param->getTypeName())]= $class;
+          }
+        }
+      }
+    }
+    // }}}
+
     // {{{ public void gc([string file default NULL])
     //     Runs the garbage collector
     static function gc($file= NULL) {
@@ -391,12 +405,20 @@
   // {{{ void uses (string* args)
   //     Uses one or more classes
   function uses() {
+    $scope= NULL;
     foreach (func_get_args() as $str) {
-      xp::$registry['loader']->loadClass0($str);
+      $class= xp::$registry['loader']->loadClass0($str);
+      if (method_exists($class, '__import')) {
+        if (NULL === $scope) {
+          $trace= debug_backtrace(FALSE);
+          $scope= xp::reflect($trace[2]['args'][0]);
+        }
+        call_user_func(array($class, '__import'), $scope);
+      }
     }
   }
   // }}}
-  
+
   // {{{ void raise (string classname, var* args)
   //     throws an exception by a given class name
   function raise($classname) {
Index: skeleton/lang/Object.class.php
===================================================================
--- skeleton/lang/Object.class.php	(revision 14768)
+++ skeleton/lang/Object.class.php	(working copy)
@@ -93,7 +93,16 @@
       if ("\7" === $name{0}) {
         return call_user_func_array(array($this, substr($name, 1)), $args);
       }
-      throw new Error('Call to undefined method '.get_class($this).'::'.$name);
+      $t= debug_backtrace(FALSE);
+      $scope= $t[2]['class'];
+      if (isset(xp::$registry['ext'][$scope])) {
+        foreach (xp::$registry['ext'][$scope] as $type => $class) {
+          if (!$this instanceof $type) continue;
+          array_unshift($args, $this);
+          return call_user_func_array(array($class, $name), $args);
+        }
+      }
+      throw new Error('Call to undefined method '.$this->getClassName().'::'.$name.'() from scope '.xp::nameOf($scope));
     }
 
     /**
Index: ports/classes/net/xp_framework/unittest/core/extensions/ExtensionInvocationTest.class.php
===================================================================
--- ports/classes/net/xp_framework/unittest/core/extensions/ExtensionInvocationTest.class.php	(revision 0)
+++ ports/classes/net/xp_framework/unittest/core/extensions/ExtensionInvocationTest.class.php	(revision 0)
@@ -0,0 +1,53 @@
+<?php
+/* This class is part of the XP framework
+ *
+ * $Id$ 
+ */
+
+  uses(
+    'unittest.TestCase', <commit_msg>- Include ports/unittest/core.ini in patch<commit_after>Index: skeleton/lang.base.php
===================================================================
--- skeleton/lang.base.php	(revision 14768)
+++ skeleton/lang.base.php	(working copy)
@@ -125,6 +125,20 @@
     }
     // }}}
 
+    // {{{ public static void extensions(string class, string scope)
+    //     Registers extension methods for a certain scope
+    static function extensions($class, $scope) {
+      foreach (create(new XPClass($class))->getMethods() as $method) {
+        if (MODIFIER_STATIC & $method->getModifiers() && $method->numParameters() > 0) {
+          $param= $method->getParameter(0);
+          if ('self' === $param->getName()) {
+            self::$registry['ext'][$scope][xp::reflect($param->getTypeName())]= $class;
+          }
+        }
+      }
+    }
+    // }}}
+
     // {{{ public void gc([string file default NULL])
     //     Runs the garbage collector
     static function gc($file= NULL) {
@@ -391,12 +405,20 @@
   // {{{ void uses (string* args)
   //     Uses one or more classes
   function uses() {
+    $scope= NULL;
     foreach (func_get_args() as $str) {
-      xp::$registry['loader']->loadClass0($str);
+      $class= xp::$registry['loader']->loadClass0($str);
+      if (method_exists($class, '__import')) {
+        if (NULL === $scope) {
+          $trace= debug_backtrace(FALSE);
+          $scope= xp::reflect($trace[2]['args'][0]);
+        }
+        call_user_func(array($class, '__import'), $scope);
+      }
     }
   }
   // }}}
-  
+
   // {{{ void raise (string classname, var* args)
   //     throws an exception by a given class name
   function raise($classname) {
Index: skeleton/lang/Object.class.php
===================================================================
--- skeleton/lang/Object.class.php	(revision 14768)
+++ skeleton/lang/Object.class.php	(working copy)
@@ -93,7 +93,16 @@
       if ("\7" === $name{0}) {
         return call_user_func_array(array($this, substr($name, 1)), $args);
       }
-      throw new Error('Call to undefined method '.get_class($this).'::'.$name);
+      $t= debug_backtrace(FALSE);
+      $scope= $t[2]['class'];
+      if (isset(xp::$registry['ext'][$scope])) {
+        foreach (xp::$registry['ext'][$scope] as $type => $class) {
+          if (!$this instanceof $type) continue;
+          array_unshift($args, $this);
+          return call_user_func_array(array($class, $name), $args);
+        }
+      }
+      throw new Error('Call to undefined method '.$this->getClassName().'::'.$name.'() from scope '.xp::nameOf($scope));
     }
 
     /**
Index: ports/unittest/core.ini
===================================================================
--- ports/unittest/core.ini	(revision 14768)
+++ ports/unittest/core.ini	(working copy)
@@ -167,3 +167,12 @@
 
 [types]
 class="net.xp_framework.unittest.core.ArrayTypeTest"
+
+[extension.invoke]
+class="net.xp_framework.unittest.core.extensions.ExtensionInvocationTest"
+
+[extension.notimported]
+class="net.xp_framework.unittest.core.extensions.NotImportedTest"
+
+[extension.notimportedhere]
+class="net.xp_framework.unittest.core.extensions.NotImportedHereTest"
Index: ports/classes/net/xp_framework/unittest/core/extensions/ExtensionInvocationTest.class.php
===================================================================
--- ports/classes/net/xp_framework/unittest/core/extensions/ExtensionInvocationTest.class.php	(revision 0)
+++ ports/classes/net/xp_framework/unittest/core/extensions/ExtensionInvocationTest.class.php	(revision 0)
@@ -0,0 +1,53 @@
+<?php
+/* This class is part of the XP framework
+ *
+ * $Id$ 
+ */
+
+  uses(
+    'unittest.TestCase', 
{'fraction_non_alphanumeric': 0.24812140857521733, 'fraction_numerical': 0.0176808604685428, 'mean_word_length': 3.3568677792041077, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4193576', 'n_tokens_mistral': 2224, 'n_tokens_neox': 2118, 'n_words': 400}