27

(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,

  1. Am I supposed to write them by hand? (really?)
  2. Will these just be public variables or standard properties with getter/setter (excuse me for my .NET mentality :))?
  3. 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.

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • Maybe this could help you: https://stackoverflow.com/questions/30560485/create-models-from-database-in-laravel-5 – Rafael Aug 08 '18 at 11:33
  • Maybe this can help: [Laravel: Eloquent Getting Started](https://laravel.com/docs/5.6/eloquent#defining-models). But basically, yes, you write them by hand. There are packages available that can generate models from an existing database – brombeer Aug 08 '18 at 11:38
  • @RafaelBoszko: That hits the nail on the head (nail's head that is :)). Thank you. – dotNET Aug 08 '18 at 11:43
  • Did you ever resolve this so that you get auto-complete benefits? I too come from C# and am used to things being a little more explicit and not "automagic". – Louise Eggleton Nov 15 '19 at 12:49
  • @LouiseEggleton: I have spent more time working with Laravel. Basic problem (or call it feature) is that a class doesn't need to contain a member at compile-time, because there is no *compile time* so to speak. PHP interprets the code at runtime and generates members on the fly. So a model class does not require you to have any members at all. This is very unlike C# world where we have strongly-typed models (Entity or even DataSets) with a 1-1 correspondence between class properties and underlying table columns.All this means there will be no auto-complete kind of thing in VS Code for Laravel. – dotNET Nov 15 '19 at 17:24
  • @LouiseEggleton: There is a nice little tool that can create model classes from your database (see Rafael's comment above), but even that tool generates DocBlocks (comments) and not actual class members. You can write them by hand if you must, but having worked with it for some time, I'd say it is unnecessary. – dotNET Nov 15 '19 at 17:28
  • Thanks @dotNet. Because I like strongly types models, I may write them in manually. I like defensive coding. I usually don't have setter methods in c#, because I make a lot of my classes require instantiation via constructor only. eg { get; } no set. So I am probably going to do something in PHP like create public properties and set via constructor. – Louise Eggleton Nov 15 '19 at 17:35
  • @LouiseEggleton: You missed the point. There is no defense AFAIK in PHP against what you're trying to prevent. If you type `obj.Nmae = "Louise";` instead of `obj.Name = "Louise";`PHP will happily accept it and add a new member `Nmae` to `obj`, irrespective of whether you do or don't a setter. (but take my advice with a grain of salt, because my C# experience is of near 2 decades, and that of Laravel is only less than a year). – dotNET Nov 15 '19 at 17:47
  • But wouldn't the explicit properties at least help with intellisense and also show intent? – Louise Eggleton Nov 15 '19 at 17:54
  • JavaScript also has no type safety, but I still appreciate things like intellisense, autocomplete and linters to help make up for lack thereof. – Louise Eggleton Nov 15 '19 at 18:08
  • @LouiseEggleton: Intellisense is a feature of the IDE, not the language. If there is a [lex. analyzer](https://github.com/felixfbecker/php-language-server) that parses PHP code and gives members info, that's just what you need for Intellisense to work. There're a few [plug-ins](https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-intellisense) that are supposed to do just that, but I've had a hard time getting them to work with my models. They just show *everything* in the Intellisense dropdown instead of the ones dictated by the context. LMK if you get them to work like C#. – dotNET Nov 16 '19 at 05:37

6 Answers6

26

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

enter image description here 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).

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • 10
    You don't need to define the properties, but indeed you *must not* define the properties. Defining them breaks the assumptions of Eloquent. This is a design choice that I don't understand, but more annoyingly is not mentioned on the Laravel documentation pages. – aross Oct 06 '20 at 15:19
  • 4
    I had this puzzling me when I moved to laravel from symfony. Like yourself I program many languages and I much prefer to strict type, you can code php strict or loose in 7+. in symfony I use doctrine orm for the database, they are called entities instead of models but they are just a coded version of your table and use repositories to make queries. You actually declare the properties with getters and setters. Laravel uses magic methods to create the properties, I don’t like it but I do like laravel in general so i live with it. Using the docblock approach works ok though to balance it a little – MMMWeirdo Jan 20 '22 at 20:07
  • Well, how about now when dynamic properties are considered deprecated in PHP 8.2 (https://www.php.net/releases/8.2/en.php#deprecate_dynamic_properties)? I could not find anything regarding this in Laravel documentation. – Boris N Feb 10 '23 at 00:37
  • 1
    Model attributes are not dynamic properties. Laravel uses the __get and __set magic methods to map database columns into a property, which is an array of attributes. – Deivide Feb 23 '23 at 19:33
8

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';
Holonaut
  • 166
  • 2
  • 11
3
  1. 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)
  2. 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

  1. 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.
frzsombor
  • 2,274
  • 1
  • 22
  • 40
NicuVlad
  • 2,491
  • 3
  • 28
  • 47
  • Thanks. Can you include your model class definition in the answer? – dotNET Aug 08 '18 at 12:07
  • Check my updated answer, I hope this is what you asked for. If you need something else I can update my answer. – NicuVlad Aug 08 '18 at 12:16
  • Now you're close to what I'm asking. What is `$newPost->title` in the above code? There is no such property or public variable in your class. Even if there were, how would that variable map to the underlying table column? – dotNET Aug 08 '18 at 12:29
  • The title is the "title" column from your DB, if you need to create a post and add a "title" value then you need to add the title in the "$fillable" array. – NicuVlad Aug 08 '18 at 12:33
  • By default probably you have also an "id", "created_at" and "updated_at" create by Laravel, but in this array you need to declare only the properties that you need to change. – NicuVlad Aug 08 '18 at 12:36
  • I'm deeply confused. You have not declared any property or variable named `title` in `Post` class. Why would it return anything for `$this->title`. Why would this even compile? PHP should straight away throw compile-time error saying that title is not a member of Post class. – dotNET Aug 08 '18 at 17:44
  • Pardon my ignorance. I just found out that PHP does not complain about non-existing class members and instead generates them on the fly. 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. – dotNET Aug 08 '18 at 18:23
  • @dotNET Bear in mind that this is just how Laravel does it, using PHP's "magic" methods. You can do normal OOP in PHP! – Charles Wood Aug 04 '20 at 23:41
  • This is not how the `$fillable` array works. When assigning values to properties individually (like you're demonstrating), Laravel does not check the $fillable array. `$fillable` and `$guarded` (white and black lists respectively) are only checked when creating or updating models using "mass assignment". Something like, `Post::create(['title' => 'My Title', 'body' => 'My Body'])` or `$post->update([...])`. – JoshP Jan 19 '23 at 16:04
1

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;
Dennis
  • 200
  • 15
  • I'm deeply confused. You have not declared any property or variable named `name` in Post class. Why would it return anything for `Post::find(1)->name`. Why would this even compile? PHP should straight away throw compile-time error saying that `name` is not a member of Post class. – dotNET Aug 08 '18 at 17:46
  • Pardon my ignorance. I just found out that PHP does not complain about non-existing class members and instead generates them on the fly. 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. – dotNET Aug 08 '18 at 18:21
0

You need to add by yourself. for example

$fillable = ['firstname', 'email','username'];
$guarded = ['price'];
PHP Geek
  • 3,949
  • 1
  • 16
  • 32
  • 1
    I'm looking for adding public properties against my columns, so that I get code-time benefits like auto-completion and detection of spelling mistake etc. – dotNET Aug 08 '18 at 17:48
0

this is not specific to PHP or Laravel, it is another "design pattern" of ORM: Active Record. Entity Framework is a Data Mapper ORM. There are a lot of articles and stuffs that explain the 2 pattern, like this one

Several other languages and frameworks use Active Record ORM, like Ruby on Rails and Python Django.

But of course, it is not the only choice, PHP have Doctrine, a Data Mapper ORM just like EnityFramework

Lumethys
  • 81
  • 5