Is there a way to make PhpStorm's autocomplete "go deeper"? - php

In PhpStorm, if I create an object, then I have all auto complete on that object working fine:
$object = new MyClass();
$object->getNa...
Will auto complete to
$object->getName();
So far so good, but if I get returned an object through the first method, then the auto complete will not work on that.
$car->getDriver()->getNam...
Will show an empty list.
The getDriver method has its PHPDoc #return tag set to 'Driver' though and in some other IDEs, this therefore works to get the correct auto complete.
Wondering if there's a setting that I missed somewhere or if PhpStorm doesn't offer this kind of advanced auto complete yet?

The function getDriver() needs appropriate type-hints for the return value (function's docblock):
* #return classOrInterfaceName
This is normally enough to have a IDE "go deeper". I'm pretty sure Phpstorm supports that, but I'm not a Phpstorm user.
Take care the file with the interface/class is within the project or referenced to it.
As a work around you can assign the return value to a variable and type-hint that variable. Might not be that comfortable but can help.

Please ensure that only one definition of class Driver exists across all your project files. This is crucial for current versions of PhpStorm
see http://youtrack.jetbrains.net/issue/WI-2202 and http://youtrack.jetbrains.net/issue/WI-2760

Related

Updating data after backend action TYPO3 11.5

Working on Typo3 11.5.13
I'm trying to update some data on my pages table after a be_user changed something.
I read something about setting hooks for that purpose but I can't seem to find a good explanation as to how hooks actually function within Typo3 and how to configure one, especially for my purpose.
As far as I can see, this problem I have should be quickly solved but the complexity of the typo3 doc is hindering my progress again. Maybe you can explain how I can accomplish my goal.
Simply put: A backend user is supposed to choose a date in a datepicker and some dateinterval in the settings of a page. After saving(Or even after picking both values) I would like to update the "Next time happening" field the user can see but not change to be updated to the given date plus the dateinterval chosen.
If you have some sort of idea please share it with me.
Generally hooks are not that good documented. Modern Events are easier to find and better commented. However, if I get your use case right, using DataHandler Hooks are they way to go. That mean, every place which are using the DataHandler to save data are then covered. The backend form engine are using DataHandler.
Basic information about hooks in the core documentation:
https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/Hooks/Index.html
How to identify or find hooks, events, signalslots (depending on TYPO3 version):
https://usetypo3.com/signals-and-hooks-in-typo3.html
https://daniel-siepmann.de/posts/migrated/how-to-find-hooks-in-typo3.html
Introduction or "DataHandler" explained:
https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Typo3CoreEngine/Database/Index.html
Basicly, DataHandler has two main kind of processings:
Data manipulations -> process_datamap()
Actions (move,delete, copy, translate) -> process_cmdmap()
For DataHandler, you register a class only for datamap and/or processmap, not for a concrete hook itself.
// <your-ext>/Classes/Hooks/MyDataHandlerHooks.php
namespace <Vendor>\<YourExt>\Hooks;
class MyDataHandlerHooks {}
// <your-ext>/ext_localconf.php
// -> for cmdmap hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['yourextname']
= \Vendor\YourExt\Hooks\MyDataHandlerHooks::class;
// -> for datamap hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['yourextname']
= \Vendor\YourExt\Hooks\MyDataHandlerHooks::class;
You need to register your class only for these kind of hooks you want to consume. And you do not have to implement all hooks.
Hooks can be looked up in \TYPO3\CMS\Core\DataHandling\DataHandler (as hooks are normally searched.
Next step would be to find the proper hook for your use case, and simply add that hook method to your class. Naming the hooks are not chooseable for DataHandler hooks.
TYPO3 Core tests contains a test fixture class for DataHandler hooks - which is not complete, but contains at least the most common ones (along with the needed method signatures) since 8.x:
https://github.com/TYPO3/typo3/blob/main/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/Fixtures/HookFixture.php
So you may have to look into the version for your core version to get a feeling how the signature should look for that core version.
Generally I would guess one of these two:
processDatamap_postProcessFieldArray(): Hook with prepared field array, and you can simple add your new stuff to write or update it and it will be saved. Good if you need to change the record directly.
processDatamap_afterDatabaseOperations(): Hook after record has been changed. This is a good startpoint if you need to do other things after saving a record.
Given your usecase, I would tip on the first one, so here a example implementation (in the class and registering as datamap hook as explained above):
// <your-ext>/Classes/Hooks/MyDataHandlerHooks.php
namespace <Vendor>\<YourExt>\Hooks;
class MyDataHandlerHooks {
/**
* #param string|int $id
*/
public function processDatamap_postProcessFieldArray(
string $status, // Status of the current operation, 'new' or 'update'
string $table, // The table currently processing data for
$id, // The record uid currently processing data for,
// [integer] or [string] (like 'NEW...')
array &$fieldArray, // The field array of a record, cleaned to only
// 'to-be-changed' values. Needs to be &$fieldArray to be considered reference.
DataHandler $dataHandler
): void
{
// $fieldArray may be stripped down to only the real fields which
// needs to be updated, mainly for $status === 'update'. So if you
// need to be sure to have correct data you may have to retrieve
// the record to get the current value, if not provided as with new
// value.
if ($table === 'be_users'
&& $status === 'update'
&& array_key_exists('target_field_name', $fieldArray)
) {
$valueToReactTo = $fieldArray['target_field_name'];
if ($valueToReactTo === 'some-check-value') {
// needs not to be there
$fieldArray['update-field'] = 'my-custom-value';
}
}
}
}

Autoloading Required Classes from User Input in PHP

Let me start off by saying I'm an intermediate level PHP coder who's learning OOP. I've got a site running, but I would like to break my code up to implement a more flexible design pattern...and because OOP is just plain awesome.
In my original code, I used switch statements to call functions that correspond with the user request.
$request = (string) $_GET['fruit'];
switch ($request) {
case 'apply':
Get_Apple();
default:
Error_Not_A_Fruit();
exit;
}
This makes it highly inflexible and requires me to change the code at multiple locations for adding new options the user may request.
I'm thinking about changing it to a Polymorphic class call. I'm using composer, so I've got my Objects setup to autoload with PSR-4 standards. So the answer seems simple, if the user request is "apple," I could create
$request = (string) $_GET['fruit'];
$product = new Product\$request;
But, if a user manually enters something that doesn't exists...say "orange," what method would I use to white list the user's input? Like I said, this is my first venture into OOP and would love to pick up design standards that you guys use. I'm thinking encapsulating the block inside a try{} & catch(){} block, but is that the way it should be done?
Any advise would be greatly appreciated :)
Cheers,
Niro
Update: I'd like to make it clearer because it may not have been before. I'm looking for the approach to doing this in such a way that one could add new Objects of Subclass Product (implementing a Product Interface). That way I can add different Product types without changing code everywhere.
Well, you can always use class_exist('Product\\Apple').
class_exists takes 2 arguments:
Class Name (full, with namespace)
Boolean value, whether to try and autoload the class or not. Default is true.
The function returns a boolean.
So you write
$fullClassName = "Product\\$request";
if(class_exists($fullClassName )) {
$product = new $fullClassName();
}
else {
//error here
}

Dynamically declared fields in Kohana

I’ve been building an application with Kohana 3.3, and recently switched development from Coda 2 — a text editor — to PhpStorm 6 — an IDE.
PhpStorm 6 has been very handy in pointing out potential code smells; it prompted me to move from attaching data to views with the __set($key, $value) magic method to instead using the bind($key, $value) method.
Another thing that PhpStorm 6 is complaining about, is that I’m declaring fields dynamically.
I have subclassed Controller_Template, and I’m attaching my view to my template like this:
public function action_index() {
# Create the view
$view = View::factory('project/list');
# Attach the view to the template
$this->template->content = $view;
}
Apparently, content is declared dynamically. I’ve been checking up the class heirarchy, and I can't find the content property declared anywhere (hence why it’s dynamic, I suppose). Is this a code smell? Is dynamic declaration bad? Can I explicitly declare the content property somewhere?
As it is, the code works. I just want to understand why PhpStorm is giving me a notice, and whether or not I’m going about things the right way.
The advantage and disadvantage of PHP is dynamic typing. Its convenient in some cases, but irritating in another. You shown here irritating example. You know, that the $view is object which inherit from View (for example), so you know which functions you can use. If you don't mess anything, there will be View type object always.
Phpstorm don't have this information and thats why you see warning. He wants you also to be careful with this code but in this case you cannot do nothing. You cannot also cast $view to View like in Java:
$this->template->content = (View)$view; //impossible :(
$view and $this->template->content are dynamic typing variables and you cannot change it. Just take care to not assign another type to your variable and everything should work fine.
I wanted to add some info to the answer Kasyx is giving. Everything he says is correct but there is an alternative to set variables in kohana if you hate dynamic typing or like some clarity in what your views are doing. (Template is just another view ;) )
You can also set variables in views with the set() function (docs) eg:
$view->set('foo', 'my value');

How do I get magento backend config xml data?

I have a system.xml in my module, which starts with this:
<config>
<sections>
<dev>
<groups>
<my_module>
<label>...
I want to get the value of this label, from a different module. How do I do it? My first thought, was Mage::getConfig('sections/dev/groups/my_module/label'), but this doesn't work - it seems the <sections> area of the config is not accessible. I also can't figure out where magento is loading this value, which it must do at some point, or it wouldn't be able to display it.
To be completely clear: I am not trying to get the config data value as stored in the core_config_data table, that's no trouble. I want to be able to get the other attributes relating to it - like the group label, or the sort order of the fields, and to do that I need to be able to read the <sections> area of the config.
The system.xml files are never merged with the global configuration. They're only loaded when Magento builds the user interface for the
System -> Configuration
section of the backend admin application. Other than that the application has no use for them.
If you want to grab the label, you'll need to load the full system.xml configuration yourself. Something like this should work.
//load and merge `system.xml` files
$config = Mage::getConfig()->loadModulesConfiguration('system.xml');
//grab entire <sections/> node
var_dump($config->getNode('sections')->asXml());
//grab label from a specific option group as a string
var_dump((string)$config->getNode('sections/dev/groups/restrict/label'));
As mentioned in another answer in this thread, there's also an adminhtml/config model class which wraps some of this logic in a getSection method, so you could do something like this.
Mage::getSingleton('adminhtml/config')->getSection('dev')->groups->my_module->label
If you look at the source of getSection
#File: app/code/core/Mage/Adminhtml/Model/Config.php
public function getSections($sectionCode=null, $websiteCode=null, $storeCode=null)
{
if (empty($this->_sections)) {
$this->_initSectionsAndTabs();
}
return $this->_sections;
}
and follow the call stack through to _initSectionsAndTabs
#File: app/code/core/Mage/Adminhtml/Model/Config.php
protected function _initSectionsAndTabs()
{
$config = Mage::getConfig()->loadModulesConfiguration('system.xml')
->applyExtends();
Mage::dispatchEvent('adminhtml_init_system_config', array('config' => $config));
$this->_sections = $config->getNode('sections');
$this->_tabs = $config->getNode('tabs');
}
You'll see this wrapper method eventually calls the loadModulesConfiguration method itself. The additional applyExtends if an old bit of meta-programming in the configuration you can read about here, which is part of a longer series on configuration loading. (self-links, too long for a StackOverflow answer).
The reason I personally wouldn't use this to grab values out of the configuration is the event that's dispatched when you make this call
Mage::dispatchEvent('adminhtml_init_system_config', array('config' => $config));
This event may trigger code in your system that assumes you're loading the system configuration system in the backend admin console area. If you just want to read the XML tree. simply loading it yourself and reading the values seems the way to go. Your use case, of course, may vary.
As so often seems to be the case, I find the answer moments after posting the question...
This is how to get sections/dev/my_module/label:
Mage::getSingleton('adminhtml/config')->getSection('dev')->groups->my_module->label
As you can see, you need to use Mage::getSingleton('adminhtml/config')->getSection('dev') to get the backend config (you can also use ->getSections() to get all the sections to iterate over). This returns a Mage_Core_Model_Config_Element Object, which is the root of a tree of objects, accessible as shown. Just do a print_r at any stage and you'll see the rest of the tree, which print_r formats like an array, although it's not.

Doctrine 2: Force scheduleForUpdate on a non-changed entity

How can I schedule an entity for update, manually, when no property is actually changed?
I tried $entityManager->getUnitOfWork()->scheduleForUpdate($entity) but it gave an error in the core, and I have no intetion of debuging Doctrine.
The entity is managed if it matters: $entity = $repository->findOne(1)
I need this so doctrine would call my EventSubscriber on flush().
I've also tried something like $entityManager->getEventManager()->dispatchEvent(\Doctrine\ORM\Events::preUpdate), but then my listener's preUpdate() receives EventArgs instead of PreUpdateEventArgs.
Any help is appreciated!
Method mentioned by Wpigott not working for me (at least in doctrine/orm v2.4.2), instead I'm using this:
$entityManager->getUnitOfWork()->setOriginalEntityProperty(spl_object_hash($entity), 'field_name', '__fake_value__');
Where field_name existent property name.
The solution is a bit hacky, but I was able to achieve this by doing something like the following.
$objectManager->getUnitOfWork()->setOriginalDocumentData($object, array('__fake_field'=>'1'));
This essentially causes Doctrine to think the document has changed from the original, and it computes it as a change which will cause the events to be executed on flush.
The example is for the MongoODM solution, but the same technique should work for ORM like below.
$objectManager->getUnitOfWork()->setOriginalEntityData($object, array('__fake_field'=>'1'));
Even though this question is quite a bit old, I just found a way much more elegant to solve this problem I want to share here using Doctrine ORM 2.6.2:
You can simply tell the ClassMetadataInfo object of your table to just state those fields as dirty that you pass to the propertyChanged function of the UnitOfWork. The important parts here are the setChangeTrackingPolicy and propertyChanged calls:
$unitOfWork = $entityManager->getUnitOfWork();
$classMeta = $entityManager->getClassMetadata('Your\Table\Class');
$ctp = $classMeta->changeTrackingPolicy; # not a function but a variable
# Tell the table class to not automatically calculate changed values but just
# mark those fields as dirty that get passed to propertyChanged function
$classMeta->setChangeTrackingPolicy(ORM\ClassMetadataInfo::CHANGETRACKING_NOTIFY);
# tell the unit of work that an value has changed no matter if the value
# is actually different from the value already persistent
$oldValue = $entity->getValue('fieldName'); # some custom implementation or something
$newValue = $oldValue;
$unitOfWork->propertyChanged($entity, 'fieldName', $oldValue, $newValue);
# field value will be updated regardless of whether its PHP value has actually changed
$entityManager->flush();
# set the change tracking policy back to the previous value to not mess things up
$classMeta->setChangeTrackingPolicy($ctp);
You may also want to have a look at the Doctrine\Common\NotifyPropertyChanged interface.
I hope this will be useful for someone.
What's the goal? If there is no property changed, why would you plan an update?

Categories