cakephp model class not found error while upgrading - php

I am trying to upgrade my CakePHP application from 1.3.6 to 2.2.4
I did all the upgrade steps based on official CakePHP upgrade documentation.
But i am struggling with this error:
Class 'Content' not found in
C:\wamp\www\cakephp-2.2.4\app\Controller\Component\OrderBaseComponent.php
on line 20
Argument 1 passed to Component::__construct() must be an instance of
ComponentCollection, none given, called in
C:\wamp\www\cakephp-2.2.4\app\Controller\Component\OrderBaseComponent.php
on line 17 and defined [CORE\Cake\Controller\Component.php, line 77]

For the first error make sure you always App::uses() every single class you use in your code.
So your Content (whatever class it is) must also be included before you can actually use it.
Only if it is a model you can just use ClassRegistry::init(), otherwise put sth like
App::uses('Content', '[TYPE]'); at the top of the file.
This second error is pretty self-explanatory!
Look into the outlined "CORE\Cake\Controller\Component.php" file and make sure your function does have the exact same arguments in your custom component:
public function __construct(ComponentCollection $collection, $settings = array()) {
//...
}

We don't have any relevant source code but generally you might add this at the top or your component file:
First:
App::uses('Content', 'Model');
And you might have to add something like in your init or construct method:
$this->Content = ClassRegistry::init('Content');
That should solve the first issue or give a clear explanation about what goes wrong. Actually this code was likely already not correctly independent functional.
I suspect it depends on the Model to be loaded already in another piece of code that's why it worked likely. A component should work not dependent on other pieces of code so adding the App::uses, App::import etc statements makes the code work always. For example when you re-use it in your other projects.
Second:
The second issue comes really with migration issues. Check that the model extends the Component class first.
Then make sure that if you implement a custom method __construct() but also init() for example that you check whether you should add a call to the parent. For example this applies to beforeFilter controller method.
public function beforeFilter() {
parent::beforeFilter();
}
Documentation and code example from: http://book.cakephp.org/2.0/en/controllers.html#the-app-controller
Relevant piece of documentation: http://book.cakephp.org/2.0/en/appendices/2-0-migration-guide.html#components

Related

PHPUnit gives error: Target [Illuminate\Contracts\View\Factory] is not instantiable

I created a simple test for my new Laravel 7 application. But when I run php artisan test I get the following error.
Target [Illuminate\Contracts\View\Factory] is not instantiable.
The error doesn't appear when I go to the page in the browser.
$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Although "Just write feature tests" may seem like a cop-out ("They're not unit tests!"), it is sound advice if you do not want to get bogged down by framework-specific knowledge.
You see, this is one of those problems that come from using facades, globals, or static methods. All sorts of things happen outside of your code (and thus your test code) in order for things to work.
The problem
To understand what is going on, you first need to know how Laravel utilizes Containers and Factories in order to glue things together.
Next, what happens is:
Your code (in HomeController::home() calls view() somewhere.
view() calls app() to get the factory that creates Views1
app() calls Container::make
Container::make calls Container::resolve1
Container::resolve decides the Factory needs to be built and calls Container::build to do so
Finally Container::build (using PHP's ReflectionClass figures out that \Illuminate\Contracts\View\Factory can not be Instantiated (as it is an interface) and triggers the error you see.
Or, if you're more of a visual thinker:
The reason that the error is triggered is that the framework expects the container to be configured so that a concrete class is known for abstracts (such as interfaces).
The solution
So now we know what is going on, and we want to create a unit-test, what can we do?
One solution might seem to not use view. Just inject the View class yourself! But if you try to do this, you'll quickly find yourself going down a path that will lead to basically recreating loads of framework code in userland. So not such a good idea.
A better solution would be to mock view() (Now it is really a unit!). But that will still require recreating framework code, only, within the test code. Still not that good.[3]
The easiest thing is to simply configure the Container and tell it which class to use. At this point, you could even mock the View class!
Now, purists might complain that this is not "unit" enough, as your tests will still be calling "real" code outside of the code-under-test, but I disagree...
You are using a framework, so use the framework! If your code uses glue provided by the framework, it makes sense for the test to mirror this behavior. As long as you don't call non-glue code, you'll be fine![4]
So, finally, to give you an idea of how this can be done, an example!
The example
Lets say you have a controller that looks a bit like this:
namespace App\Http\Controllers;
class HomeController extends \Illuminate\Routing\Controller
{
public function home()
{
/* ... */
return view('my.view');
}
}
Then your test[5] might look thus:
namespace Tests\Unit\app\Http\Controllers;
use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;
class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
public function testHome()
{
/*/ Arange /*/
$mockFactory = $this->createMock(Factory::class);
app()->instance(Factory::class, $mockFactory);
/*/ Assert /*/
$mockFactory->expects(self::once())
->method('make')
->with('my.view')
;
/*/ Act /*/
(new HomeController())->home();
}
}
A more complex example would be to also create a mock View and have that be returned by the mock factory, but I'll leave that as an exercise to the reader.
Footnotes
app() is asked for the interface Illuminate\Contracts\View\Factory, it is not passed a concrete class name
The reason Container::make does nothing other than call another function is that the method name make is defined by PSR-11 and the Laravel container is PSR compliant.
Also, the Feature test logic provided by Laravel already does all of this for you...
Just don't forget to annotate the test with #uses for the glue that is needed, to avoid warnings when PHPUnit is set to strict mode regarding "risky" tests.
Using a variation of the "Arrange, Act, Assert" pattern
This is not how you test endpoints in Laravel. You should let Laravel instantiate the application as it is already setup in the project, the examples you can see here.
What you already wrote can be rewritten to something like this.
$response = $this->call('GET', route('home')); // insert the correct route
$response->assertOk(); // should be 200
For the test to work, you should extend the TestCase.php, that is located in your test folder.
If you're finding this in The Future and you see #Pothcera's wall of text, here's what you need to know:
The ONLY reason he's doing any of that and the ONLY reason you're seeing this in the first place in a Unit test is because he and you haven't changed from PHPUnit\Framework\TestCase to Tests\TestCase in the test file. This exception doesn't exist when you extend the test case that includes app().
My advice would be to simply extend the correct base test case and move on with your life.

How should I register my Sonata application's new admin class?

I am using the Sonata admin bundle to build a backend for a blog. I have created a new Post entity and used sonata:admin:generate to generate an admin class called PostAdmin. This admin class extends AbstractAdmin. So far so good.
In accordance with https://sonata-project.org/bundles/doctrine-orm-admin/master/doc/reference/form_field_definition.html, I add the following code to my class:
public function validate(ErrorElement $errorElement, $object)
{
die('At least the validate() method is being called.');
$errorElement
->with('author')
->assertNotBlank()
->assertNotNull()
->end();
parent::validate($errorElement, $object); // TODO: Change the autogenerated stub
}
... but my die() statement does not appear to get called. (Also, when I remove the die() call, the assertions appear to get ignored, as I can leave my "author" field blank and still save a record.)
====
UPDATE #1: Per https://symfony.com/doc/3.x/bundles/SonataAdminBundle/reference/conditional_validation.html, I tried throwing an exception instead of dying. Even with this better debugging technique, it appears that the method is not getting called.
UPDATE #2: It looks like none of the methods in my PostAdmin class are being called at all. Is there a place I need to register that PostAdmin class in order for its methods to be called?
I found my answer. It's embarrassing.
There was an old file called PostAdminOld.php that contained a duplicate of my PostAdmin class. Running make install on my project in a fit of desperation, I saw the following message that helped me pin down the problem:
Warning: Ambiguous class resolution, "AppBundle\Admin\PostAdmin" was found in both "/usr/src/app/src/AppBundle/Admin/PostAdminOld.php" and "/usr/src/app/src/AppBundle/Admin/PostAdmin.php", the first will be used.
Warning: Ambiguous class resolution, "AppBundle\Controller\PostAdminController" was found in both "/usr/src/app/src/AppBundle/Controller/PostAdminControllerOld.php" and "/usr/src/app/src/AppBundle/Controller/PostAdminController.php", the first will be used.
... and that was that. Removing the old admin class definition allowed me to see my more recent changes.

PHPUnit constraints extension gives error "PHPUnit_Util_Type::export()" not found

I want a mock object that can tell me if:
when one of its methods are called
that one of the arguments passed to that method
is an array
and has a particular key/value pair.
I want to use PHPUnit's constraints to achieve, this, so my test code would look like this:
$mock = $this->getMock('\Jodes\MyClass');
$mock->expects($this->once())
->method('myMethod')
->with(
$this->logicalAnd(
$this->isType('array'),
$this->arrayHasPair('my_key', 'my_value'),
)
);
// ... code here that should call the mock method
In this previous SO question, the guy ended up writing his own constraint.
I found this library which seems to implement quite a few nifty things. So I installed it by adding this line in my composer.json's require section:
"etsy/phpunit-extensions": "#stable"
But when I try using it, I get an error. I use it like so:
class MyClassTest extends PHPUnit_Framework_TestCase {
public function arrayHasPair($key, $value){
return new PHPUnit_Extensions_Constraint_ArrayHasKeyValuePair($key, $value);
}
public function testmyMethod(){
// code as per my example above
}
}
But that produces this error:
PHP Fatal error: Call to undefined method PHPUnit_Util_Type::export() in C:\MyProject\vendor\etsy\phpunit-extensions\PHPUnit\Extensions\Constraint\ArrayHasKeyValuePair.php on line 50
This previous question/answer explains what the problem is, but I'm not sure what I should do about it. Does this mean that the developers of that library have abandoned it? Is there an alternative to use? Or what options do I have for fixing it? I'm amazed such basic constraints still don't exist in PHPUnit. Obviously I could write my own constraints but surely that's unnecessary?
The PHPUnit_Util_Type::export() method was removed a while ago. The extension you want to use has to be updated to be compatible with current versions of PHPUnit.

Calling function from another controller bug

I have 2 controllers: UsersController and AnalyticsController.
When I run:
//UsersController:
function dummyFunction(){
$this->Analytic->_loadChartFromId($chart_id);
}
the output is:
Query: _loadChartFromId
Warning (512): SQL Error: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '_loadChartFromId' at line 1 [CORE\cake\libs\model\datasources\dbo_source.php, line 684]
The _loadChartFromId() function takes $chart_id as an argument and returns an array as output. I have no idea why Query: _loadChartFromId appears.
You don't call other controller methods from your controller.
In your users controller, $this->Analytic is an instance of the Analytic model, not the AnalyticsController. So CakePHP thinks you are trying to call a public method called _loadChartFromId() on the Analytic model, which, as you know, doesn't exist.
The reason you get the error is because if you try to call a non-existent method of a model, CakePHP tries to convert it to one of its Magic Find Types. Of course, it's not a valid Magic Find Type either, so you get a SQL error.
Solution
It's difficult to provide a complete solution as we only have part of your code, but you are perhaps violating the concept of MVC with the way you're coding your app.
You need to do one of two things:
Move _loadChartFromId() to your users controller. This seems to me like it would be counter-intuitive, as it probably has nothing to do with the User.
Move the method to your Analytic model. You would need to make it public so the controller can access it, and in your users controller you would need to make sure you have the Analytic model loaded.
class Analytic extends AppModel {
public function _loadChartFromId($chart_id) {
// ...
}
}
Then you can call the method as you were doing before, from your users controller.
I could have opted to close this question as an exact duplicate of at least 5 other questions (if you search for "cakephp another controller").
But the answers there are just terrible. They actually try to invoke new Dispatchers or requestAction().
So if your question is about another controller method:
The short answer is: You don't.
The long answer: You still dont. That's a typical beginners mistake.
You should put the functionality into a component if it is mainly business logic. The component then can be accessed from multiple controllers.
If it is more like model data (as in your example), put the functionality into the model layer (in an appropriate model). this way you can also access it from anywhere in your application.
Also: Accessing protected methods from other objects is never a good idea. Use public methods if you intend to use it from "outside" the object.
If your question is about a model method:
You need to include your model in your controller before you can use it.
Either by using public $uses or by using loadModel('ModelName') or even ClassRegistry::init('ModelName').

Calling overridden method from within overriding method in OO PHP

Working in a symfony model, I want to override a function and call the overridden function from within the overriding one, along the lines of
class MyClass extends BaseMyClass {
function setMyProperty($p) {
parent::setMyProperty($p);
//do some other stuff
}
}
This is resulting in a segmentation fault. I don't want to alter the parent class - it's been generated by symfony, and may feasibly be overwritten in the future if the model is rebuilt. This seems like something that should be straightforward, but I'm struggling to find the solution.
Since you saw the problem, I guess you should mark it as answered to remove it from the unanswered list.
I've managed to find a solution to my own question on the symfony project forum.
I can't call the overridden function because it doesn't exist. Though it exists enough for me to override it.
Using
$this->_set('my_property', $p);
Works where
parent::setMyProperty($p);
Causes the error.
Note that
$this->setMyProperty($p);
Works fine in my class if the method has not been overridden.

Categories