How to add custom method to Laravel Query Builder [duplicate] - php

This question already has answers here:
How to customize Laravel's Database\Query\Builder (make better subquery)
(2 answers)
Closed 11 days ago.
I want to add custom methods for Laravel Query Builder.
I want to have something like this (Methods will be more complicated in further)
<?php
namespace App\Helpers;
class Builder extends \Illuminate\Database\Query\Builder
{
/**
* #return $this
*/
public function whenWhere(): self
{
return $this;
}
}
In code I have
DB::table('items')->select('id')->whenWhere()->get()
And I'm getting error
Call to undefined method Illuminate\\Database\\Query\\Builder::whenWhere()
I know there is a way to use macros in query providers, but I don't want use it because IDE don't see macros, so it is the main reason why I need to accomplish this in another way
*I'm not using models in project.

You can add public function with prefix scope:
public function scopeWhenWhere($query)
{
return $query->where('something', 'something');
}
It should let you call like this:
Builder::select(['column1', 'column2'])->whenWhere()->get();
Also you can extend the function with addition parameters:
public function scopeWhenWhere($query, $param)
{
return $query->where('something', $param);
}
Now you can put function param like this:
Builder::select(['column1', 'column2'])->whenWhere('ifsomething')->get();

Related

Replace CodeIgniter Query builder

I'm trying to make my own implementation of the Query builder in codeigniter.
Basicly I would like to add some function and all the provided function. Something like :
$this-db->from('myTable')
->where('id', 1)
->custom_where('name', 'customsValues')
->get()
There values are irrelevent here.
I've already built a class that extends en current CI_DB_query_builder but I have no idea where to set my class to be used as the primary query builder. I've try to look up similar problem on google but could not find anything.
Any help would be appreciated.
I've found the answer to my question. However, any other good answer is welcome since mine is not the prettiest.
First let me walk you through what I was trying to do.
I wanted to use Tightenco's collect library to use collection rather than array. This way I could use more intuitive chain array function: $this->db->from('...')->results()->map(function($items) { ... })->toArray();
Then, i wanted to have my own function, such as where_if.
I started by making my own query builder class that extended from CI_DB_query_builder. I have an example below:
<?php
require BASEPATH . 'database/DB_query_builder.php';
class CustomQueryBuilder extends CI_DB_query_builder {
public function where_if($where, $value, $condition) : CustomQueryBuilder {
if($condition) {
$this->where($where, $value);
}
return $this;
}
public function results() : \Tightenco\Collect\Support\Collection {
return \Tightenco\Collect\Support\Collection::make($this->get()->result_array());
}
}
To link this class as the main Querybuilder, I had to change it in the file system/database/DB.php.
I changed the path of the require_once in line 171 :
require_once(APPPATH.'libraries/QueryBuilder/CustomQueryBuilder.php');
I also changed the alias class on line 182
class CI_DB extends CustomQueryBuilder { }
Please note that this is on codeigniter v3.0.6, your line number may differ.
Now, i needed to import some function so the autocomplete on PHPStorm would still point to my custom querybuilder, because once i used the function from, it returned a CI_DB_query_builder object.
Here is how I imported the function I used the most.
/**
* Nothing changed here, for autocompletion only
* #param mixed $from
* #return $this|CI_DB_query_builder
*/
public function from($from) {
parent::from($from);
return $this;
}
I really hope this helps people that are trying the same thing. If you got any feedback on this project, please let me know !

How to add where condition to eloquent model on relation?

I want to add where condition to my Model when with('category') is called. My relations are like this:
class Post extends Model
{
public function category()
{
return $this->belongsTo(Category::class);
}
}
Now I use this code to display post categories:
Post::where('slug', $slug)->with('category')->get();
I want to add where condition to Post Model when with('category') is called. I should display only posts.status== published if with('category') is called.
I think return $this->belongsTo(Category::class); is where i should add my where condition, but this doesn't work:
return $this->query()->where('posts.status', 'published')->getModel()->belongsTo(User::class)
How can I add where condition to all post queries if with('category') is called?
I know Laravel query scopes, but i think there is a simpler way we can use. (perhaps on $this->belongsTo(Category::class))
Relationships are implemented using additional queries. They are not part of the base query, and do not have access to modify the base query, so you cannot do this inside the relationship method.
The best way to do this is with a query scope:
Class:
class Post extends Model
{
public function scopeWithCategory($query)
{
return $query->with('category')->where('status', 'published');
}
}
Query:
$posts = Post::where('slug', $slug)->withCategory()->get();
Edit
Based on your comment, I think you've probably asked the wrong question. You may want to post another question explaining what you have setup, and what you need to do, and see if anyone has any suggestions from there.
However, to answer this specific question, I believe you should be able to do this using a global query scope. This is different than a local scope described in my original answer above.
Global query scopes are applied when get() is called on the Eloquent query builder. They have access to the query builder object, and can see the items that have been requested to be eager loaded. Due to this, you should be able to create a global query scope that checks if category is to be eager loaded, and if so, add in the status constraint.
class Post extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
// make sure to call the parent method
parent::boot();
static::addGlobalScope('checkCategory', function(\Illuminate\Database\Eloquent\Builder $builder) {
// get the relationships that are to be eager loaded
$eagers = $builder->getEagerLoads();
// check if the "category" relationship is to be eager loaded.
// if so, add in the "status" constraint.
if (array_key_exists('category', $eagers)) {
$builder->where('status', 'published');
}
});
}
}
The code above shows adding in a global scope using an anonymous function. This was done for ease and clarity. I would suggest creating the actual scope class, as described in the documentation linked above.
This should work:
Post::where(['slug' => $slug, 'status' => 'published'])->with('category')->get();
you have to use withPivot() method .
class Post extends Model
{
public function category()
{
return $this->belongsTo(Category::class)->withPivot('status');
}
}
please refer to my question here

Hide Restler method from swagger-ui

Using Restler 3.0.0-RC6, which internally packages swagger-ui, I have an API method defined like so:
<?php
namespace v1:
class PostgreSQL {
public function fetchArray($sql, $args = null) {
And then all of my classes that I include via Restler's addAPIClass extend that PostgreSQL class. That means when swagger runs, every single API shows a fetchArray function. I'd like to have that method not appear in the swagger documentation as it's not really part of the API. Other 'things' on the website also use the class though so I can't change the modifier from public.
What's the proper syntax to hide that method from swagger-ui's webpage?
There are two ways to achieve this,
One is to mark the fetchArray method as private with #access private comment. This will remove fetchArray from all api urls while keeping the fetchArray still accessible for PHP
Problem in your case is that you don't want to modify the PostgreSQL as its part of a framework that is maintained by composer. Instead of directly extending it from the base class use an intermediary class which adds the comment and then extend that class as shown below
class Base {
public function fetchArray(){
return array();
}
}
class Intermediary extends Base {
/**
* #access private
*/
public function fetchArray(){
return array();
}
}
class MyApi extends Intermediary { //instead of extends Base
//other api methods here
//see in the explorer to note that fetchArray is no longer listed
}
Another way is to just exclude it on Explorer with
use Luracast\Restler\Explorer;
Explorer::$excludedPaths = array('myapi/fetcharray','another/fetcharray');
You should not extend your API layer class from a data layer class. Just use the data layer class.
class DataLayer
{
public function fetchArray()
{
return array();
}
}
class ApiLayer
{
private $dl;
function __construct()
{
$this->dl = new DataLayer();
}
public function getAll()
{
return $this->dl->fetchArray();
}
}

How to load models in Laravel?

I started studying Laravel and ran into a problem using models. How to load them? For example in CodeIgniter i used it like $model = $this->load->model('some_model'). In Laravel when i call it from controller like Sites::OfUser() it work fine, but when i call Sites::getId() it says that method should be static...
Is it possible to call method without static or i need to create facades for each model?
My model looks like this:
namespace Models;
use Eloquent;
class Sites extends Eloquent {
public function scopeOfUser($query)
{}
public function getId($name)
{}
}
For static method--
$type = Sites ::scopeOfUser($query);
and if you want normal like codeingiter then use--
$model = new Sites ();
$type = $model->scopeOfUser($query);
You can of course make a static method in the model, and do some static work in it (get ID for name or whatever).
That's no problem.
However, you must declare it static if you want to use the ::, which you are doing not.
public static /* <-- this */ function getId($name)
{
// Do work
// return $result;
}
If you want to access a method with ::, you will need to make it a static method or create a Facade.
The reason why Sites::OfUser() is "working" is because you have prefixed that method with scope.
Scopes allow you to easily re-use query logic in your models. To
define a scope, simply prefix a model method with scope.
If you want to use Facades you can follow my answer here on how to create a Facade.

Laravel retrieve param in class constructor [duplicate]

This question already has answers here:
Passing parameter to controller from route in laravel
(4 answers)
Closed 8 years ago.
Here is my route:
Route::controller('/app/{companyId}/', 'HomeController', array('before' => 'auth'));
How can I retrieve $companyId argument in __constructor to avoid retrieving it separate in all my actions?
If you want to get the parameters in the __construct of your controller you could do this:
class HomeController extends \BaseController
{
public function __construct()
{
$this->routeParamters = Route::current()->parameters();
}
}
it will return a key value list of parameters for the route (ex: ['companyId' => '1']) #see \Illuminate\Routing\Route
You can also get a specific parameter using the getParameter() or parameter() methods.
NOTE: I'm not sure this is such a great idea tho. There might be a more elegant way to solve or better approach to your problem.
If you want to make the process simpler, route model binding seems to be the easiest way to go. Instead of having to fetch for the right Model instance in every action of your controller, you pass the right Model to your controller during the routing process.
But you have to use Route::resource. In routes.php :
Route::bind('company', 'Company');
Route::resource('company', 'HomeController');
Then you have an instance of category passed to your controller. For example for /company/1 :
public function show($company)
{
// Here you can use, for instance, $company->name
}

Categories