Repository: codeigniter4/CodeIgniter4 (PHP)

Issue:
Bug: [ViewCells] when there are cells with the same short name, only the first cell is loaded
### PHP Version

8.2

### CodeIgniter4 Version

4.3.6

### CodeIgniter4 Installation Method

Composer (using `codeigniter4/appstarter`)

### Which operating systems have you tested for this bug?

Windows

### Which server did you use?

apache

### Database

_No response_

### What happened?

Hello,

I created a project that uses widgets. My widgets are in folder `/app/Widgets`. Theses widgets are used to display partial views.
For exemple, the widget `/app/Widgets/Customer/Account.php` have a method `lastRegistrations` to display 10 last registrations to the administrator.

In the same page, I call two widgets to display latest registrations of Customers and Administrators like that :
          => `<?= view_cell("\App\Widgets\Customer\Account::lastRegistration"); ?>`
          => `<?= view_cell("\App\Widgets\Admin\Account::lastRegistration"); ?>`

Everything worked fine when I was on version 4.1 with PHP7.4.
Today, I upgraded my CodeIgniter project to the latest version 4.3.6 with PHP 8.2.

The project launches fine overall after some changes, but I noticed a problem on some pages.

In the page where I call the two previous widgets, I noticed that the second widget (Admin) displayed the same content as the first widget (Customer).

I did some research, and I found the source of the problem in the file `system/Config/Factories.php` in the method `__callStatic`.

In this method, there is a line `$basename = self::getBasename($name);` who return the name of the class.
          => In my case : `$name = "\App\Widgets\Customer\Account";`
          => Result : `$basename = "Account";`

This method stores the namespace in array `self::$basenames` using `$basename` as key.
The `self::$basenames` variable is used to check if an instance of the class already exists and to return the namespace of the class.

In my case, my two widgets have the same basename, so the second call will use an instance of the first call, because the check is based on the same basename. This is why I have the same results.

To solve this, I found two solutions:
          1. Use `$name` instead of `$basename` : `self::$basenames[$options['component']][$name] = $class;`
          2. Or, generate a hash of the `$name` : `$basename = md5($name);`

### Steps to Reproduce

1. Install CodeIgniter (version 4.3.6)
2. Create two controllers with the same name in two subfolders :
          => `/app/Controllers/SubFolderOne/Test.php`
          => `/app/Controllers/SubFolderTwo/Test.php`
3. Create a method on each controllers who return different string values
4. Call this two methods in view file using "view_cell" function
          => `<?= view_cell("\App\Controllers\SubFolderOne\Test::render"); ?>`
          => `<?= view_cell("\App\Controllers\SubFolderTwo\Test::render"); ?>`
5. The first call will return the result of first controller, but the second call will also return the content of first controller instead of the second controller

### Expected Output

The first call will be return the result of first controller, and the second call will be return the content of the second controller.

### Anything else?

_No response_


Discussion:
Thank you for reporting.
This seems a bug and undocumented breaking change.

> Thank you for reporting. This seems a bug and undocumented breaking change.

Thank you for your reactivity! 👍 
@jeandemoroque Please try: #7686
> @jeandemoroque Please try: #7686

This solution fixes the problem I encountered.
However, I don't think this is the right solution.

I just tested the same kind of script with the models, I encounter the same problem:
`$accountOne = model('\App\Models\SubFolderOne\Account');`
`$accountTwo = model('\App\Models\SubFolderTwo\Account');`

The `$accountTwo` variable is an instance of `\App\Models\SubFolderOne\Account` instead of `\App\Models\SubFolderTwo\Account`.

The solution would be to correct the problem in the method `__callStatic` of `system/Config/Factories.php` file directly.
We need to retrieve the class instance in cache using fullname, et not the basename:
1. Use `$name` instead of `$basename` : `self::$basenames[$options['component']][$name] = $class;`
2. Or, generate a hash of the `$name` : `$basename = md5($name);`
@jeandemoroque Thank you for review.

Yes, the current model() can't load `\App\Models\SubFolderTwo\Account`.
But I'm not sure that it is a really bug.

Your solution will break the existing feature, *preferApp*.
See https://codeigniter4.github.io/CodeIgniter4/concepts/factories.html#preferapp-option


@kenjis Yes, you're right. I didn't see this option _preferApp_.

It's strange to receive an instance of another class, but maybe it's normal.
However, it seems strange that there is no way to load classes with the same short name at all. 
So I have created an issue #7694.




Patch:
diff --git a/system/View/Cell.php b/system/View/Cell.php
index 5d575bced1e7..5c4d0ce2e99f 100644
--- a/system/View/Cell.php
+++ b/system/View/Cell.php
@@ -178,7 +178,8 @@ protected function determineClass(string $library): array
         }
 
         // locate and return an instance of the cell
-        $object = Factories::cells($class);
+        // @TODO extend Factories to be able to load classes with the same short name.
+        $object = class_exists($class) ? new $class() : Factories::cells($class);
 
         if (! is_object($object)) {
             throw ViewException::forInvalidCellClass($class);


Test patch:
diff --git a/tests/_support/View/OtherCells/SampleClass.php b/tests/_support/View/OtherCells/SampleClass.php
new file mode 100644
index 000000000000..cff88bc58c4f
--- /dev/null
+++ b/tests/_support/View/OtherCells/SampleClass.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * This file is part of CodeIgniter 4 framework.
+ *
+ * (c) CodeIgniter Foundation <admin@codeigniter.com>
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+namespace Tests\Support\View\OtherCells;
+
+/**
+ * Two classes with the same short name.
+ *
+ * - Tests\Support\View\SampleClass
+ * - Tests\Support\View\OtherCells\SampleClass
+ */
+class SampleClass
+{
+    public function hello()
+    {
+        return 'Good-bye!';
+    }
+}
diff --git a/tests/system/View/CellTest.php b/tests/system/View/CellTest.php
index 33dd3984bd0d..5c1863236ab1 100644
--- a/tests/system/View/CellTest.php
+++ b/tests/system/View/CellTest.php
@@ -113,6 +113,17 @@ public function testDisplayRendersWithNamespacedClass()
         $this->assertSame($expected, $this->cell->render('\Tests\Support\View\SampleClass::hello'));
     }
 
+    public function testDisplayRendersTwoCellsWithSameShortName()
+    {
+        $output = $this->cell->render('\Tests\Support\View\SampleClass::hello');
+
+        $this->assertSame('Hello', $output);
+
+        $output = $this->cell->render('\Tests\Support\View\OtherCells\SampleClass::hello');
+
+        $this->assertSame('Good-bye!', $output);
+    }
+
     public function testDisplayRendersWithValidParamString()
     {
         $params   = 'one=two,three=four';