Yii2 field accessed only via magic method - yii2

/**
* 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.

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 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.

Typehint inherited class variables in 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;
}

Propel toArray Mechanics in Doctrine?

I was using Propel for a long time and now I want to try Doctrine.
In My Propel days I used PropelObjectCollection::toArray (for a collection) or PropelObject::toArray() for a single record to convert the PropelObject via array to json.
In my company we override the toArray method to store virtual columns in the array and then the json string.
For example:
public function toArray() {
$arr = parent::toArray();
$arr['full_name'] = $this->getFullName(); // full_name isnt part of the table, it's just a getter
return $arr;
}
When I turn this into json i have my full_name property in my json and then in my Extjs store Object (we use extjs).
Now I wanna try doctrine, but doctrine doesn't seem to allow this.
Can i override a function or property in my doctrine class, or can I do this by annotations, is it possible to generate a json with propertys ('first_name', 'last_name', 'full_name') if my Doctrine class only has the properties $first_name, $last_name and no $full_name
or is there a work around to achieve the same?
Thanks for your help
Edit:
I found something in JMSSerializerBundle if you use Annotations:
#VirtualProperty
use JMS\Serializer\Annotation\VirtualProperty;
at the top of my Doctrine Entity File and an example method
/**
*
* #VirtualProperty
* #return string
*/
public function getFullName() {
return $this->getName(). " mylastname";
}
my json then contains the virtual property full_name

PhpStorm cannot autocomplete model attributes

simply I want PhpStorm autocomplete my model's attributes when I use find(), findAll(), findByAttributes() etc...
I have a model like:
/**
* member model parameters:
* #property integer $id
* #property integer $city_id
* #property string $e_mail
*/
class Member extends CActiveRecord
{
/**
* #static
* #param string $className
* #return Member
*/
public static function model($className = __CLASS__)
{
return parent::model($className);
}
...
When I use active record methods like:
$member = Member::model()->findByAttributes(array('e_mail'=>'Foo Bar'));
and try to autocomplete when I wrote this:
$member->
It only gives me CActiveRecord's parameters and methods in the list.
I tried to change
/**
* Finds a single active record that has the specified attribute values.
* See {#link find()} for detailed explanation about $condition and $params.
* #param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
* An attribute value can be an array which will be used to generate an IN condition.
* #param mixed $condition query condition or criteria.
* #param array $params parameters to be bound to an SQL statement.
* #return CActiveRecord the record found. Null if none is found.
*/
public function findByAttributes($attributes,$condition='',$params=array())
{...
this method's return param from CActiveRecord to Member, self, parent, $this, child etc...
Autocomplete only worked when it was "Member". But this method is used for all models not just the Member model so this is not a solution.
If anyone knows the solution (preferably without changing the framework core methods) I will be glad.
NOTE: All of my awesome Yii code is freely available on bitbucket in the repositories here and here. If you hate Yii's verbosity, check out my Pii class. I think you'll totally dig it.
I've run into this and what I do is augment the phpdoc for the static model() method. This corrects the issue and the autocomplete works fine.
For instance:
class MyModel extends CActiveRecord {
/**
* #static
* #param string $className
* #return MyModel|CActiveRecord
*/
public static function model($className=__CLASS__) {
.... yada yada yada ...
}
}
Note the pipe and additional class added to the "#return". This tells PhpStorm to also include that class in the auto-complete lookups.
One additional note, if you're using namespaces, you may need a slash in front of some class names. Just depends on your project and includes.
=============== UPDATE: 2013-08-05 ===============
With PhpStorm v6 and up, it looks like you can use:
*
* #return $this
*
And also get proper auto-completion.
On a side note, the whole static "model()" method thing is antiquated (like Yii) and I have a base model class that I use now for all projects. It contains the static model method; which is then no longer required in each of my subclasses. Here's an example...
<?php
namespace My\Awesome\Name\Space;
/**
* MyBaseModel
* etc.etc.
*/
class MyBaseModel extends \CActiveRecord
{
/**
* Returns the static model of the specified AR class.
*
* #param string $className
*
* #return $this
*/
public static function model( $className = null )
{
return parent::model( $className ? : \get_called_class() );
}
//code code code code
}
/**
* MySubModel
*/
class MySubModel extends MyBaseModel
{
/**
* {#InheritDoc}
*/
public function tableName()
{
return 'my_sub_table';
}
}
$_models = MySubModel::model()->findAll( 'xyz = :xyz', array( ':xyz' => 'abc' ) );
if ( !empty( $_models ) )
{
foreach ( $_models as $_model )
{
// Do awesome stuff...
}
}
Autocomplete works fine for all the subclasses...
Just thought I'd update this and let y'all know.
You can use phpdoc #method. You can use this approach for frequently-used models or you can create new template for code generator.
/**
* #method Member findByPk($pk,$condition='',$params=array())
*/
class Member extends CActiveRecord
{