Going through the auto generated code of YII for a Model class, I understand that the table columns are injected into the Model class through Annotations (#property)
<?php
/**
* This is the model class for table "tbl_project".
*
* The followings are the available columns in table 'tbl_project':
* #property integer $id
* #property string $name
*/
class Project extends CActiveRecord
{
Here property $id and $name become part of the Project class and can be accessed like such:
$proj = new Project();
$proj->id = 1;
I tried to look up annotations in PHP but found nothing as all links point to either PHPDoc . I am more interested in the Dependency Injection part of it. Can someone explain the concept please and point to a list of available annotations.
Yii does not use annotations.
It uses table schema extracted from database.
If you remove annotations all will work.
This would be interesting for you http://www.yiiframework.com/doc/api/1.1/CDbTableSchema
And here are some instructions how to speed up your app. One of ways is to enable schema caching. http://www.yiiframework.com/doc/blog/1.1/en/final.deployment
The Block comments in are only to use with PHPDoc or for your own sense.
Although my IDE (PhpStorm) uses the phpdoc block comments and its properties for code inspection and code hinting.
As said in the comments, Yii does not parse the block comments for dependency purposes.
Related
(I come from Visual Studio + Entity Framework background and trying to locate equivalent functionality in Laravel + Eloquent)
In EF and Visual Studio, we add a new Model to our application and just tell it about our existing database. EF can then generate Models for my tables with public properties for columns. This gives us all those IDE and compiler benefits such as Intellisense, spelling-error detection etc.
I've recently stated exploring VS Code, Laravel and Eloquent. Going through all those tutorials and articles, I'm not sure when and how these properties are generated in the model classes. I just tried artisan make:model command and it did generate the model class, but there are no properties in it. So,
Am I supposed to write them by hand? (really?)
Will these just be public variables or standard properties with getter/setter (excuse me for my .NET mentality :))?
Is there a tool/extension that could examine my database and create models with properties for their columns?
Update
To the people who answered my question, thanks a lot. Plus some of the comments I posted were due to my ignorance about PHP's (strange IMO) approach about member access; I just found out that PHP does not complain about non-existing class members and instead generates them on the fly (e.g. $post->NonExistingMember = SomeValue runs okay; this would not even compile in most other languages that I know). Big surprise for me. I have used C++, VB, C#, Java among several other languages and haven't seen that behavior anywhere else. All those languages would throw a compile-time error straight away saying something like Type X does not contain a member named Y. Cannot see how PHP's different approach fits together with OOP.
The actual problem that I posted this question for still remains unresolved. Although I can use reliese/laravel to generate Model classes for my database, the tool still does not generate class members against table columns, so I do not get auto-complete benefits. I'd love to hear from the experts if that can be done (automatically of course).
Update 2
Now that I understand Laravel environment slightly better, I thought I'd share my experience. See my answer below.
Now that I have spent some time with Laravel, Eloquent and PHP in general, I'll share a few things in the hope that these helps other starters.
PHP is a dynamic language and its code is compiled on the fly (this is unlike C# and VB.NET). Your model classes do not need to explicitly define members for them to be accessible/assignable, so as long they extend Model (a built-in class in Laravel Eloquent), you can assign values to non-existing members that have the same name as the underlying database table column and Eloquent will store it in the DB for you. So for example, if you have a posts table in your database that has a column named body, you can write the following code to create a new record in your database:
$p = new Post;
$p->body = 'Some stuff';
$p->save();
Of course you need to have a class Post in your project that extends from Model but you don't need to define a member body inside that class. This would sound strange to the people coming from .NET world, but that's how dynamic languages work.
As for automatically generating models, Laravel includes built-in commands (php artisan make:model) that can generate those for you.
Lastly, for intellisense and auto-complete, use the same tool that is used by Laravel itself, i.e. DocBlocks. These are special type of comments in PHP using which you can document your code elements. So you can add DocBlocks to all your model classes containing property names and types. Fortunately for everyone, there is a very neat extension in VS Code that can do this automatically for you. Install it using the following command:
composer require --dev barryvdh/laravel-ide-helper
Now run the following command to generate DocBlocks for all of your model classes (obviously you should already have generated your database and models before this):
php artisan ide-helper:models --dir='app'
The extension will fetch the structure of your database and inject DocBlocks to all your models, which will look something like this:
/**
* App\User
*
* #property int $id
* #property string $name
* #property \Illuminate\Support\Carbon|null $created_at
* #property \Illuminate\Support\Carbon|null $updated_at
* #method static \Illuminate\Database\Eloquent\Builder|\App\User whereCreatedAt($value)
* #method static \Illuminate\Database\Eloquent\Builder|\App\User whereId($value)
* #method static \Illuminate\Database\Eloquent\Builder|\App\User whereName($value)
* #method static \Illuminate\Database\Eloquent\Builder|\App\Exam whereUpdatedAt($value)
* #mixin \Eloquent
*/
class User extends Model
{
}
VS Code will now show you table field names in model properties, like this (see how intellisense brings up name member from our DocBlocks as we type na...):
Note that I also have Intelephense installed in my VS Code, though I'm not sure if that is required for auto-complete feature to work.
Edit
Dynamic Properties have been deprecated in PHP 8.2 and I'm hearing that they'll become invalid in PHP 9.0, which means Laravel models should not be able to do this magic stuff in the future versions.
I'm not a PHP guru, but I hear that we don't need to panic. Two things: Firstly, the objects implementing __get and __set will keep working fine. Secondly, Plus you (they) can also use #[AllowDynamicProperties] on model classes to allow dynamic props. And lastly, they can rewrite model generator to spit out column names as props in the model class. This last one will be the best and will take PHP one step closer to how C# world works (precisely where this post started, lol).
I use annotations to declare all the properties for autocomplete (works in PHPStorm, not sure about other IDEs).
/**
* #property $id
* #property $microsoft_id
* #property $name
* #property $qualification
* #property $company
*/
class ShopTenant extends Model
{
public $connection = 'shop';
public $table = 'tenants';
protected $guarded = ['id'];
}
You can use something like this to get a list of all columns of a table, then paste/format that list into your annotations:
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'tenants';
Unfortunatilly yes, you need to write them by hand, but only if you need to update the value of these properties, check point 2($fillable array)
You need to declare the properties that can be filled:
For example a model called Post for a database table "posts" that has 2 columns "title" and "body" :
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
//not mandatory to declare the table name, Laravel will find it using reflection if you follow the naming standard
protected $table = 'posts'; //not mandatory
protected $fillable = ['title','body'];
}
In the controller:
$newPost = new Post;
$newPost->title = 'A new title';
$newPost->body = 'A new body';
$newPost->save(); //only at this point the db is modified
Or you can hide them if you return the properties in an array or a JSON response(in the Collection also the hidden ones will be displayed):
protected $hidden = [
'title',
];
Also you can inject new properties in the model using Accessors
I don't think so, you can install some Laravel VS Code plugins to make your life easier(e.g: Blade snippets)
You can check this article.
Actually you don't need to specify the properties. Its all done by laravel automatically. When you create a model laravel uses the plural (and it has a really good system for that: post becomes posts, activity becomes activities, etc.) of the classname to access the table. So you can work with an empty model without setting the $table or the $fillable/$guarded property.
// use only, if your table name differs from the models classname
$table = 'users_options'; // when you want to name your model 'Vote' but the table is called 'users_options' for instance.
// use fillable and guarded only to specify mass-assignment columns
$fillable = [whatever, ...];
$guarded = [whatever, ...];
you can access the properties whenever you want:
class Post extends Model
{
}
// would do sth like this: select name from posts where id = 1;
// without specifying anything in the model
$name = Post::find(1)->name;
You need to add by yourself. for example
$fillable = ['firstname', 'email','username'];
$guarded = ['price'];
I am recently start working on Phalcon. It is very good framework. I am trying to understand the design pattern of this.
Suppose that i am working on Form module. There is a class named Text and i am using this as $text = new Text("field_name");, Text class in included from use Phalcon\Forms\Element\Text;. Now i checked this namespace directory and found the Text class but there are only a render function. Here it is-
namespace Phalcon\Forms\Element;
/**
* Phalcon\Forms\Element\Text
*
* Component INPUT[type=text] for forms
*/
class Text extends \Phalcon\Forms\Element
{
/**
* Renders the element widget
*
* #param array $attributes
* #return string
*/
public function render($attributes = null) {}
}
Nothing is here for create a form element so i checked the parent class of Text but also this is inherited by an interface and its only define the all fucntions which present in its parent interface.
There are no defication for create a form elemet. Then how it's works?
How it can produce the code for an form element?
I also find such things in another module also. Please explain, is this any design pattern in oops?
I also works in npm and also checked packages they also have same design pattern.
You just checked php stubs. Phalcon is framework working as php extension wrriten in zephir which is translated to C and then compiled as extension and you can't edit and check it source code so easy.
Here you have this class - https://github.com/phalcon/cphalcon/blob/master/phalcon/forms/element/text.zep
First i think you should learn and check docs what you are actually using before start using it.
Is it considered a bad practice to add fields to Symfony entity in controller? For example lets say that I have a simple entity:
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
}
And then in UserController.php I want to do the following:
foreach($users as $user){
$user->postsCount = someMethodThatWillCountPosts();
}
So later that postsCount can be displayed in Twig. Is it a bad practice?
Edit:
It's important to count posts on side of mysql database, there will be more than 50.000 elements to count for each user.
Edit2:
Please take a note that this questions is not about some particular problem but rather about good and bad practices in object oriented programming in Symfony.
As #Rooneyl explained that if you have relation between user and post then you can get count easily in your controller, refer this for the same. But if you are looking to constructing and using more complex queries from inside a controller. In order to isolate, reuse and test these queries, it's a good practice to create a custom repository class for your entity.Methods containing your query logic can then be stored in this class.
To do this, add the repository class name to your entity's mapping definition:
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
//...
}
Doctrine can generate empty repository classes for all the entities in your application via the same command used earlier to generate the missing getter and setter methods:
$ php bin/console doctrine:generate:entities AppBundle
If you opt to create the repository classes yourself, they must extend
Doctrine\ORM\EntityRepository.
More Deatils
Updated Answer
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundreds of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This can lead to pretty serious performance problems, if your associations contain several hundreds or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection: SOURCE
"rather about good and bad practices in object oriented programming"
If that's the case then you really shouldn't have any business logic in controller, you should move this to services.
So if you need to do something with entities before passing them to twig template you might want to do that in specific service or have a custom repository class that does that (maybe using some other service class) before returning the results.
i.e. then your controller's action could look more like that:
public function someAction()
{
//using custom repository
$users = $this->usersRepo->getWithPostCount()
//or using some other service
//$users = $this->usersFormatter->getWithPostCount(x)
return $this->render('SomeBundle:Default:index.html.twig', [
users => $users
]);
}
It's really up to you how you're going to do it, the main point to take here is that best practices rather discourage from having any biz logic in controller. Just imagine you'll need to do the same thing in another controller, or yet some other service. If you don't encapsulate it in it's own service then you'll need to write it every single time.
btw. have a read there:
http://symfony.com/doc/current/best_practices/index.html
Can you help me on this one. I'm new in CodeIgniter and PhpStorm, I'm having a problem. The PhpStorm IDE is showing an error, even though the code works fine (the Persons(controller) class can't find the Person(model) class).
$data['persons']=$this->**person**->get_person(); = on this syntax from Persons class, there is a message "Field person not found in class Persons".
Can you enlighten me on how to resolve this, but actually the output is good and it retrieves the data without issue just a warning message in Persons class.
The person property ($this->property) is not explicitly declared as it is created and accessed via PHP's magic methods.
PhpStorm has no special support for CodeIgniter framework and cannot guess where $this->person is coming from and what type it is.
But you can help IDE -- just a small PHPDoc comment before the actual class -- using #property tag.
/**
* #property Person $person Optional description
*/
class Persons extends CI_Controller {
...and oh magic -- it works:
The answer from LazyOne did not work for me initially. After some testing I found out that my issue was upper/lower case related on how you declare the property in PHPDoc - hopefully the following observations can help someone else. This is what I have to declare my model class:
class Custom extends CI_Model {}
In my controller I load and use the model for example the following way:
$this->load->model('Custom')
$table = $this->Custom->get();
Now for phpStorm to pick up this class correctly I originally added a PHPDoc #property comment above a core class as described by others (either above the CI_Controller class or separate CI_phpStrom.php file) like this:
*
* #property Custom $custom
*
However, this didn't remove the issue, as the variable name becomes case sensitive in this case and I had to write:
*
* #property Custom $Custom
*
for my above controller code to pick up the class correctly. An alternative would be to use lowercase when calling functions (this works even if your model declaration used uppercase)
$this->load->model('custom')
$table = $this->custom->get();
The funny thing all this uppercase or lowercase didn't matter if I call my model class "Custom_model" - then it made no change if set the PHPDoc property variable to $Custom_model or $custom_model ...
Normally most people add the ‘_model’ suffix to the Model class names. I suggest that you rename your model with "Person_model" call your model "person_model" like this:
$this->load->model('person_model');
$this->person_model->get_person();
I think that you might find it an adequate solution!
I write my entities in let's say models/ folder.
something like:
namespace Organisation\User;
/**
#Entity
*/
class Customer {
/**
* #Column(type="integer") #GeneratedValue
*/
protected $_id;
}
}
So I'll instantiate my entity with $customer = new \Organisation\User\Customer();
Ok, but if I use doctrine orm:generate-entities library/ it will generate it under the following directory :
library/Organisation/User/Customer.php
ANd that's ok, but if I look at the code, there aren't any of my annotation, and therefore when I try to use it, I get doctrine\ORM\Mapping\MappingException: Class Organisation\User\Customer is not a valid entity or mapped super class. because there aren't any annotation.
So what I need to do is to remove the namespace, generate into the same directory as the entities with metadata inforations are, move to my library folder, and add the namespace to work with.
It looks ugly, do I have missed something?
edit: I forgot to tell that orm:generate-entities doesn't work recursively, then, I can't even use my actual structure within my entities metadata
If you've written your entity classes already, why are you trying to generate them?
They typical way to drive a new project is to write your annotated entity classes, and use orm:schema-tool:create to generate your database schema.
Most examples I've seen do stick the Entities under library/, in some nested directory based on the namespaced name of the class, just as you've described. This is generally a good thing, as it works with the default Doctrine2 autoloader setup.
If you're not trying to fit Doctrine2 onto an already existing database schema, I'd recommend that you simply stick all your entity classfiles someplace like library//Entity/.php, and use orm:schema-tool:update and orm:schematool:update to manage the database for you.
Use the arg --generate-annotations.