234234assert (!$ abstractReflection ->getMethod ('concreteMethod ' )->isAbstract (), 'concreteMethod should NOT be marked as abstract ' );
235235
236236// Test extending the abstract class in PHP
237+ // Note: PHP subclasses of Rust classes don't have Rust backing, so inherited Rust
238+ // methods cannot be called. The subclass must override any methods it wants to use.
237239class ConcreteTestClass extends TestAbstractClass {
238240 public function __construct () {
239241 parent ::__construct ();
@@ -242,11 +244,16 @@ public function __construct() {
242244 public function abstractMethod (): string {
243245 return 'implemented abstract method ' ;
244246 }
247+
248+ // Must override concreteMethod since we can't call the inherited Rust method
249+ public function concreteMethod (): string {
250+ return 'concrete method from PHP subclass ' ;
251+ }
245252}
246253
247254$ concreteObj = new ConcreteTestClass ();
248255assert ($ concreteObj ->abstractMethod () === 'implemented abstract method ' , 'Implemented abstract method should work ' );
249- assert ($ concreteObj ->concreteMethod () === 'concrete method in abstract class ' , 'Concrete method from abstract class should work ' );
256+ assert ($ concreteObj ->concreteMethod () === 'concrete method from PHP subclass ' , 'Concrete method from PHP subclass should work ' );
250257
251258// Test lazy objects (PHP 8.4+)
252259if (PHP_VERSION_ID >= 80400 ) {
@@ -348,3 +355,71 @@ public function __construct(string $data) {
348355
349356$ uncloneable = new TestUncloneableClass ('test ' );
350357assert_exception_thrown (fn () => clone $ uncloneable , 'Cloning uncloneable class should throw ' );
358+
359+ // Test issue #138 - PHP subclass of Rust class extending a non-abstract class
360+ class PhpSubclassOfArrayAccess extends TestClassArrayAccess {
361+ public function __construct () {
362+ // Call parent constructor
363+ parent ::__construct ();
364+ }
365+
366+ public function customMethod (): string {
367+ return 'custom method result ' ;
368+ }
369+
370+ // Must override inherited methods since PHP subclass doesn't have Rust backing
371+ public function offsetExists ($ offset ): bool {
372+ // Reimplement the logic instead of calling parent
373+ return is_int ($ offset );
374+ }
375+ }
376+
377+ $ phpSubclass = new PhpSubclassOfArrayAccess ();
378+ assert ($ phpSubclass instanceof TestClassArrayAccess, 'PHP subclass should be instanceof parent class ' );
379+ assert ($ phpSubclass instanceof TestClassArrayAccess, 'PHP subclass should work with parent class methods ' );
380+ // Test overridden method
381+ assert ($ phpSubclass ->offsetExists (1 ) === true , 'Overridden method should work ' );
382+ // Test custom method
383+ assert ($ phpSubclass ->customMethod () === 'custom method result ' , 'Custom method should work ' );
384+
385+ // Test issue #138 - Greeter example from the issue
386+ // Test regular Rust class works
387+ $ greeter = new TestGreeter ('world ' );
388+ assert ($ greeter ->greet () === 'Hello, world! ' , 'Regular Rust class should work ' );
389+
390+ // Test PHP subclass can override methods
391+ $ greeterSubclass = new class extends TestGreeter {
392+ public function __construct () {
393+ parent ::__construct ('php ' );
394+ }
395+
396+ // Must override to use it
397+ public function greet (): string {
398+ return 'Hello from PHP! ' ;
399+ }
400+ };
401+ assert ($ greeterSubclass ->greet () === 'Hello from PHP! ' , 'PHP subclass method override should work ' );
402+ // The overridden method is called, not the parent's
403+
404+ // Test calling inherited Rust methods on PHP subclass (issue #138)
405+ class PhpSubclassGreeter extends TestGreeter {
406+ public function __construct () {
407+ parent ::__construct ('inherited ' );
408+ }
409+ // NOT overriding greet - should call parent's Rust method
410+ }
411+ $ inheritedGreeter = new PhpSubclassGreeter ();
412+ assert ($ inheritedGreeter ->greet () === 'Hello, inherited! ' , 'Inherited Rust method should work on PHP subclass ' );
413+ assert (get_class ($ inheritedGreeter ) === 'PhpSubclassGreeter ' , 'get_class should return subclass name ' );
414+ assert ($ inheritedGreeter instanceof TestGreeter, 'instanceof should work for parent class ' );
415+ assert ($ inheritedGreeter instanceof PhpSubclassGreeter, 'instanceof should work for subclass ' );
416+
417+ // Test anonymous class with inherited method
418+ $ anonInherited = new class extends TestGreeter {
419+ public function __construct () {
420+ parent ::__construct ('anon ' );
421+ }
422+ // NOT overriding greet
423+ };
424+ assert ($ anonInherited ->greet () === 'Hello, anon! ' , 'Anonymous class should inherit Rust method ' );
425+
0 commit comments