(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'];
Related
I have a Laravel application, and I just switched to timestampTz and timestampsTz in every single migration. As soon as I ran php artisan migrate I immediately ran into "Trailing data" issues with Carbon due to the date format mismatch caused by the change.
I don't want to add the $dateFormat property to every model I create when I have no intention of ever using timezone-less timestamp columns. I also don't want to introduce a trait or make a new superclass that extends Eloquent's Model that I then need to add to every model I already have (and ones that I generate in the future).
Is there any way to avoid all this and just have every timestamp field be treated as if they all had timezones?
This is easily done in Laravel 6 by creating a new Illuminate Grammar class and overriding the getDateFormat method, which is used as a fallback if the dateFormat property is missing on a model.
Have a look inside vendor/laravel/framework/src/Illuminate/Database/Query/Grammars. Your class will need to extend one of the vendor-specific grammar classes found here based on what database you connect to. For this example, I will be extending PostgresGrammar. Adjust app/Providers/AppServiceProvider.php like so:
use Illuminate\Support\Facades\DB;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
* #throws \Doctrine\DBAL\DBALException
*/
public function register()
{
// ...
$conn = DB::connection(DB::getDefaultConnection());
$platform = $conn->getDoctrineConnection()->getDatabasePlatform();
$conn->setQueryGrammar(new class($platform->getDateTimeTzFormatString()) extends PostgresGrammar {
protected $date_format;
public function __construct(string $date_format)
{
$this->date_format = $date_format;
}
public function getDateFormat()
{
return $this->date_format;
}
});
}
}
This will replace the original query grammar with another that will let us take over the date format string. An anonymous class is used to avoid having to create a separate file for this small bit of functionality, but you may choose to move this to its own file for readability. The anonymous class is passed the value of $platform->getDateTimeTzFormatString() as the constructor's only argument, which is then stored for use by the getDateFormat method.
After this change, any trailing data errors should be gone for good. Just make sure to use timestampTz and timestampsTz in every migration going forward. 3rd-party libraries usually let you publish any migrations bundled with them, allowing you to adjust those as needed.
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
I am using cakephp 2.6.9. I have a table named: chat_info and model file: ChatInfo.php and class inside ChatInfo:
<?php
/**
*
*/
class ChatInfo extends AppModel
{
var $name = "chatinfo";
}
?>
but it shows an error. I searched for this error and found that this is due to naming convention violation in cakecaphp. But whats wrong am I doing here
Model::useTable
As found in the docs:
The useTable property specifies the database table name. By default, the model uses the lowercase, plural form of the model’s class name
Conventions are not intended to be unbreakable rules. They are guidelines which, if followed, make life easier. That does not mean they have to be followed. Using useTable it's possible to use any table name, in this case:
class ChatInfo extends AppModel
{
public $useTable = "chat_info";
}
Two asides, assuming you're not actually using php4:
It is a terrible idea to set the model name to something other than the actual model name. It's not necessary to set it to anything, since it's designed purpose is php4 compatibility. The name of a model is the class name, setting it to something else can easily lead to confusion or unexpected side effects.
Declaring variables with var is php 4 style, use the features of the version of php in use, i.e. declare variables using public, protected or private.
if you will rename the table from chat_info to chat_infos it will be ok
Or if you want to mantain that name for the table then add this in your model:
$useTable = 'chat_info';
and your model will be associated to the table chat_info (without plural mode)
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!
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.