Typehint inherited class variables in PHPStorm - phpstorm

In PHPStorm, I can type-hint a variable this way:
/** #var Point $point */
$point->x();
However, say I inherited a variable from a parent class, and want to type-hint it:
class PointProxy extends Proxy
{
public function x()
{
...
/** #var Point $this->geometry */
return $this->geometry->x();
}
}
This doesn't work, PHPStorm acts as if I had type-hinted $this, and not $this->geometry.
Is there a way to make such a type-hint work without redeclaring the $geometry property in the subclass, or is this unsupported?

Try this code. Also you can press alt+enter at undefined properties and select Add #property it will help you to create phpdoc faster.
/**
* #property Point $geometry
*/
class PointProxy extends Proxy {
public function x() {
return $this->geometry->
}
}

If the parent object types the property as Geometry but you want it typed as a Point (which descends from Geometry) in your child class, I would recommend creating an accessor in your child class. Possibly with some type checking.
class PointProxy extends Proxy
{
/**
* Access the geometry object on parent class as a Point
*
* #return Point
*/
private point()
{
if(!is_a($this->geometry, 'Point'))
{
// Log an error or something, this is not a state we should be in
}
else
{
return $this->geometry;
}
}
public function x()
{
...
return $this->point->x();
}
}

I ran into a very similar problem. I had a generic storage class that dealt with database operations and then a proxy class on top which proxied all storage class methods through a try catch (using __call()) so that I could handle exceptions in one location.
Now whenever I accessed the storage instance like $storage->retrievePhoto($id), the PHPStorm IDE could not typehint for me. My solution involved adding another class name annotation to the $storage object.
For example, see below. Since the specific proxy class is really just a wrapper over the original storage class, it doesn't present any problems although it is still not 100% to my liking but it works.
final class PhotoRepository
{
/**
* #var \Repositories\Photos\PhotoStorage
* \Repositories\Photos\PhotoStorageExceptionHandlerProxy
*/
private $storage;
/**
* #param \Repositories\Photos\PhotosStorageExceptionHandlerProxy $storage
*/
public function __construct(PhotosStorageExceptionHandlerProxy $storage)
{
$this->storage = $storage;
}

Related

Symfony, How to use DenyAccess with Doctrine object?

I want to control the same user access on some methods in my controller.
Currently, I use this :
$this->denyAccessUnlessGranted('ACCESS', $this->Player($toolRepository));
However I am forced to use this line and inject the ToolRepository into each method ...
What would be the easiest way to do it?
I saw that there was the IsGranted annotation but my subject needs to be a doctrine object to control access with my Vote.
/**
* #Route("/player")
*/
class PlayerController extends AbstractController
{
/**
* #Route("/", name="player")
* #throws Exception
*/
public function Player(ToolRepository $toolRepository): \App\Entity\Tool
{
$playerTool = 'TestTool2';
$tool = $toolRepository->findOneBy(array('libelle' => $playerTool));
if (!$tool) {
throw new Exception('Tool : ' . $playerTool . 'not found!');
}
return $tool;
}
/**
* #Route("/main", name="player")
* #IsGranted ("ACCESS", subject="tool")
* #throws Exception
*/
public function mainPlayer(PlayerRepository $playerRepository, ToolRepository $toolRepository): Response
{
$this->denyAccessUnlessGranted('ACCESS', $this->Player($toolRepository));
$players = $playerRepository->findAll();
return $this->render('player/player_mainpage.html.twig', ['players'=>$players]);
}
}
I think this ressource should answer you: Symfony voters.
You'll put your security logic in your custom voter which will be called in every function of your controller (or every methods where you want to control access) isGranted() function.
Calling your Player() function is a easier way to do this for beginner, you can keep like that but you shouldn't put it in Controller and use a Service instead .
Edit:
You may store your ToolRepository as Controller private property and inject it in a __construct() method so you don't have to inject ToolRepository in each method

How to override system.mail.yml in Drupal 8?

I have the below code in my file: core\modules\system\config\install\system.mail.yml
interface:
default: 'php_mail'
I want to change the code to:
interface:
default: 'SMTPMailSystem'
In order to get my SMTP module to work. On changing the code in the core file my module works. Since making direct changes in core file is not good I want to know how do we override such files. I am fairly new to Drupal 8 hence couldn't get through.
Drupal has an article on Configuration override system, which gives an overview and starter code to override configurations defined in *.yml . You can jump to "Providing overrides from modules" section right away for your case.
In short:
Create a module (config_example used as an example)
Create a config_example.services.yml, and put:
services:
config_example.overrider:
class: \Drupal\config_example\ConfigExampleOverrides
tags:
- {name: config.factory.override, priority: 5}
config.factory.override is the important thing here, others are up to you to change.
Define the class which implements ConfigFactoryOverrideInterface:
namespace Drupal\config_example;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Example configuration override.
*/
class ConfigExampleOverrides implements ConfigFactoryOverrideInterface {
/**
* {#inheritdoc}
*/
public function loadOverrides($names) {
$overrides = array();
if (in_array('system.mail', $names)) { // (a)
$overrides['system.mail'] = [
'interface' => ['default' => 'SMTPMailSystem']
];
}
return $overrides;
}
/**
* {#inheritdoc}
*/
public function getCacheSuffix() {
return 'ConfigExampleOverrider'; // (c)
}
/**
* {#inheritdoc}
*/
public function getCacheableMetadata($name) {
return new CacheableMetadata();
}
/**
* {#inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
}
The following thing is changed to work for your case:
(a) The in_array needle is changed to system.mail which is the YML you wish to override. The value assigned to $overrides['system.mail'] is changed to what you wish to be placed.

How to get grid behavior columns dynamically?

I'm making a plugin system and I'm dynamically adding behaviors with relation to Order model. For example:
class OrderBehavior extends Behavior
{
public function getOrderTrackNumber()
{
return $this->owner->hasOne(TrackNumber::class, ['order_id' => 'id']);
}
}
At runtime I don't know which plugins (and therefore which behaviors) are activated.
How can I get all relation properties (in example orderTrackNumber) dynamically for render in GridView columns?
You may use getBehaviors() to get all active behaviors attached to model. At this point you should probably implement some interface for behaviors which may add new relations, so they will be able to provide list of defined relations - it may save you performance hell (browsing all behavior's methods and searching relations definitions may be slow and unreliable). For example:
interface BehaviorWithRelationsInterface {
/**
* #return string[]
*/
public function getRelationsNames(): array;
}
Then in model:
/**
* #return string[]
*/
public function getAllRelationsNames(): array {
$relations = [];
foreach ($this->getBehaviors() as $behavior) {
if ($behavior instanceof BehaviorWithRelationsInterface) {
$relations = array_merge($relations, $behavior->getRelationsNames());
}
}
// add relations defined directly in model
$relations[] = 'user';
return $relations;
}
If you don't miss anything, getAllRelationsNames() should return names of all relations defined in model.
there's no built in method in yii2 that returns all relations of a model,
you can either check your attached behaviours via behaviours() method,
or, if you need a complete list, check via Reflection whether your getters and attached behaviors return an object that implements relation interface.

Yii2 field accessed only via magic method

/**
* This is the model class for table "hashtag".
*
* #property string $text
*
* #property TweetHashtag[] $tweetHashtags
* #property Tweet[] $tweets
*/
class Hashtag extends ActiveRecord
{
.........
public function getTweetHashtags()
{
return $this->hasMany(TweetHashtag::className(), ['hashtag_text' => 'text']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getTweets()
{
return $this->hasMany(Tweet::className(), ['id' => 'tweet_id'])->viaTable('tweet_hashtag', ['hashtag_text' => 'text']);
}
}
When I do in some component
$hashtags = Hashtag::find()
->with('tweets')
->where(['text' => $hashtagText])
->all();
foreach($hashtags as $hashtag)
{
print_r($hashtag->tweets);
}
It`s working but why tweets - field accessed only via magic method and how can i fix it? And tweetHashtags working well.
Class Tweet have same relationship but public function getHashtags() working without this problem.
Your question is not clear. Each method on a Component class that start with get (like getName) can be accessed with property form (e.g. name). On special case, relations of Yii's ActiveRecord, if you access to relation by property form, you get results. In fact $this->tweets is a shorthand for $this->getTweets()->all().
P.S: On Yii2 Document http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#accessing-relational-data:
Note: While this concept looks similar to the object property feature,
there is an important difference. For normal object properties the
property value is of the same type as the defining getter method. A
relation method however returns an yii\db\ActiveQuery instance, while
accessing a relation property will either return a yii\db\ActiveRecord
instance or an array of these.
$customer->orders; // is an array of `Order` objects
$customer->getOrders(); // returns an ActiveQuery instance
This is useful for creating customized queries, which is described in the next section.

AS3 access class instance from everywhere

for my current project I am starting to work with AS3 and I have written a ClipManager class where I can define an MC like "mainView" during initialization like this:
clipManager:ClipManager = new ClipManager(mainView);
With my clipManager I can now easily load stuff into the mainView etc. The problem is that I want every button throughout the whole thing to access Class Methods of this instance to alter the mainView. Can I have something like a global Class instance in Flash or is there any smarter way to achieve what I am trying to do?
You can either add your ClipManager class as a static somewhere - i.e. a god object - (perhaps your main class) and access it through that, or you can use the Singleton pattern.
A common way to implement it in as3:
public class Singleton
{
private static m_instance:Singleton = null; // the only instance of this class
private static m_creating:Boolean = false;// are we creating the singleton?
/**
* Returns the only Singleton instance
*/
public static function get instance():Singleton
{
if( Singleton.m_instance == null )
{
Singleton.m_creating = true;
Singleton.m_instance = new Singleton;
Singleton.m_creating = false;
}
return Singleton.m_instance;
}
/**
* Creates a new Singleton. Don't call this directly - use the 'instance' property
*/
public function Singleton()
{
if( !Singleton.m_creating )
throw new Error( "The Singleton class can't be created directly - use the static 'instance' property instead" );
}
}
Now, to access your class, you call Singleton.instance. There'll only ever be one instance of this class.
As for anti-patterns etc, well that's another post :)