Database : Data Model

Models are structures built to have a direct relationship with database. Hence, they are capable of performing database queries. Spoova handles database communications based on standard relationships and user session id. Database relationships are handles based on one to one, one to many or many to many relationships. Spoova however, uses specifically designed basic methods to communicate directly with database.

Model::where()
Model::read()
Model::update()
Model::delete()
Model::ofUser()
Model::of()

  • where()
    This method is used to set a situation where a particular condition is met. It is usually called upon the model name itself. Any of the read(), update() and delete() methods can be applied upon it. Assuming we have a model class User as below:

    1a - User model
      <?php 
    
      namespace spoova/mi/windows/Models;
    
      class User extends Model {
    
        function __construct(){
    
        }
    
      } 
                            
    Since the class above is a model, we can access the where() method from a route as shown below:
    Example 1b - Using where() method of a model
      <?php 
    
      namespace spoova/mi/windows/Routes;
    
      use spoova/mi/windows/Models;
    
      class SomeRoute{
    
        function __construct() {
    
            User::where('id = ?', [1])->read()->User; // read user where id is one
            User::where('id = ?', [1])->delete(); // delete user where id is 1
            User::where('id = ?', [1])->update(['firstname'=>'Felix']); // update user where id is 1, set firstname as "Felix"
            
        }
    
      }
                            
    The examples above, are cases in which the where() condition can be applied. The first argument contains list of fields and placeholders for binded parameters while the second argument contains a list of binded parameter values. The assumed table name will be "users". The table name can be modified by re-defining a tableName() method on the model class which must return a string of the custom table name.

  • read()
    This method is used to retrieve data from database. It takes two arguments. The first argument (string or array) is the number of selected columns to return while the second argument (array) defines the limit of data to be returned. Using Example 1a and 1b as reference, the Example 2 below describes how to use the read() method.

    Example 2 - Using read() method
      User::read()->User; // fetch all user data
    
      User::read(['username'])->User; // fetch only the username of every user
    
      User::read(['firstname', 'lastname'], [10])->User; // fetch firstname and lastname of 10 users
                            


  • update()
    This update() method is used to update a selected record. It takes a single array parameter. Which contains key(field name) and value(new) pairs. When a condition is not set upon it, all records will be updated:

    Example 3 - Using update() method
      Posts::update(['date' => 2024-05-24 ]) //update all posts records, set all date rows as "2024-05-24"
        


    It is generally advised to turn off live server when performing operations that modifies the database records to prevent auto-execution of queries.

  • delete()
    This delete() method takes a single parameter which can either be a bool of true or an integer limit of number of data to be deleted. The limit may not be applicable on multiple table. Calling this method without a condition can be dangerous as all records belonging to the relative database table may get deleted. The bool argument of "true" ensures that a developer is aware of the changes they are about to make (i.e deleting all records) before making them. If no argument is supplied, and no condition is set on the delete method, this method will not delete any data. It is also advised to to keep the live server off if this method will be applied.

    Example 4 - Using delete() method
      Posts::delete(true); //delete all posts
        

  • OfUser()
    The ofUser() method is used to pull a data of the current user session account. The database default structure format demands that any table owned by the current user must have a user_id foreign key column that is mapped to the id field on the user's table. When data are obtained using models, ofUser() makes it possible to pull only data related to the current user. For example: If a database table "user" contains an id (primary key) column, then a table (comments) must have a field name with user_id. The user_id is then used to pull data from the comments table which belongs to the current session user.

    Example 5a - PostModel.php
      <?php
    
        namespace spoova\mi\core\class\Models;
        use Models;
    
        Posts extends Models {       
    
    
        }
    
        public static function tableName() {
            return 'Posts';
        }
    
      }
                            


    In Example above, a post model was extended to the Models class with the table name "Posts". When data is pulled from this class, it uses the "Posts" database table. If the ofUser() static method is applied on "Posts" (i.e Posts::ofUser() ), then spoova will try to find the Posts related to the current user id by looking for a user_id field in the Posts table. By default, this method uses the current user id to pull data, however, this can be modified. The ofUser() method takes its first argument as an integer. This integer is the id of the user whose data must be pulled from the database. For example:

    Example 5b - Home.php (route)
      <?php
    
        namespace spoova\mi\windows\Routes;
    
        use Window
        use spoova\mi\windows\Models\Posts;
    
        Home extends UserFrame {       
    
            function __construct() {
    
                $currentUserPosts = Posts::ofUser()->read()->Posts;
    
                $customUserPosts = Posts::ofUser(2)->read()->Posts;
    
            }
    
        }
    
      }
        

    In Example above, a post model was used to obtain data of the current user or a custom user just by setting the user id. This is by far the easiest way to pull data from database without writing any query. Spoova will run its queries internally to pull respective data from the database then store it using the current model name. It should be noted that the read() method is used to read data from the database while the ->Posts is the current Model name which stores the data obtained as a traversable object. When an error occurs, the last error is saved and can be obtained using the read()->DBError property. In the event that the foreignKey field name is not user_id, then a second argument can be supplied into the ofUser() method to define a new key name, In this relationship, the user database table (i.e user) will be the owner while the Model (e.g Posts) table is being owned. We don't need to set the user table since that has been done in the icore/init file during installation.

  • Of()
    The of() method is similar to the ofUser() method. In this method, the table name can be customized if the relationship is between a model and any other database table. The first argument takes a new database table name while other arguments follows the ForeignId and ForeignKey structure respectively. By default, if the owner database table for example is "admin", then a owned table "posts" must have an "admin_id" foreign key field while the owner table must have an "id" local key field which helps spoova to naturally connect the two fields. For example:

    Example 6 - Child of Parent table
      Posts::of('admin', 3)->read()->Posts;
                                    


    In the Example above, the posts database table will look for posts where admin_id is 3. It should be noted that the "admin_id" foreign key is generated from the combination of the singular form of the owner table along with an "id" local key by default. This means that for example, if the owner table was "admin" or "admins", then the default generated foreign key will be "admin_id". This is done by stripping off the last "s" character of the field name. This means that for a string with double "ss" last characters, one will be removed while the other will remain.

    Example 7 - Child with custom foreign key of Parent table
      Posts::of('admin', 3, 'user_id')->read()->Posts;
                            


    The example above is of a more complex relationship between database tables in which the foreign key is a custom user_id field. It is assumed that the custom foreign key supplied in this case is related to the admin table's local key "id". This means that the admin table must have an "id" primary key field.

  • bind()
    The bind() method is a method that can be called upon the model's of() method. The bind() method is used to set up a connection between three database fields. In this connection, the binded table is the highest table while the current model is the lowest table. The database table structures resemble the format below:
    A
    ----
    B
    ----
    C
    The code chain structure, however resembles the format below:
    A
    --
    C
    ------
    B
    The description for the model structure above is listed below:

    • "A" is a child table to "C" while "C" is a child to binded table "B". Hence, "C" table is the link table or bridge between "A" and "B".
    • This connection assumes that if table "A" is a child table, then table "C" is a parent table while table "B" is a grand table.

    In order to set up this connection, the table "A" must have a Foreign key field name "C_id" (modifiable) relative to its direct parent "C" and the table "C" must have a Foreign key name "B_id" (non-modifiable) relative to its direct parent "B". Once the connection is successfully chained, we can proceed to obtain our data through the use of the current model's property which must be initialized with a capital letter case. The code below is an example of this connection.
    Assuming we have a table structure as below:
      comments 
        -id
        -post_id 
    
      posts 
        -id 
        -user_id 
    
      user 
        -id
                            
    In the table structure above, we can link to the owner of the comment's post through the post id.
      Comments::of('posts', 3)->bind('users')->read()->Comments;
                            


    The setup above assumes that, the comments table has a foreign key field of post_id while the posts table has a foreign key field of user_id. From the sample above, the comment table will look within itself for where post_id foreign key is equivalent to posts table local key "id" 3. then the posts table will bind to its own parent "users" through the parent foreign key field name "user_id". This relationship can thus be defined as a complex relationship, one that is defined for a Child, Parent and GrandParent. In the event that the foreign key of the post table on comments table above is not post_id, this can be modified by supplying a third argument of the foreign key field name on the of() method.

      Comments::of('posts', 3, 'foreignKey')->bind('users')->read()->Comments;
                            

Database Relationships
Database relationships have three main structures which are: one to one, one to many and many to many relationships. Spoova model uses static methods to link and obtain database information under these three defined relationships. When working with relationships, data obtained can be accessed through a small letter case property unlike the default with uses an uppercase inital case. The vdump() helper function might serve a good purpose to view the type of data returned.

One to One Database Relationship (matchOne)
In this relationship, a child model class method is placed as a method upon its Parent Model. The type of relationship is then defined. Once the relationship is defined, then we can call the method. It is important to note that the child method must be a static method.

Consider two database tables "user" and "admin" where a user cannot exist twice within the admin table. Using the structure below
Table structure
  user 
    -id 
    -username

  admin 
    -id 
    -user_id
                                    

Here, we need to create the User and Admin Model classes as below:

Admin model
  class Admin extends Model {


  }
                                    


User model
  class User extends Model {

    function __construct(){
        
    }

    function admin(){

        return matchOne(Admin::class);

    }


  }
                                    
In the code above, the User model will try to look for a "user_id" foreign key field within the admin table. The "user_id" is generated from the combination of the current model's name and a default local key name "id". Once the relationship is defined, we can obtain the data by calling the method as below:
Obtain relative data
  User::admin()->read()->admin;
                                    
The code above will return a traversable object containing the obtained data. The read() method is a directive that tells the Model to obtain the defined relationship data between a model and its respective child models. Also, unlike the of() and ofUser() methods that use the Model's name as a property to obtain data, when accessing data under any of the known predefined relationships, the first parameter of any relationship is used to obtain the collected data. For example, the admin property was used to access the data because the first argument of matchOne() is the Admin::class. In situations where the foreign key is not "user_id", we can supply the foreign key as the second parameter. Also if the local key is not "id", we can supply the local key as the third argument just as below:
User model
  class User extends Model {

    function __construct(){
        
    }

    function admin(){

        return matchOne(Admin::class, 'foreign_key', 'local_key');

    }


  }
                                    
The above data simulates a structure or format for setting up a one to one relationship between two database tables. By default, spoova uses the model name of classes as the default database table name. This behaviour can be overidden by setting the model's table name using the tableName() method which can be applied for any model. For example in the code below, the "PostItems" model will assume that the database table name is post rather than postitems because the default behaviour was changed.

PostItems model
  class PostItems extends Model {

    function __construct(){
        
    }

    function admin(){

        return matchOne(Some::class);

    }

    static function tableName() : string{
        
        return 'posts';

    }


  }
                                    



One to One Database Relationship (matchOneFor)
This relationship ensures that we can access the data of a parent model by using the relationship between the parent's child model and the current model

Consider three database tables "student", "book" and "author" as a structure below:

Table structure
  student 
    -id 
    -name
    -book_id

  book 
    -id 
    -author_id

  author 
    -id 
    -user_id
                                    
In the structure above, assuming that each student can only keep a single book from each author, we can access the book's author by declaring the matchOneFor relationship as shown below:
Student model
  class Student extends Model {


    static function bookAuthor(){

        return matchOneFor(Author::class, Book::class);

    }


  }
                                    


In the code above, since the student is connected to the book by the book_id foreign key and the book is connected to the author by the author_id foreign key, the matchOneFor relationship will pull the author data by using the Book model. The matchOneFor(Author::class, Book::class) simply translates as "match one Author::class for Book::class". Once the relationship is defined we can obtain our data as below:

Obtain relative data
  Student::bookAuthor()->read()->author;
                                    

The code above will return a travsersable object containing the obtained data. By default, this relationship assumes that the default local keys for author and book are both id respectively. We can however, overide this condition by supplying a custom Foreign and Local keys for both the author and the book by supplying a third and fourth argument respectively as an array of Foreign and local keys, where the first value of the array is the foreign key while the second value is a local key just as shown below:
Student model
  class Student extends Model {


    static function bookAuthor(){

        return matchOneFor(Author::class, Book::class, ['authorForeignKey', 'authorLocalKey'], ['bookForeignKey', 'bookLocalKey']);
    
    }


  }
                                    

One to Many Database Relationship (matchMany)

Similarly to "one-to-one" relationship, in "one-to-many" relationship, the child model class method is placed on its Parent Model. Within the child Model, the "matchMany" relationship is defined. Once this relationship is defined, then we can call the method.

Consider the database tables "posts" and "comments". Each post can have many comments. Using the table structure below, we can set up a one to many relationship:

Table structure
  posts 
    -id 
    -username

  comments 
    -id 
    -post_id
                                    

Using the table structure above, now we can access our data as below:
  <?php 

    use spoova\mi\core\classes\Model;

    Posts extends Model {

        static function comments(){

            return self::matchMany(Comments::class);

        }

    }

    
Now, we can access our relationship by calling the static method just as below:
  Post::comments()->read()->comments;
    
The above data simulates a structure or format for setting up a one to many database relationship between two database tables. The structure reflects that since the Post model owns many comments, then spoova assumes that we are trying to read the comments which are more than one. We can also modify the foreign and local key of the parent model through a second and/or third argument. as shown below:
  <?php 

    use spoova\mi\core\classes\Model;

    Posts extends Model {

        static function comments(){

            return self::matchMany(Comments::class, 'foreign_key', 'local_key');

        }

    }

    

One to Many Database Relationship (matchedFor) inverse of "matchOne"/"matchMany"
In a similar manner like "one-to-one" and "one-to-many" relationship, the matchedFor sets an inverse relationship for these two relationship (i.e matchOne and matchMany). In this case, the current model where this relationship is defined assumes that it is being owned by a parent model. This relationship can be defined by placing a Parent model class method on its Child Model. Within the Child Model, the "matchedFor" relationship will be defined This means that the first parameter of the matchedFor must be a parent model. Once the relationship is defined, then we can call the method.

  <?php 

    use spoova\mi\core\classes\Model;

    Posts extends Model {

        static function user(){

            return self::matchedFor(User::class);

        }

    }
    
Now, we can access our relationship by calling the static method just as below:
  Posts::user()->read()->posts;
    
Because the relationship above is an inverse relationship, rather than accessing the data by using the parent model's table name (i.e user), instead, we have to access the data through the child model (i.e post). The benefit of this setup is that we get to understand the kind of relationship that exists between the Post model and the User model as one between a child and a parent respectively. We can also modify the foreign and local key of the supplied class through a second and third argument as shown below:
  <?php 

    use spoova\mi\core\classes\Model;

    Posts extends Model {

        static function user(){

            return self::matchedFor(User::class, 'foreign_key', 'local_key');

        }

    }
    

Many to Many Database Relationship (maps)
This relationship is more advanced than the "one-to-one" and "one-to-many" database relations. This can be best explained with a situation in which a user can have many roles while many roles can also be assigned to multiple users. Using a database structure between a user and role tables, a third table "role_user" which is generated through alpabetic conjuction of role and table, we have a sample structure as below:

  users 
    -id
    -name 

  roles 
    -id 
    -role

  user_role 
    -id 
    -user_id 
    -role_id
    
In the code sample structure above, the users and roles table both have a binding relationship through a third table role_user which by default contains fields generated by the singular form of its source tables along with a default "id" local key specific to both parent fields. Both the singular form and the default local key are separated by and underscore "_". It is important to note that the singular form of any table is genarated by stripping off the last "s" character of that table name. This means that while a singular form of users will be user, the singular form of user will also be user. Using the table structure above, we can set up our relationship as shown below:
  <?php 

    class User extends Model {

        static function roles(){

            return maps(Roles::class);

        }

    }
    
In the relationship above, the user class will connect to the roles table by using the relationship it has with the user_role table. If the linking table is not user_role which is the default, then we can supply the custom table name as the second argument. Also, in this relationship, the parent tables users and roles must have an "id" primary key. However, the foreign keys can be modified. The supplied model's custom foreignkey and the current model's foreign key on the linking table can be supplied as the third and fourth argument respectively. Hence we can have a format as below:
  <?php 

    class User extends Model {

        static function roles(){

            return maps(Roles::class, 'bindingTable', 'rolesForeignKey', 'userForeignKey');

        }

    }
    
We can also define an inverse relationship for the above through the Roles class just as below
  <?php 

    class Roles extends Model {

        static function roles(){

            return maps(User::class);

        }

    }
    

Collection

All properties obtained in any database relationship are reflections of the collection class. This means that rather than calling the property itself, data returned can still be accessed using the collection() method. For example:

  User::posts()->read()->posts;

  User::posts()->read()->collection(); //same as above
 
The collection() method will enable us to access the correct property assigned to any relationship.

  Posts::user()->read(['firstname'], 2)->posts;
 
The code above returns two posts, showing only the firstname of the users that made the two posts.

Read method
The read() method is a directive that tells a model to process the relationship defined in a way that the relative data is returned. For example, we can select unambiguos fields easily by declaring the array list of fields we want to obtain as the first parameter of the read method just as shown below:
  User::posts()->read(['firstname','lastname','post'])->posts;
    
In the code above, the read() method will process the data in a way that only the "firstname", "lastname" and "post" of each posts is returned depending on the type of relationship defined. We can also set the limit of data obtained by setting the limit as a second parameter.
  User::posts()->read(['firstname','lastname','post'], [10])->posts;
    
Setting limits on read method works exactly as the query builder crude operator "read()" does. This means that we can also define the position at which the data is being obtained by defining two limits within the second argument.

Modifying Relationships
When setting up relationships, there are certain conditions or modification can be applied to data obtained using predefined methods. These methods are where(), withDefault(), use(), byRecent() and order(). These methods can only be applied before the read() method is called. Once the data is obtained through the read() method, then we can apply other methods which are error(), pull(), protect() and shuffle().
Setting up "Where" condition
   User::where('id = ?', [1])->read()->User; // only user table where "id" field is 1 will be selected
   User::posts()->where('posts.id = ?', [1])->read()->posts; // only posts table where "id" field is 1 will be selected
 
The example above gives an insight into how the where() method can be applied on models or relationships. This method enables us to be able to set certain conditions which our data must meet before it can be successfully obtained. The where() method takes the first argument as a raw string that defines a list of fields and their respective values (or binded parameter placeholders). If the binded parameter sign (i.e "?") is used, then a second parameter that matches the number of expected binded values must be supplied on the where() method.
Setting up "withDefault" condition
   User::where('id=?', [1])->withDefault(['name' => 'Felix Russel'])->read()->User;
  
The "withDefault" condition enables us to set up our data with a default data value if such key does not exist in the data obtained. It is important to note that the default data supplied will not be returned should any error occur from the generated sql query.
Setting up "use" condition
  User::posts()->use([Postclass => ['id'=>'postid'], User::class => ['id' => 'userId']])->read()->posts;
 
In certain situations, where we have two similar fields between two related tables "A" and "B", the field in table "A" may overide the field in table "B". In this situation, it might be difficult to pull the record from the table that was overidden. The use method enables us to customize table names or even fetch a table that was overidden. The code sample is above is a way by which we can pull hidden data. The use() method is supplied an an array argument which contains the model class (or table name) as index while the index's takes an array value of old field name and new custom field name of the related index which in this case is a table or model class. In this way, we can fetch the hidden data without any problem. The index (or model classes) supplied must be related tables, else, an error will be returned. The use() method cannot be applied directly on a model. It can either be binded on the where() method or directly on dynamic relationship methods such as of(), ofUser() or dynamic relationship methods such as the example given above before the read() method is called.
Setting up "byRecent" condition
  1. User::posts()->byRecent()->read()->posts;

  2. User::posts()->byRecent('date')->read()->posts;

  3. User::posts()->byRecent([Posts::class, 'post'])->read()->posts;

  4. User::posts()->byRecent(['posts', 'post'])->read()->posts;
 
The "byRecent" condition allows us to select recent data. It cannot be used with the order() method. By default, a "one-to-one" relationship will use the id of the parent model to select recent data while a "one-to-many" will use the id of the child model to select recent data if a custom field is not defined. For example, assuming we are working with a "one-to-many" relationship, then:
  • In code line 1, the recent data will be sorted by using the Posts model's id field since no custom field is defined.
  • In code line 2 above, the data will be sorted using the "date" field of the Posts model.
  • Code line 3 above will select the post based on the supplied model class and the custom field "post" defined.
  • In line 4 above, the sort table "posts" is defined rather than supplying the model's class. While line 3 is dependent on the model table name, line 4 is independent.
  • Under a one-to-one or many-to-many relationship, data will be sorted based on the model calling the relationship unless the sort table name (or model) is defined.
Setting up "order" condition
  1. User::posts()->order('firstname')->read()->posts;
  2. User::posts()->order('firstname', 'DESC')->read()->posts;
  3. User::posts()->order([Posts::class, 'post'])->read()->posts;
  4. User::posts()->order(['posts', 'firstname'])->read()->posts; // same as line 3 above if Posts::tablename() is not modfified
 
The order() condition allows us to select data by order. It overides the byRecent() method hence, it cannot be used along with it. The first argument is the field used to sort the data while the second argument defines the order in which the field is sorted. By default, the second argument uses the ascending order "ASC" but can be changed to descending order "DESC".
  • In code line 1, the data will be sorted in ascending order by using the Owner model's firstname field depending on the relationship type.
  • In code line 2, the data will be sorted in descending order by using the Owner model's firstname field depending on the relationship type.
  • In code line 3, the data will be sorted in descending order by using the Posts model's (table name) and table field "post".
  • In code line 4, the data will be sorted in descending order by using the posts table and table field "post". This may be similar to line 3 above if the table name is not modified.
Pulling data
   vdump(User::posts()->read()->pull(1));
 
Whilst the read() method returns a multidimentional transversible object, the pull() method allows us to pull data out of a list of collections using the defined data access key. For example, the code above will pull out the first data obtained from a multidimentional data. This method does not require the use of collection() or dynamic property to access the returned data. Hence, we can directly pull a data out of the object using the index key of that object as shown above. The data returned is usually a Collectibles object.
Protecting data
   User::where('id = ?', [1])->read()->protect(['password']);
 
The protect() method allows us to hide a specific data value. Once an access key is protected, the value becomes ***Protected***. The code above is an example local scope protection. Data collections can also be protected at a global level. This means that all models will remember to hide the value of some specific fields. This can be done by calling the Collection::protect() method. Once the protection is set up, all models will ensure to protect the data keys set on the global scope just as below:
   User::where('id = ?', [1])->read()->collection(); //password value not protected

   Collection::protect(['password']);   //password protected globally

   User::where('id = ?', [1])->read()->collection(); //password value protected from global protection
 

Shuffle data
   User::where('id = ?', [1])->read()->shuffle();
 
The shuffle() method is used to shuffle the data returned within the collection object. It takes no argument.
The get() method
  Comments::of('posts')->read()->Comments
 
When we have a multiple lists of data returned in the collection object, we can pull out a specific data using the the data key. Assuming we have a chained structure as above and a returned data format as shown below:
  object(spoova\core\classes\Collection)[24]

    private 'data' => 
        array (size=2)

        0 => 
            object(stdClass)[25]
                public 'id' => int 1
                public 'user_id' => int 2
                public 'post' => string 'This is a post' (length=14)
                public 'added_on' => string '2023-01-04 18:42:00' (length=19)
                public 'post_id' => int 2
                public 'comment' => string 'This is a comment to a post' (length=27)
        1 => 
            object(stdClass)[26]
                public 'id' => int 2
                public 'user_id' => int 2
                public 'post' => string 'This is a post' (length=14)
                public 'added_on' => string '2023-01-12 21:22:32' (length=19)
                public 'post_id' => int 2
                public 'comment' => string 'This is a second comment to post' (length=32)

    private 'datakeys' => 
        array (size=2)

        0 => int 0
        1 => int 1

    private 'iterator' => int 0

    private 'callables' => 
        array (size=0)
        
        empty

    public 'error' => string '' (length=0)                                
                                
We can pull the first comment by using the get() method just as below:
  Comments::of('posts')->read()->Comments->get(0);
                            
The data returned will be a format like below:
  object(stdClass)[25]
    public 'id' => int 1
    public 'user_id' => int 2
    public 'post' => string 'This is a post' (length=14)
    public 'added_on' => string '2023-01-04 18:42:00' (length=19)
    public 'post_id' => int 2
    public 'comment' => string 'This is a comment to a post' (length=27)
                            
According to the above structure, we can then access any of the stdClass object by calling the property just as below:
  Comments::of('posts')->read()->Comments->get(0)->comment;  // This is a comment to a post                      
                            
We can also supply our child key as a second argument on the get() method rather than calling it as a property just as below
  Comments::of('posts')->read()->Comments->get(0, 'comment');  // This is a comment to a post                      
                            
In situations where we need to pull the entire data of an index, we can declare the second parameter as an empty array container. The container will then be filled with the entire array data. This is shown below:
  Comments::of('posts')->read()->Comments->get(0, []);  // Get all data in index "0" as an array                      
                            
In situations where we need to get specific fields, we can do this by declaring the specific fields to obtain:
  Comments::of('posts')->read()->Comments->get(0, ['comment','user_id','lol']);                     
                            
The above will return a format as below:
  array (size=3)
    'id' => int 1
    'comment' => string 'This is a comment to a post' (length=27)
    'lol' => boolean false                    
                            
In the above, lol does not exist as a property, hence, it is set as false rather than triggering an error.
Getting a property from an index that does not exist will trigger an error. However, the data obtained can be optimized for get method.

Optimizing model data for get() method
When fetching an index that does not exist in a list of collection data through get(), this can trigger error. The word "optimize" here means improving the efficiency of obtaining data from collection object even if an error occurs in sql query and the collection class cannot return any valid data. The class ModelOptimizer is used to optimize model data in a way that even if an error occurs in a collection object list, the error can still be silenced in a way that doesn't break the application. Assuming we have a chained structure as below:
  var_dump( Roles::user() );
                                    
If the code above returns an error as below:
  object(spoova\core\classes\Collectibles)[18]

    public 'user' => 
    
        object(spoova\core\classes\Collection)[16]
        
            private 'data' => 
                array (size=0)
                empty
            private 'datakeys' => 
                array (size=0)
                empty
            private 'iterator' => int 0
            private 'callables' => 
                array (size=0)
                empty
            public 'error' => string 'Sql Error: Unknown column 'user.role_id' in 'on clause''
                                    
In the above, if we try to use the get() method directly to fetch an index, it may trigger an error. Instead, we can first optimize the object before finally chaining the get() method on it just as below:
  var_dump( Optimize(Roles::user()->user)->get(0, ['firstname','lastname']) );
                                    
The collection data above was optimized before calling the get() method. Since no data index "0" exists, the optimizer will ensure that the get() method returns a false value by default. In case we want to obtain the list of array we supplied (i.e firstname and lastname), we can set the Optimizer mode as false. This will ensure that if array is supplied as sub indexes, then each of the array supplied will become an index assigned the false value. An example is shown below:
  var_dump( Optimize(Roles::user()->user, false)->get(0, ['firstname','lastname']) );

  //Rather than return false, the above returns:

  array (size=2)

    'firstname' => boolean false
    'lastname' => boolean false

                                    

Fetching Sql Errors
The errors encountered by any relationship is usually being stored in the error property but can be obtained through the error() method. The "error" method can be placed upon the dynamic collection properties or directly on the read() method. A sample format is below:
   User::posts()->read()->collection()->error(); //valid
   
   User::posts()->read()->posts->error(); //valid

   User::posts()->read()->error(); //valid
 
Any of the method above is valid to obtain error. If no error occurs, then the error() method will return a false value.