I'm using php activerecord. I need initialize a Order object and then set related objects as show below
$order = new Order();
Order->_plan = Plan::find(1);
I get the error Undefined property: Order->_plan in /var/www/ordenes-web/core/libs/php-activerecord/lib/Model.php on line 428
My class:
class Order extends ActiveRecord\Model{
static $belongs_to = array(
array(
'_plan',
'class_name' => 'Plan',
'foreign_key' => 'plan'
),
);
}
The relationship works fine. If I find an Order with the finder I get the related object _plan:
Order::find(1)->_plan // Works!
What am I doing wrong?
The problem is that ActiveRecord not support this behavior. You can see here
This line
Order->_plan
Really doesn't mean anything. You can find the properties of an object, for instance, your $order object.
This line means you are making an object and then finding the _plan property. Which is good.
Order::find(1)->_plan // Works!
To do this with an object, you should do
$order = Order::find(1);
var_dump($order->_plan;) // should work!
Now you can change stuff in your _plan (if it is there), like
$order->_plan->world = "Hello".
Now you should remember that you set the relationship in you objects, so that's is how they are reached. You're not supposed to created objects like this I think.The example you say that works does something like
find the order with id 1.
get its plan.
But your example that doesn't work does something else
find a new order
assign the plan with id 1 to that order.
The last part doesn't work like that I think, see this link that #Overflow012 posted.
Related
I have this function that receives a "user" model by parameter , I want to collect the properties of that object, for this I do it this way, the code works for me but the editor "phpstorm" complained to me with this error and it was to know what would be the best way to do this.
Thank you
public function sendEmail(User $user)
{
$params = [
'name' => "{$user->completeName}",
'language' => "{$user->locale}",
'user_id' => "{$user->id}"
];
}
Field accessed via magic method less... (Ctrl+F1)
Inspection info: Referenced field is not found in subject class. Note: Check is not performed on objects of type "stdClass" or derived.
Thanxs,
maybe this is simpler
$params = $user->toArray();
or
$params = $user->toJson();
That's because in Laravel your model does not actually have the properties defined.
In PHP there is the concept of magic methods though ( http://php.net/manual/en/language.oop5.overloading.php#object.get ), where the __get() method allows you to basically intercept the access to an otherwise inaccessible (or non-existing) property.
This is what happens behind the scenes in Laravel. All your property accesses are "intercepted" and Laravel looks if your database contains a column which is named like the property you are trying to access (very simplified speaking).
In a Laravel context you can savely ignore this warning.
I have an A entity and this have a property call B as relation 1:n from B to A. When I update A in TCA backend interface, when an particular field is active, the solution runs a hook of type function processDatamap_postProcessFieldArray (...)
I have to create a new element of B and save in ObjectStorage attribute of A. This works in execute time, create an objet and attaching it, but can not save in DB. I have tried with functions of extbase and Repository but it does not work. In my reserch identified the framework Doctrine for create queries, similar to persistence behavior, but in this case I could save the new object of B.
My question is: how could I use Doctrine for build query that allows to make update for object A, adding the new element B and save this element in the relation in DB.
I am working with TYPO3 7.6
You shouldn't use Extbase within the DataHandler hooks. Also plain database queries (neither with Dotrine or TYPO3_DB) are not good idea for creating entities within BE. Better way is to use TYPO3 DataHandler API. Example creation of Entity B during create/edit of Entity A could look like that.
Register hook typo3conf/ext/example/ext_localconf.php
defined('TYPO3_MODE') || die('Access denied.');
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['example'] = 'Vendor\\Example\\DataHandling\\DataHandler';
typo3conf/ext/example/Classes/DataHandling/DataHandler.php
namespace Vendor\Example\DataHandling;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\StringUtility;
class DataHandler implements SingletonInterface
{
public function processDatamap_afterDatabaseOperations(
string $status,
string $table,
$id,
$fieldArray,
\TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler
) {
// Do nothing if other table is processed
if ($table !== 'tx_example_domain_model_entitya') {
return;
}
// Get real UID of entity A if A is new record
$idOfEntityA = $dataHandler->substNEWwithIDs[$id];
// Example fields of entity B
$entityB = [
'sys_language_uid' => '0',
'entitya' => $idOfEntityA,
'hidden' => '0',
'title' => 'I\'m entitty B',
'starttime' => '0',
'endtime' => '0',
'pid' => $fieldArray['pid'],
];
// Add entity B associated with entity A
$dataHandler->start(
[
'tx_example_domain_model_entityb' => [
StringUtility::getUniqueId('NEW') => $entityB
]
],
[]
);
$dataHandler->process_datamap();
}
}
Tested on 8.7, but will work on 7.6 too. Here you can read more about DataHandler https://docs.typo3.org/typo3cms/CoreApiReference/8.7/ApiOverview/Typo3CoreEngine/Database/
In contrary to the previous answer, I see no reason, why extbase shouldn`t be used in the DataHandler Hooks. I do it myself in an extension with dynamic objects that are being synchronized via a SOAP-Webservice.
You got to keep few things in mind (in this order inside the hooked function) :
-Obey naming policies !!
-Instantiate the ObjectManager manually via GeneralUtility::makeInstance
-Get ALL the repositories manually (with all I mean really all.. also repositories of childs of models you are working with inside the hooked function).
-Create new object instances with object manager => not with "new".
Then you can just add childs to parents as you are used to.. but dont forget to persistAll() via persistenceManager manually in the end.
Hope this could help. Basically, a function hooked via DataMap Hook acts like a static function called via ajax => you have to make sure to get all the desired utilities and managing classes manually, because typo3 doesn`t auto-inject them.
Hope this helps,
Oliver
Here's a test file:
class MyTest extends CDbTestCase
{
public $fixtures = array(
'my_data' => 'MyData',
);
public function testMyFunction()
{
$myObjectNotInDefaultScope = $this->my_data('out_of_scope_object');
//Can't do anything with $myObjectNotInDefaultScope since it returns null
// Is it possible to use resetScope?
// I can always set a primary key for the object and use findByPk but that's a hack
}
}
and here's the corresponding fixture:
<?php
return array(
'out_of_scope_object' => array(
'title' => 'This one is out of scope',
'status' => 'archived', // so not in the default scope
),
'in_scope_object' => array(
'title' => 'This one is in scope',
'status' => 'active',
),
);
Both rows in the fixture are added to the db table, so that's not the problem. I can access both rows via the primary keys that they're allocated. But I can't access the out of scope object in this way:
$myObjectNotInDefaultScope = $this->my_data('out_of_scope_object');
which when you're testing is really how you want to access it, I think.
I have a less than satisfactory solution in use for now of allocating the object a primary key value and using findByPk (edit: with resetScope()) to load the object. I would prefer to use the normal way of working with fixtures instead, if that's possible.
Edit: To clarify a little in response to some posts:
It is possible to use fixtures as a method to return an object. This would work:
$myObjectInDefaultScope = $this->my_data('in_scope_object');
but this wouldn't work BECAUSE it's not in the default scope and there's seemingly no way currently of running resetScope() for a fixture method call:
$myObjectNotInDefaultScope = $this->my_data('out_of_scope_object');
Why do I need to do this? Well, I might want to test my unarchive method, for example. Seems reasonable to me. (As mentioned before, I can get round this a little inelegantly by using a primary key to load the object corresponding to the fixture).
Although I can access the fixture data as an array using:
$arrayNotInDefaultScope = $this->my_data['out_of_scope_object'];
it's returning an array not an object, so I can't test the object's methods on an array.
To answer my own question, there is currently no way to use resetScope() with Yii fixtures (v 1.14). It could be implemented with some effort but given that Yii2 is on its way, it's probably not worth the effort of generating a pull request that may never make it in to the source.
For now, IMO, the cleanest workaround is:
1) Define a primary key in the fixture
2) Get the primary key from the fixture array and look up the object using it:
$arrayOutOfScopeObject = $this->my_data['out_of_scope_object'];
$myObjectNotInDefaultScope = MyObject::model()
->resetScope()
->findByPk($arrayOutOfScopeObject['id']);
You could, of course, save yourself the effort of looking up the pk from the fixture by hard-coding the pk value in your test code, but that leaves your test code vulnerable to being broken by changes to a fixture that's shared with other tests.
You are using the fixtures as a method, whilst it is an array of object.
So instead of:
$myObjectNotInDefaultScope = $this->my_data('out_of_scope_object');
You should be doing:
$myObjectNotInDefaultScope = $this->my_data['out_of_scope_object'];
Check the guide for more info
I'm trying my best to learn MVC and cakePHP and I had a question about passing arrays to the view. Currently, I have some basic code below.
class AwarenesscampaignsController extends AppController {
public function view($id = null) {
$this->Awarenesscampaign->id = $id;
$this->set('data', $this->Awarenesscampaign->read());
}
This is what I "think" is currently happening.
AwarenesscampaignsController is set up. The view paramater requests id and matches it up with the Model, Awarenesscampaign. This matches up with the database and returns an array which is set to the variable "$data", and then the view is loaded.
My first question: is my understanding accurate?
What I would like to do is with this is to be able to pass another array, from a different model. For instance, I would like to query the table Posts (Controller: PostsController/ Model: Post).
For instance, my first attempt was to do the following inside the function:
$this->Post->find('all');
But this yields the error:
Indirect modification of overloaded property AwarenesscampaignsController::$Post has no effect [APP/Controller/AwarenesscampaignsController.php, line 20]
Additionally, I'm not sure how I would send both variables to the view.
To recap:
Was my understanding accurate?
How do I query a variable from another controller/model?
How do I sent this array to the appropriate view for that controller?
Thanks,
-M
You're on the right lines, and aren't doing it wrong per se. I would say your understanding is pretty good for a beginner.
By default Cake automatically loads a model that it thinks is directly related to the controller. So in AwarenesscampaignController, you can automatically access Awarenesscampaign (the model).
It doesn't know about any other model, though. One way you might solve this is by adding the following property to your controller:
// This has to contain ALL models you intend to use in the controller
public $uses = array('Awarenesscampaign', 'Post');
This goes at the top of the class, before you start declaring the functions. It tells Cake that you want to use other models except the 'default' one, but you have to add that one to the array too, or you'll lose access to it.
You can also use loadModel inside your action, if it's a one-off. It's then accessed the same way as you would access a model normally:
public function view($id = null) {
$this->loadModel('Post');
$posts = $this->Post->find('all');
...
}
To send this to your view, you can call set again, but you might want to change data to something more readable, and to prevent confusion:
public function view($id = null) {
...
$this->set('campaign', $this->Awarenesscampaign->read());
$this->set('posts', $this->Post->find('all'));
}
They'll be accessible as $campaign and $post respectively.
One tweak I would make, though, is to not use 'read' unless you intend to edit something. You can use findByColumnName to get the same data. Since you're using just an id, you can call findById:
$campaign = $this->Awarenesscampaign->findById($id);
There's quite a lot of magic going on there. It just means you can search for a particular value in a more short-hand format.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
Finally, while you can access other models (as demonstrated), you can't, or generally shouldn't, try and access one controller from another. If you have code that you want to use in more than one controller, but can't go in the model, you can create Components.
http://book.cakephp.org/2.0/en/controllers/components.html#creating-a-component
The manual is fairly comprehensive. While sometimes hard to navigate, it will often have an answer to most of your questions.
http://book.cakephp.org/2.0/en/
1) Your understanding is good enough. What this is doing is basically mapping a row of database table with object. So after setting the Model id $this->Awarenesscampaign->id = $id, now Model is pointing to the row of database table that has id equals to what has been passed to view action.
2) you can query another table by calling the methods of that particular Model. If your model is somehow associated with the current Model that you are in, you can use chaining to call that Model's action. e.g. if your in Posts controller and Post Model is associated with Comment Model t get the data you can chain through.
$comments = $this->Post->Comment->find();
If however your Model of interest is not associated with current Model, there are couple of ways to perform operations of other Model. A good option is to use Class Registry. Say for example you want to use Customer Model which is not related to your current Model. In your controller you will do
$customer= ClassRegistry::init("Customer");
$customers= $customer->find();
3) to set multiple variables for the view you can set them via compact function or using associated row.
$posts = $this->Post->find();
$comments = $this->Post->Comment->find();
$this->set(compact('posts', 'comments'));
// or
$this->set('posts' => $posts, 'comments' => $comments);
$winnerBid = Bids::model()->find($criteria);
Model has next relations:
public function relations() {
return array(
'item' => array(self::BELONGS_TO, 'Goods', 'item_id'),
'room' => array(self::BELONGS_TO, 'Rooms', 'room_id'),
'seller' => array(self::BELONGS_TO, 'RoomPlayers', 'seller_id'),
'buyer' => array(self::BELONGS_TO, 'RoomPlayers', 'buyer_id'),
);
}
When I am trying to save:
$this->seller->current_item++;
$this->seller->wins++;
$this->seller->save();
I am getting error:
Indirect modification of overloaded
property Bids::$seller has no effect
(/var/www/auction/www/protected/models/Bids.php:16)
But it was everything fine at another server?
How to fix it? Or override php directives? Any ideas? TNX
The problem here is that $seller is not a "real" property (Yii implements properties on its Models by using the magic __get method), so in effect you are trying to modify the return value of a function (which has no effect). It is as if you tried to do:
function foo() {
return 42;
}
// INVALID CODE FOR ILLUSTRATION
(foo())++;
I 'm not sure about the status of this behavior on different PHP versions, but there is an easy workaround you can use:
$seller = $this->seller;
$seller->current_item++;
$seller->wins++;
$seller->save();
I was also having the error message "Yii Indirect modification of overloaded property" when trying to massively manipulate attributes using the CActiveRecord attributes property.
Then, I discovered another method to overcome this issue, in a case where the magic method is related to an object variable which contains an array take a look: you create an AUXILIARY ARRAY in which you put the original and the new values (sometimes one wants to REPLACE a value related to one of the keys, and these methods are not satisfactory). And AFTERWARDS use an assignation, which works like the reference. For example:
$auxiliary_array = array();
foreach(Object->array_built_with_magic as $key=>$value) {
if(….) {
$auxiliary_array[$key] = Object->array_built_with_magic[$key];
} else if (…) {
$auxiliary_array[$key] = $NEW_VALUE
}
}
//So now we have the array $auxiliary_array with the
// desired MIX (that is, some originals, some modifications)
//So we will do now:
Object->array_built_with_magic =$auxiliary_array;
I had this error on yii when upgrade to php8.1,
it was in createCommand() method.
and it didn't complain in former version of php, that we access a property on model which hasn't been initialized.
the workaround was to change the bindParam() method to the bindValue().
because the former wanted to use corresponding database field which has not been initialized yet.
but the later (bindParam) just insert the value in the sql statement.