pilat

pilat

Member Since 1 Year Ago

Experience Points 11,790
Experience
Level
Lessons Completed 99
Lessons
Completed
Best Reply Awards 0
Best Answer
Awards
  • Start Your Engines Achievement

    Start Your Engines

    Earned once you have completed your first Laracasts lesson.

  • First Thousand Achievement

    First Thousand

    Earned once you have earned your first 1000 experience points.

  • One Year Member Achievement

    One Year Member

    Earned when you have been with Laracasts for 1 year.

  • Two Year Member Achievement

    Two Year Member

    Earned when you have been with Laracasts for 2 years.

  • Three Year Member Achievement

    Three Year Member

    Earned when you have been with Laracasts for 3 years.

  • Four Year Member Achievement

    Four Year Member

    Earned when you have been with Laracasts for 4 years.

  • Five Year Member Achievement

    Five Year Member

    Earned when you have been with Laracasts for 5 years.

  • School In Session Achievement

    School In Session

    Earned when at least one Laracasts series has been fully completed.

  • Welcome To The Community Achievement

    Welcome To The Community

    Earned after your first post on the Laracasts forum.

  • Full Time Learner Achievement

    Full Time Learner

    Earned once 100 Laracasts lessons have been completed.

  • Pay It Forward Achievement

    Pay It Forward

    Earned once you receive your first "Best Reply" award on the Laracasts forum.

  • Subscriber Achievement

    Subscriber

    Earned if you are a paying Laracasts subscriber.

  • Lifer Achievement

    Lifer

    Earned if you have a lifetime subscription to Laracasts.

  • Laracasts Evangelist Achievement

    Laracasts Evangelist

    Earned if you share a link to Laracasts on social media. Please email [email protected] with your username and post URL to be awarded this badge.

  • Chatty Cathy Achievement

    Chatty Cathy

    Earned once you have achieved 500 forum replies.

  • Laracasts Veteran Achievement

    Laracasts Veteran

    Earned once your experience points passes 100,000.

  • Ten Thousand Strong Achievement

    Ten Thousand Strong

    Earned once your experience points hits 10,000.

  • Laracasts Master Achievement

    Laracasts Master

    Earned once 1000 Laracasts lessons have been completed.

  • Laracasts Tutor Achievement

    Laracasts Tutor

    Earned once your "Best Reply" award count is 100 or more.

  • Laracasts Sensei Achievement

    Laracasts Sensei

    Earned once your experience points passes 1 million.

  • Top 50 Achievement

    Top 50

    Earned once your experience points ranks in the top 50 of all Laracasts users.

22 Oct
3 weeks ago

pilat left a reply on Minimal Bootstrap For Webhooks Processing

Both the Jobs are queued. So, the controller's job is only to receive the webhook, analyse it a little bit (reject 'leads.update' ones; find if there is a custom job available) and dispatch queued jobs. The dispatched jobs are then executed in a separate thread/process (in a worker).

Overall, the server is pretty loaded, yes. But… I think that upgrading hardware is not a proper way to solve this certain issue :-) There is no heavy-loading at all, in this specific action: as it only needs to add a record to "jobs" table and immediately response with 200 - Ok status. Just wondering if I really need to load "bootstrap whole laravel application" for doing so…

pilat started a new conversation Minimal Bootstrap For Webhooks Processing

Hi,

Is there a way to load "as less Laravel as possible" when processing a webhook? The objective here is to send the response asap otherwise the external system would send the same webhook again and eventually would disable webhooks at all.

Note: I'm using queued jobs already, so my controller looks like this:

class WebhooksController extends Controller
{
    public function process(Request $request)
    {
        if ($request->has('leads.update')) {
            // do not process this kind of hooks…
            return response("Ok", 200);
        }

        try {
            // Cache-related routines:
            $job = new \App\Jobs\UpdateLeadJob($request->all());
            $this->dispatch($job);
        } catch (\Exception $e) {
            Log::critical("[webhooks] dispatching primary job failed: {$e->getMessage()}", $request->all());
        }

        try {
            // Custom webhook processors:
            $customJob = $this->getCustomJob($request);
            if ($customJob) {
                $this->dispatch($customJob);
            }
        } catch (\Exception $e) {
            Log::critical("[webhooks] dispatching custom job failed: {$e->getMessage()}", $request->all());
        }

        $executionTime = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
        if (1.9 < $executionTime) {
            Log::error("[webhooks] webhook processing took more than 2 secs!", $request->all());
        }

        return response("Ok", 200);
    }

    // ...

}

Still, it happens sometimes that it's not fast enough. So, I can usually see several "webhook processing took more than 2 sec" messages in the logs.

So, probably I need a way to disable middlewares or something else… Is there any way to reduce the application loading time?

13 Oct
1 month ago

pilat left a reply on Performant Way To Mass "update Or Create"

Well, this worked for some time, but now it hit its top as well…

After some good chunk of "thinking time", I've realised that I'm willingly making the system overly complicated on low [!!and, what's most important — not well-learned!!] level (Database) for the sake of simplicity on high-level (ORM). This is just wrong…

What I do: I regularly sync (and less often, but still regularly, re-sync from scratch) data from an external API into my application's database, so that I could query this data as I need. And for "closer" access, for sure.

Thus, here's my basic conditions:

  1. Lots of writes; Often, in large chunks (could easily be like a half a million records within one sync session);

  2. A need to keep IDs for existing records.

  3. At the same time, I need to remove the records not available now.

Here's the pseudo-code of how it's done now:

// 1. updating a number of leads;

$leadIds = $this->updateLeadsSince($timestamp);

// 2. now, need to update some linked data

$this->syncCustomFields( $leadIds );

// ...
protected function syncCustomFields( $leadIds )
{
    // Step 1: mark all related fields as deleted, without actually deleting them:
    
    CustomField::whereIn('lead_id', $leadIds)->update([ 'deleted' => true ]);

    // Step 2: update or insert new data, utilising "INSERT … ON DUPLICATE UPDATE";
    // all new records are written with "deleted = false";
    
    $this->generateCustomFieldsForLeads($leadIds);

    // Step 3: delete the records remaining with "deleted = true" for real:
    
    CustomField::where('deleted', true)->delete();
}

With this schema, I often get wait lock errors, especially on update([ 'deleted' => true ]) queries. The insertion itself (Step 2) is also slower than I would like… An I have no idea on how to optimise it, without giving up one any of the 'basic conditions"…

--

However.

If to look "from a bird's sight height", what I actually need in this case: is a composite primary key + REPLACE INTO statements. That's all and it's definitely simpler.

The Eloquent ORM, however, does not provide any good support for composite primary keys, and this is why I added "AUTO INCREMENTAL" surrogate key in the very beginning. But this key is causing so many troubles in unknown area now, that I'm considering to convert my tables in a way to have it simpler on the database level and tackle with Eloquent instead; This might work, for example: https://blog.maqe.com/solved-eloquent-doesnt-support-composite-primary-keys-62b740120f

So, I'll hopefully keep you informed. ;-)

22 Aug
2 months ago

pilat left a reply on Optimize MySQL To Avoid Locks

  1. I'm on a shared hosting at this moment…

  2. This is clearly an application issue. I mean, despite it can be solved by tweaking MySQL, the "for real" fix assumes doing something to the application code. Suppose, I upgrade MySQL and get rid of these deadlocks/timeouts on my current loading, but as the number of users grows, the issue would inevitably get back…

21 Aug
2 months ago

pilat started a new conversation Optimize MySQL To Avoid Locks

Hi,

Regularly get errors like this:

SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction (SQL: delete from `custom_fields` where `subdomain` = subdomain_name and `deleted` = 1)

The query may really take quite a long time (too many records in this DB table), thus there are locks… Is there a way to make it in a lockless fashion? To split onto several smaller queries or something like that?

01 Aug
3 months ago

pilat left a reply on Single-file Components: Failed To Mount Component: Template Or Render Function Not Defined.

A workaround ("kostillie"): replace all .js with .react.

mix.react('resources/assets/js/app.js', 'public/js')
   .react('resources/assets/js/map.js', 'public/js')
   .react('resources/assets/js/calendar.js', 'public/js');
   // … few more deps

pilat left a reply on Troubleshooting "Serialization Of Closure Is Not Allowed" Issue When Caching Routes

I've found a duplicate route that fixed this issue.

The error message is very misleading, I'd say…

pilat started a new conversation Single-file Components: Failed To Mount Component: Template Or Render Function Not Defined.

Hi,

I've trying to migrate from elixir to mix in my Laravel 5.3 project. What I've done so far:

  1. Rewritten package.json to be "like in laravel 5.6":
  "devDependencies": {
    "axios": "^0.18",
    "babel-preset-react": "^6.24.1",
    "bootstrap-sass": "^3.3.7",
    "cross-env": "^5.1",
    "gentelella": "^1.4.0",
    "laravel-echo": "^1.4.0",
    "laravel-mix": "^2.0",
    "laravel-vue-pagination": "^1.2.0",
    "portal-vue": "^1.3.0",
    "pusher-js": "^4.2.2",
    "vue": "^2.5.7",
    "vuedraggable": "^2.16.0"
  },

Note: I'm loading jQuery and some other "non-vue" libs from CDN, so, they're not in this list.

  1. Clean-reinstalled all dependencies: rm -rf node_modules package-lock.json && npm cache clear --force && npm install

  2. In resources/js/bootstrap.js, replaced vue-resource with axios:

window.axios = require('axios');

window.axios.defaults.headers.common = {
    'X-CSRF-TOKEN': window.Laravel.csrfToken,
    'X-Requested-With': 'XMLHttpRequest'
};

Vue.prototype.$http = axios;

  1. Added function mix(...) implementation to my app/helpers.php

  2. "converted" gulfile.js to webpack.mix.js:

let mix = require('laravel-mix');
mix.sass('resources/assets/sass/app.scss', 'public/css')
   .sass('resources/assets/sass/accounts_custom.scss', 'public/css')
   .sass('resources/assets/sass/map.scss', 'public/css')
   .sass('resources/assets/sass/calendar.scss', 'public/css');

mix.js('resources/assets/js/app.js', 'public/js')
   .js('resources/assets/js/map.js', 'public/js')
   .react('resources/assets/js/calendar.js', 'public/js');
   // … few more deps

if (mix.inProduction()) {
    mix.version();
}
  1. Built the project: npm run dev

Now, in my browser, I get errors like this in every place, where any component from .vue file is referenced:

map.js:1449 [Vue warn]: Failed to mount component: template or render function not defined.

found in

---> <Waiter> at resources/assets/js/map_components/Waiter.vue
       <Root>

, here's how this specific component is plugged in:

Vue.component('waiter', require('./map_components/Waiter.vue'));

But, the problem is the same with any component…

What am I missing?

26 Jul
3 months ago

pilat left a reply on Casting To Boolean

I'm afraid it's confusing and may lead to errors. Here's the documentation:

Now the is_admin attribute will always be cast to a boolean when you access it, even if the underlying value is stored in the database as an integer:

Ok, so I can check it like this: $user->is_admin === true?

nope.

# tinker

>>> App\Activity::first()->processed === 0
=> true

>>> App\Activity::first()->processed === false
=> false
25 Jul
3 months ago

pilat started a new conversation Troubleshooting "Serialization Of Closure Is Not Allowed" Issue When Caching Routes

I'm trying to cache routes in Laravel 5.3-based project. Here's the error I get:

$ php artisan route:cache -v

Route cache cleared!


  [Exception]
  Serialization of 'Closure' is not allowed


Exception trace:
 () at /Users/pilat/version-control/mappanel/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteCacheCommand.php:95
 serialize() at /Users/pilat/version-control/mappanel/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteCacheCommand.php:95
 Illuminate\Foundation\Console\RouteCacheCommand->buildRouteCacheFile() at /Users/pilat/version-control/mappanel/vendor/laravel/framework/src/Illuminate/Foundation/Console/RouteCacheCommand.php:65
 Illuminate\Foundation\Console\RouteCacheCommand->fire() at n/a:n/a
 call_user_func_array() at /Users/pilat/version-control/mappanel/vendor/laravel/framework/src/Illuminate/Container/Container.php:508
 Illuminate\Container\Container->call() at /Users/pilat/version-control/mappanel/vendor/laravel/framework/src/Illuminate/Console/Command.php:169
 Illuminate\Console\Command->execute() at /Users/pilat/version-control/mappanel/vendor/symfony/console/Command/Command.php:261
 Symfony\Component\Console\Command\Command->run() at /Users/pilat/version-control/mappanel/vendor/laravel/framework/src/Illuminate/Console/Command.php:155
 Illuminate\Console\Command->run() at /Users/pilat/version-control/mappanel/vendor/symfony/console/Application.php:817
 Symfony\Component\Console\Application->doRunCommand() at /Users/pilat/version-control/mappanel/vendor/symfony/console/Application.php:185
 Symfony\Component\Console\Application->doRun() at /Users/pilat/version-control/mappanel/vendor/symfony/console/Application.php:116
 Symfony\Component\Console\Application->run() at /Users/pilat/version-control/mappanel/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:121
 Illuminate\Foundation\Console\Kernel->handle() at /Users/pilat/version-control/mappanel/artisan:35

Important notes:

  • I've already moved all Closures from route/*.php files to controllers and re-checked it several times. The only closures in these files remaining are in Route::group constructions.
  • I've tried a script like this, jut to be on the safe side (it returned nothing):
# tinker

$routes = app('router')->getRoutes()

foreach ($routes as $route) {
    if (is_object($route) && ($route instanceof Closure)) {
        $r = new ReflectionFunction($route);
        dump($r->getFilename());
    }
}
10 Jul
4 months ago

pilat left a reply on Show Uncaught Exceptions To Console Rather Than Laravel.log File

here's for 5.4: https://gist.github.com/adamwathan/125847c7e3f16b88fa33a9f8b42333da

But it worked for 5.3 as well (copied only the interesting pieces over, not the whole php file).

05 Jul
4 months ago

pilat left a reply on Valet, Custom Driver With Other PHP Version

@willvincent I like your idea of switching between installed phps, but it doesn't work for some reason…

Here's my aliases:

# PHP versions
alias use56="brew unlink [email protected] && brew unlink php && brew link --force [email protected] && valet restart"
alias use70="brew unlink [email protected] && brew unlink php && brew link --force [email protected] && valet restart"
alias use72="brew unlink [email protected] && brew unlink [email protected] && brew link php && valet restart"

And here's the result of using one of them:

✗ use56
Unlinking /usr/local/Cellar/[email protected]/7.0.30_1... 0 symlinks removed
Unlinking /usr/local/Cellar/php/7.2.7... 0 symlinks removed
Warning: [email protected] is keg-only and must be linked with --force
Note that doing so can interfere with building software.

If you need to have this software first in your PATH instead consider running:
  echo 'export PATH="/usr/local/opt/[email protected]/bin:$PATH"' >> ~/.zshrc
  echo 'export PATH="/usr/local/opt/[email protected]/sbin:$PATH"' >> ~/.zshrc

In Brew.php line 182:

  Unable to determine linked PHP.


restart

Could you help me to get this working?

30 Jun
4 months ago

pilat left a reply on How Do I Get List Of All Available Attributes In The Model?

It looks I can do i this way:

return array_key_exists($key, $this->attributes) || $this->hasGetMutator($key));

Let me test it out :-)

pilat started a new conversation How Do I Get List Of All Available Attributes In The Model?

Hi,

What I need is to get a list of all available attributes (keys) on the model, including accessors.

Here's what I'm trying to do:

public function fillData($template)
{
    preg_match_all('/{([^}]+)}/', $template, $matches);
    $placeholders = $matches[1];

    $attributes = $this->getFullAttributesList(); // <<< HOW??

    $replaceData = [];

    foreach ($placeholders as $placeholder) {
    if (in_array($placehoder, $attribute)) {
        $replaceData[$placeholder] = $this->{$attribute};
        }
    }

    return $this->miniTwig($template, $replaceData);
}

protected function miniTwig($template, $data)
{
    $what = array_map(function ($key) {
        return '{' . $key . '}';
    }, array_keys($data));

    $with = array_values($data);

    return str_replace($what, $with, $template);
}


protected function getFullAttributesList()
{
    // is there a way?
}


/// Useage exmaple:

$model->fillData("The product {product_name} costs {price");
09 Apr
7 months ago

pilat started a new conversation How To Receive And Process Request Known To Be In Non UTF-8 Charset?

Hi,

Here's the issue: one of sites sends data to my app in a non-UTF-8 charset. That site is not under my control, so, let I it be as it is.

Now, when I'm trying to process this data, I have few issues.

  1. I cannot log the request data.
Log::debug("[[email protected]{$site}] request", [ $request->all() ]);

Log::debug("[[email protected]{$site}] request", [ $_REQUEST ]);

Both lines produce the following in the log file:

[2018-04-09 15:40:05] production.DEBUG: [[email protected]] request

(there's not even "[]" after the "message" part and it's strange…)

  1. Later on, when I try to dispatch a Job with this data as payload, I get the following error:

[2018-04-09 15:40:06] production.ERROR: exception 'InvalidArgumentException' with message 'Unable to create payload: Malformed UTF-8 characters, possibly incorrectly encoded' in /home/i/ivento/kartazamerov.ru/public_html/mappanel/vendor/laravel/framework/src/Illuminate/Queue/Queue.php:94

  1. Error stays the same even if I try to covert this data from the original charset to UTF-8:
        $payload = $request->all();
        if ('ThatDinozaurus' === $SiteName) {
            $payload = iconv('CP1251', 'UTF-8', json_encode($payload));
            $payload = json_decode($payload, true);
        }

The task: to be able to handle those requests as well as other, good'ol utf-8 ones. I know which site sends non-standard charset. I have iconv at my disposal. But still something's wrong…

27 Feb
8 months ago

pilat left a reply on Can I Cancel Event Broadcasting From Within The Event Class?

Okey, I'll stick with helpers, again =)

  1. Make a helper function. E.g., this one:
function pusher($event) // @tido find better name with lower risk of collision
{
    if (is_null($event->broadcastOn())) {
        return;
    }

    event($event);
}
  1. In the Event class, you can make all the checks in ::broadcastOn() and return null if it shouldn't be published:
class SomethingWasSynced implements ShouldBroadcast
{

    // ...

    public function broadcastOn()
    {
        if (! $this->broadcastWhen()) {
            return null;
        }

        return new Channel("syncing-{$this->subdomain}");
    }

    public function broadcastWhen() // if I ever upgrade to 5.6+, I'll leave this method as is, but remove that check from `::broadcastOn()` ;-)
    {
        if (App::runningInConsole())
            return false;
        }

        return true;
    }
}
  1. Use pusher() instead of event() in your client code:
pusher(new SomethingWasSynced($this->subdomain, 'custom_fields', $count));
26 Feb
8 months ago

pilat left a reply on Can I Cancel Event Broadcasting From Within The Event Class?

@mrbadr doesn't work.

I've tried:

  1. Returning null from __construct();
  2. Returning null from broadcastOn();

Having BROADCAST_DRIVER=log in the .env file, I can see that broadcasting is still happening:

[2018-02-26 17:56:37] local.INFO: Broadcasting [App\Events\SomethingWasSynced] on channels [] with payload:
{
    "broadcastQueue": "pusher",
    "subdomain": "domain1",
    "what": "activities",
    "totalCount": 40,
    "socket": null
}

Just the list of channels is empty. I am wondering: will these messages (with the empty list of channels) be actually sent to Pusher and eat up my notifications limits?

pilat left a reply on Can I Cancel Event Broadcasting From Within The Event Class?

The second question is: how do I tell a web request (Http\Kernel) from a Console one (Console\Kernel)? Can I do it from within the event class as well?

It seems I can use this guy: App::runningInConsole()

pilat left a reply on Can I Cancel Event Broadcasting From Within The Event Class?

UPDATE: I've just found broadcastWhen() method in the documentation for Laravel 5.6, but there is no such method in Laravel 5.3. So, need to find the other way around.

pilat started a new conversation Can I Cancel Event Broadcasting From Within The Event Class?

Hi, I have an event class to send notifications about external API sync status. It's quite simple, and I'm appending the code to the end of this post.

Now, I have one task: I'd only like to broadcast this event if the following conditions are both met:

  1. It's not Local environment;
  2. t is Http kernel, not Console (because the syncing can also be initiated as Artisan command or a scheduled task and there is no use for websockets in this case).

Now, I'm using something like this in my "client code" when firing the event:

        if (! App::environment('local')) {
            event(new SomethingWasSynced($this->subdomain, 'custom_fields', $this->cfsGeneratedCount));
        }

The problem: I have to remember to use this conditions every time I need to fire this event. Besides, it's not very dry this way.

So the question is: can I check this condition and cancel/abort broadcasting from within the event itself? For example, by returning false in constructor or calling some magic method, I don't know…

The second question is: how do I tell a web request (Http\Kernel) from a Console one (Console\Kernel)? Can I do it from within the event class as well?

class SomethingWasSynced implements ShouldBroadcast
{
    use InteractsWithSockets, SerializesModels;

    /**
     * The name of the queue on which to place the event.
     *
     * @var string
     */
    public $broadcastQueue = 'pusher';

    public $subdomain;
    public $what;
    public $totalCount;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($subdomain, $what, $totalCount)
    {
        $this->subdomain = $subdomain;
        $this->what = $what;
        $this->totalCount = $totalCount;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new Channel("syncing-{$this->subdomain}");
    }

    public function onQueue()
    {
        return 'pusher';
    }
}
17 Jan
9 months ago

pilat left a reply on Search In JSON Column Does Not Work With Query Builder And Eloquent Model (but Works With Raw Query)

I've found the issue. Hope, it may help someone:

It turns out that linked_company_id is stored as string in the json structure I search in. Like: …,"linked_company_id":"68743242",…n't ask me why, I get this data from external service's API). So, I've tried the following code:

$allLeadsEloquent = LeadsCache::where('json->linked_company_id', (string)$companyId)
    ->get();

And it worked.

Still, I find it not very neat, for at least the following reasons:

  1. How come the Debugbar still produces the query that actually finds rows?

  2. This "strict type match" is quite hard to debug. Especially, considering that type match is not required when searching in normal (not JSON) column.

pilat started a new conversation Search In JSON Column Does Not Work With Query Builder And Eloquent Model (but Works With Raw Query)

Hi, in Laravel 5.3, I'm trying to use that short syntax for the JSON columns:

$allLeadsEloquent = LeadsCache::where('json->linked_company_id', $companyId)
    ->get();

The result is: empty collection. Note: the same result is with Query Builder (DB('table_name')).

Now. I open Debugbar and grab the "Raw query" produced by this code and run this query in the Sequel Pro: there it finds some rows!

Here's the how the raw query looks:

select * from `leads_cache` where `json`->'$."linked_company_id"' = '68743242'

What possibly could go wrong with this?…

11 Jan
10 months ago

pilat left a reply on Performant Way To Mass "update Or Create"

@kfirba

Finally implemented this approach and had some time to test it out. Looks good so far! At least, I can be sure that relations records IDs stay unchanged (once created) and they're not going to overflow INTEGER type eventually :-)

Thank you very much!

pilat left a reply on How To Make Sure/check If A Listener Is Not Queued?

There probably was some other error in the code, resulting in that data not to become immediately available… Don't remember how I've fixed it, to be honest, but the issue is not there anymore.

pilat left a reply on Disabling Vue Devtools In Production

I'm not sure if I want to create Webpack config specifically for this one option (I have Laravel 5.3). Will these options work "runtime"? I mean, if I do the following in my app.js file:

if (location.hostname.indexOf('production-domain.com') === -1) {
    Vue.config.debug = true;
} else {
    Vue.config.devtools = false;
    Vue.config.debug = false;
    Vue.config.silent = true;
}
10 Jan
10 months ago

pilat started a new conversation Setting Wait_timeout For MySQL Connection

Hi, I need to set wait_timeout session variable for the database connection. BUt:

  1. It has to be done inside my application because the hosting team refuses to change this option "for all of the users". They say I can do it easily by issuing the following query: SET SESSION wait_timeout = XXXXXX.

  2. I need to find a proper place in which to issue this query. As I understand, it should be issued somewhere near connection establishing, is it right? There is no such option in config/database.php and I shouldn't touch files inside vendor/, off cause. Besides, this adjustment should be indexed by git.

So, could you advise a proper way to inject this?

13 Dec
11 months ago

pilat left a reply on Is There A Nice Way To Handle Empty Dates In Templates?

One more question: is there a way to extend Carbon\Carbon in a way, that all Eloquent models would use my custom class for the dates? Meanwhile, I shall probably issue a feature request to Carbon themselves…

12 Dec
11 months ago

pilat left a reply on Is There A Nice Way To Handle Empty Dates In Templates?

There's no evaluation going on except for the PHP interpreter.

I don't now… I'm literally parsing $input into arguments.

If that bothers you then you should probably stop using Blade. The whole thing is based on it.

I see. Let us put it this way: I don't trust myself writing this kind of code. I'm ok with built-in directives, written by masters and covered by loads of tests ;-) I'd better use helper function for this one case.

@tykus: optional() is a thing! I should probably backport this helper into my Laravel 5.3. based project. If I need some fallback text, should I use it Like this?

{{ optional($record->some_date_field)->format('d.m.Y') or 'n/a' }}

pilat left a reply on Is There A Nice Way To Handle Empty Dates In Templates?

@topvillas Blade extension looks like a plan!

But… Here's what I came to:


// AppServiceProvider.php

// ...
       Blade::directive('dateOrText', function ($input) {
            $args = preg_split('/\s*,\s*/', $input);
            $expression = $args[0];
            $defaultText = isset($args[1]) ? $args[1] : "'-'";
            $format = empty($args[2]) ? "'d.m.Y'" : $args[2];

            return "<?php echo date_or_text($expression, $defaultText, $format); ?>";
        });
// ...

// helpers.php

// ...
function date_or_text($expression, $defaultText = '-', $format = 'd.m.Y')
{
    if (is_numeric($expression)) { // timestamp is passed:
        if (0 === $expression) {
            return $defaultText;
        }
        $expression = \Carbon\Carbon::createFromTimestamp($expression);
    }

    // assume that DateTime object is passed:
    return (int)$expression->format('Y') > 1970 ? $expression->format($format) : $defaultText;
}

// template.blade.php

                <td>
                    @dateOrText($task->complete_till_carbon, '-', 'd.m.Y H:i')<br>
                    @dateOrText($task->complete_till, '-', 'd.m.Y H:i')<br>
                    @dateOrText($task->complete_till, '-')<br>
                </td>

… but the question: is `@dateOrText(…argooking so much better than an `{{ date_or_text(…argshat it worth parsing sing ` in the the `Blade::dir's callback? Besides, the whole hole `Blade::direc concept smells like like ` to me…

So, the final version:

// helpers.php

// ...
function date_or_text($expression, $defaultText = '-', $format = 'd.m.Y')
{
    if (is_numeric($expression)) { // timestamp is passed:
        if (0 === $expression) {
            return $defaultText;
        }
        $expression = \Carbon\Carbon::createFromTimestamp($expression);
    }

    // assume that DateTime object is passed:
    return (int)$expression->format('Y') > 1970 ? $expression->format($format) : $defaultText;
}

// template.blade.php

                <td>{{ date_or_text($task->complete_till_carbon, '-', 'd.m.Y H:i') }}</td>

pilat started a new conversation Is There A Nice Way To Handle Empty Dates In Templates?

Hi,

Let say, I have a nullable Date or DateTime field in my DB. Eloquent exports it to templates as Carbon object, so, I can freely setup formatting:

<td>{{ $record->some_date_field->format('d.m.Y') }}</td>

This is very convenient, and I'd prefer to have this option available.

The problem is when 'm trying to display dates from those records, that have it empty (or null; or zero, if I store timestamps). Here's a piece of real code:

    public function getCompleteTillCarbonAttribute()
    {
        return \Carbon\Carbon::createFromTimestamp($this->complete_till);
    }
<td>{{ $task->complete_till_carbon->format('Y-m-d') }}</td>

When original complete_till is 0, I'll have 1970-01-01 date on the output. To avoid this, I should do one of the following:

  1. Use conditions in blade:
<td>{{ 0 === $task->complete_till ? 'n/a' : $task->complete_till_carbon->format('Y-m-d') }}</td>
  1. Make a function that returns date as string:
    public function getCompleteTillStringAttribute()
    {
    return 0 === $this->complete_till
        ? 'n/a'
        : date('d.m.Y', $this->complete_till); // don't see any use for Carbon in this case
    }

Neither way is very elegant, I wold say. I'd prefer to control the template to be in charge for appearance, but not to have that ugly ternary condition operator…

Something like this: {{ $record->a_date->format('d.m.Y')->ifEmpty('n/a') }}, or this: {{ $record->a_date->format('d.m.Y', 'n/a') }};

Do anyone have good solution to the issue?

27 Nov
11 months ago

pilat left a reply on Eager Load Relations Stored In Json Field

I'm on the same issue too. Here's my example:

# Eloquent model:
class Activity extends Model
{
    // ...
    public function notifications()
    {
        return $this->hasMany(\App\NotifyLog::class, 'report->activity->element_id', 'element_id');
    }
    // ...
}


# Client code:
# 1. No eager loading:
App\Entities\Activity::has('notifications')->take(3)->get()->map(function ($a) { return $a->notifications; });
# ^^^ works like a charm.

# 2. With eager loading:
App\Entities\Activity::with->('notifications')->has('notifications')->take(3)->get()->map(function ($a) { return $a->notifications; });
# ^^^ nope… Here's the exact results:
# "select * from `activities` where exists (select * from `notify_logs` where `notify_logs`.`report`->'$."activity"."element_id"' = `activities`.`element_id`) limit 3"
# []
# 17504.04
# "select * from `notify_logs` where `notify_logs`.`report`->'$."activity"."element_id"' in (?, ?)"
# array:2 [
#   0 => 15062832
#   1 => 13737651
# ]
# 1.27
# => Illuminate\Support\Collection {#1114
#      all: [
#        Illuminate\Database\Eloquent\Collection {#1085
#          all: [],
#        },
#        Illuminate\Database\Eloquent\Collection {#1087
#          all: [],
#        },
#        Illuminate\Database\Eloquent\Collection {#1061
#          all: [],
#        },
#      ],
#    }

pilat left a reply on With() Does Not Work If I Use Where() In Relations

Ok. What I understand at this moment is that there has to be an option to use ::withColumn() in the eager-loading query builder.

Something like this:

    public function custom_fields()
    {
        return $this->hasMany(\App\CustomField::class, 'element_id', 'element_id')
            ->whereColumn('custom_fields.subdomain', 'activities.subdomain');
            // or simpler: ->whereColumn('subdomain', 'subdomain');
    }

    public function lead()
    {
        return $this->hasOne(\App\AmoLeadsCache::class, 'id', 'element_id')
             ->whereColumn('leads_cache.subdomain', 'activities.subdomain');
    }
    // this doesn't work, off cause…

At the moment, I'll stick to the custom made local scopes, like this one:

    public function scopeWithLead($query, $subdomain)
    {
        return $query
            ->with([ 'lead' => function ($query) use ($subdomain) {
                $query
                    ->where('subdomain', $subdomain);
            }]);
    }

, although I don't find it very DRY having to repeat that $subdomain constraint on both the Activitiy and its relations:

// finally, the client code:
\App\Entities\Activity::subdomain('subdomain1')->withLead('subdomain1')->take(3)->get();
// don't like that repetition of subdomain constraint however… :/
22 Nov
11 months ago

pilat left a reply on With() Does Not Work If I Use Where() In Relations

@BryceSharp thank you. It looks similar to my current workaround.

However, I loose eager loading this way. So, if I have, say, 500 activities in the list, I'll execute 500 extra SQL queries to fill them all.

You are assuming that the instance of Activity calling fillData() has the scope of the eager loaded lead.

Yes, this is what bothering me. Why is it exactyl there is no that scope when I use ->where() in the relation functions, but the scope is present when I don't use ->where()? Is there a way I could preserve both the extra constraint in relations AND the eager loading?

21 Nov
11 months ago

pilat left a reply on With() Does Not Work If I Use Where() In Relations

Listening to SQL queries:

>>> DB::listen(function ($query) { dump($query->sql); dump($query->bindings); dump($query->time); });
=> null
>>> App\Entities\Activity::with('lead')->latest()->take(3)->get()->map(function ($a) { return $a->fillData('{name}: {url}'); });
"select * from `activities` order by `created_at` desc limit 3"
[]
74.0
"select * from `leads_cache` where `leads_cache`.`subdomain` is null and `leads_cache`.`id` in (?, ?, ?)"
array:3 [
  0 => 4879927
  1 => 5219288
  2 => 5673393
]
13.18
=> Illuminate\Support\Collection {#1034
     all: [
       ": ",
       ": ",
       ": ",
     ],
   }

So, it looks for null subdomain for some reason…

But if there's no eager loading, it makes proper queries (just too many of them):

>>> App\Entities\Activity::latest()->take(3)->get()->map(function ($a) { return $a->fillData('{name}: {url}'); });
"select * from `activities` order by `created_at` desc limit 3"
[]
80.55
"select * from `leads_cache` where `leads_cache`.`id` = ? and `leads_cache`.`id` is not null and `leads_cache`.`subdomain` = ? limit 1"
array:2 [
  0 => 4879927
  1 => "mysubdomain"
]
8.77
"select * from `leads_cache` where `leads_cache`.`id` = ? and `leads_cache`.`id` is not null and `leads_cache`.`subdomain` = ? limit 1"
array:2 [
  0 => 5219288
  1 => "mysubdomain"
]
1.31
"select * from `leads_cache` where `leads_cache`.`id` = ? and `leads_cache`.`id` is not null and `leads_cache`.`subdomain` = ? limit 1"
array:2 [
  0 => 5673393
  1 => "mysubdomain"
]
0.75
=> Illuminate\Support\Collection {#1028
     all: [
       "Name 1: https://url1",
       "Name 2: https://url2",
       "Name 3: https://url3",
     ],
   }

pilat left a reply on With() Does Not Work If I Use Where() In Relations

Ok, I made that client code up, in fact.

Here's the part of the same Activity class that does not work:

class Activity extends Model
{
    // ...

    public function fillData($template)
    {
        if (strpos($template, '{price}') !== false) {
            $replaceData['price'] = empty($this->lead->price) ? '0' : $this->lead->price;
        }
        if (strpos($template, '{url}') !== false) {
            $replaceData['url'] = empty($this->lead->url) ? '[no url]' : $this->lead->url;
        }
        if (strpos($template, '{name}') !== false) {
            $replaceData['name'] = empty($this->lead->name) ? '[no name]' : $this->lead->name;
    }

    // ...
}

Now goes the client code:

1. No eager loading:

>>> App\Entities\Activity::latest()->take(3)->get()->map(function ($a) { return $a->fillData('{name}: {url}'); });

=> Illuminate\Support\Collection {#1026
     all: [
       "Name 1: https://url1",
       "Name 2: https://url2",
       "Name 3: https://url3",
     ],
   }

2. With eager loading:

>>> App\Entities\Activity::with('lead')->latest()->take(3)->get()->map(function ($a) { return $a->fillData('{name}: {url}'); });

=> Illuminate\Support\Collection {#1028
     all: [
       ": ",
       ": ",
       ": ",
     ],
   }

3. Still with eager loading, but without "::where()" in relation functions (my very first example):

>>> App\Entities\Activity::with('lead')->latest()->take(3)->get()->map(function ($a) { return $a->fillData('{name}: {url}'); });

=> Illuminate\Support\Collection {#1026
     all: [
       "Name 1: https://url1",
       "Name 2: https://url2",
       "Name 3: https://url3",
     ],
   }
20 Nov
11 months ago

pilat started a new conversation With() Does Not Work If I Use Where() In Relations

Hi, I've stumbled over this issue.

Laravel 5.3

Here's couple of examples:

class Activity extends Model
{
    public function custom_fields()
    {
        return $this->hasMany(\App\CustomField::class, 'element_id', 'element_id');
    }

    public function lead()
    {
        return $this->hasOne(\App\AmoLeadsCache::class, 'id', 'element_id');
    }
}

class Activity extends Model
{
    public function custom_fields()
    {
        return $this->hasMany(\App\CustomField::class, 'element_id', 'element_id')
            ->where('custom_fields.subdomain', $this->subdomain);
    }

    public function lead()
    {
        return $this->hasOne(\App\AmoLeadsCache::class, 'id', 'element_id')
            ->where('leads_cache.subdomain', $this->subdomain);
    }
}

Now, in client's code I do the following:

Activity::with('custom_fields', 'lead')->where(/*some other cond*/)->get();

It works as expected if I don't have ->where() in the "relations" methods. If i have, however, all relations are empty (lead=>null, custom_fields=>[]).

Anyone knows how to overcome this? I really need to preserve those ->where() clauses, but I also need eager loading.

18 Oct
1 year ago

pilat started a new conversation Arrow Keys Don't Work In Tinker

Help, please,

There's a problem which causes me lots of pain when I'm trying to use tinker on production server.

In the local development I can use Up and Down keys to browse commands history. Also, I often use Ctrl+{A,E} to move to the line start/end. None of this works when I'm on remote server via ssh. Even Left and Right keys are not working :-(

Here's, what I get when pressing this or that key:

>>> Up arrow: ^[[A ; Down arrow: ^[[B ; Left: ^[[D ; Right: ^[[C ; Ctrl+A: ^A ; Ctrl+E: ^E

Terminal: iTerm2

Remote server OS: some sort of linux... uname -a only says it's Linux and then displays the host name.

Note: I can use arrows in shell (/bin/bash), when connected to that server. The issue is only in tinker.

13 Sep
1 year ago

pilat left a reply on Performant Way To Mass "update Or Create"

@kfirba Thank you, this is something I've thought of, actually, just wanted to avoid slipping off the ORM-way. Also, there is a strange error when I'm trying to add "unique index" to this table (looks that I have some records violating this).

But I should definitely give it a try and measure how fast if would become.

Btw, I import 500 records at a time, are there any limitation on the SQL query length?

P.S.: there was a point in past, when I simply used REPLACE INTO …but it kept increasing autoincremental ` and that was also "no an ORM" :-)

pilat started a new conversation Performant Way To Mass "update Or Create"

Hi, I'm looking for a way to do something like updateOrCreateMany (imaginary method).Currently, the code looks like this:

class ActivityRepositoryEloquent extends BaseRepository implements ActivityRepository
{
    // ... before this method, that was something like:
    //  $this->model
           ->whereIn('element_id', array_column($records, 'element_id')
           ->update([ 'delete' => true ]);

    public function insertMany($records)
    {
        $searchFields = [
            'subdomain',
            'element_type',
            'element_id',
            'slug',
        ];

        foreach ($records as $record) {
            $record['deleted'] = false;

            $this->model->updateOrCreate(
                array_only($record, $searchFields),
                array_except($record, $searchFields)
            );
        }
    }
}

What it does, in fact, is the following: it makes SELECT * …nd then `UPDATE ?or each of the records!

Is there a way to minimize the number of SQL queries?

A comment about that 'deleted' field: I'm syncing data with a 3rd-part service. For the original data, let's call is "leads", there is id field defined by 3rd-party system. So, I simply store it in my database with that ID and have no issues. There is related data, however, like "custom fields", that I (re)generate in my site, basing on the leads data. Here I have auto-incremental id and I've noticed that my genius schema (deleting all related and re-generating it after leads sync) causes ids to grow higher and higher after each sync.

So, the revised schema is the following:

  1. Set deleted = false for all the custom fields, with element_id IN ([ array of lead ids ])

  2. updateOrCreate custom fields, basing on leads data + hardcoded deleted = 0

  3. Delete all the records that have deleted = 1 remaining;

22 Aug
1 year ago

pilat left a reply on How To Retrieve Raw Url Query String

http_build_query($request->query()); // for GET params only
// or
http_build_query($request->input()); // for all data

Not only this would give you "query string, formatted for direct using in links", it would also prepare it in accordance to all related RFCs: eliminate duplicates, encode non-latin characters and so on.

16 Aug
1 year ago

pilat left a reply on Design Question: Controllers, Request Processing

The very first dependency is $subdomain. It's just a string, reflecting currently selected "account". This is an account in a 3rd-party CRM system, in fact, and every user of my Laravel-based site is expected to belong to one or another (or even to several) such accounts. And, even if a user belongs to several accounts, each request to one of "account-aware pages" is bound to one account only (and there must be one).

2nd dependency is, let's call it $api. It is used to communicate with that CRM via API. provides methods like buildApiUrl(), apiCall(), getUsers(), getGroups(), getCustomFields() and many others. I need to know $subdomain when initializing this $api dependency.

3rd one is $conf. It's not an instance of certain class, but rather an stdClass object. I build this $conf once with a command like $this->conf = (object)App::make(\App\Config::class, [ $this->subdomain ])->getAllAsArray(); and then access config variables in $this->conf->var_name fashion. Also, there's separate configuration for each "subdomain".

On the second though, I should probably do the following this time:

  1. Not sure that I even need that parent ServiceAwareController (at least now), considering that I needed it for a common constructor from the very beginning, and then it turned out that controller's constructor was not a very good place to touch Request.

  2. Obtain dependencies from the service container in each action method, just like in "something I'd like to avoid" examples.

I often confuse action methods with other kinds of methods, forgetting that there is only one action method called within the request. So, all that "loading from service container", however "repeating" it looks in the code, will actually be called once per each request, which is good.

  1. Make controllers slimmer. Much slimmer than I have them now. This way I may actually need less dependencies in my action methods.

  2. Then, (after #2 + #3 are done) analyze what is repeating too much and do something with it, if it still itches.

pilat left a reply on Design Question: Controllers, Request Processing

@martinbean I'm trying to become a bit more DRY about dependencies hooking. Currently, my controller actions look like it's shown in the "would like to try to avoid" examples (both kinds, in fact), but I've realized that the set of dependencies I need inside action methods is almost the same each time ('subdomain', 'conf', 'api_connector', something like this), so, maybe I could move them out of single actions somehow?

pilat started a new conversation Design Question: Controllers, Request Processing

Hi, I have a number of controllers (but not all), where I need access to specific set of dependencies. Ideally, I'd like to access these dependencies as the class's properties, so, they're like "cached", and they're accessible from any method, closure etc. within that controller.

Here's the meta-code:

class ServiceAwareController extends Controller
{
  protected $subdomain, $dep1, $dep2, $dep3;

  public function __construct()
  {
    $this->subdomain = $request->subdomain;
    $this->dep1 = App::make('dep1', [ $this->subdomain ]);
    $this->dep2 = App::make('dep2', [ $this->subdomain ]);
    $this->dep3 = App::make('dep3', [ $this->dep3 ]);
    // ...
  }
}

class ChildController1 extends ServiceAwareController
{
  public function anActionMethod()
  {
    return $this->dep2->produceSomething();
  }
}

class ChildController2 extends ServiceAwareController
{
  public function anActionMethod()
  {
    return $this->dep1->produceSomething();
  }
}```


The issue: my services depend on request and controller's constructor is not a place to work with request. The request is not fully ready, at this point. This I've read on Github when I've got stuck with "why there is no effect from my middlewares": I've placed some code into the controller's constructor right after `$this->middleware()` calls, and that code presumed all middlewares have already run, which was why it failed.

Is there an elegant solution on how to provide easy access to dependencies to all my "child controllers"? Without overloading action methods' signatures, or putting numerous "App::make" in the beginning of each of them?

I.e., this is something I'd like to avoid, if possible:
```php
class ChildController1 extends ServiceAwareController
{
  public function anActionMethod(Dep1 $dep1, Dep2 $dep2)
  {
    // 1. signature is overloaded
    // 2. there might be some extra logic required to instantiate dependency properly (well, at least I can put that logic into service provider)
  }

  public function anotherActionMethod(Dep1 $dep1, Dep2 $dep2)
  {
    $this->subdomain = $request->subdomain;
    $this->dep1 = App::make('dep1', [ $this->subdomain ]);
    $this->dep2 = App::make('dep2', [ $this->subdomain ]);
    $this->dep3 = App::make('dep3', [ $this->dep3 ]);
    // it won't be very DRY if I put this stuff at the top of each action method...
  }```

15 Aug
1 year ago

pilat left a reply on Auth()->check() Inside Error Template

Why would you want to run abort in a service provider and then still have your application work?

I don't want it to work. I want to display an error page, but to offer some adequate (to the current auth status) links for the user to "get out from this error page".

pilat started a new conversation Auth()->check() Inside Error Template

Good Morning,

I have an issue with auth() checking inside the error template. Here's what I'm trying to do:

@if (auth()->check())
    <a href="{{ url('home') }}" class="btn btn-default">Home</a>
@else
    <a href="{{ url('register') }}" class="btn btn-default">Register</a>
    <a href="{{ url('login') }}" class="btn btn-default">Login</a>
@endif

Now, if I call abort() inside a controller — the `auth()-check works as expected. If I call `abort from inside a service provider (deferred), however, `auth()->checkalways returns false.

Is there a way to work around this issue?

14 Aug
1 year ago

pilat started a new conversation Cannot Test HTTP Responses When Using Abort() Inside A Service Provider

Hi, an't figure out one thing:

I have the following code in my Service Provider (it's deferred, by the way):

if ($someCond) {
  abort(404, 'blah-blah-blah');
}

… and here's the test:

/** @test */
public function test_regression_404_error_did_not_consider_auth_status()
{
    $this
        ->actingAs(User::find(1))
        ->get('section/somethingimaginary')
        ->seeStatusCode(404)
        ->see('something that only logged user should see on the error page');
}

When I try to run this test, I see the following error in PHPUnit's output:

1) MiddlewaresTest::test_regression_404_error_did_not_consider_auth_status
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: blah-blah-blah

How can I make this test work?

Notes:

  • the service provider is deferred;

  • when trying this same URL in the browser, I can actually see the error page I expect and the browser knows it has 404 response code;

  • there is something conditional (dependent on auth()->check()) on the error page, and it doesn't work, in fact. The error screen is alway as there were no logged in user. When I did abort(403) from a controller, this issue was not here.

11 Aug
1 year ago

pilat left a reply on Asserting Array/json Structure Without Doing Any Request

@JhumanJ this is not what I look for. I need an ability to test my array structure, using the same "template convention" as in seeJsonStructure().

pilat started a new conversation Asserting Array/json Structure Without Doing Any Request

Hi, I'd like to test array structure, returned by some methods. I like the way how seeJsonSructure() works, but I can't apply it to a "raw data".

Here's example of what I'm trying to do:

        $this->seeJsonStructure(
            [
                '*' => [
                    'id', 'name',
                ],
            ],
            $object->getAllThings()
        );

I understand that seeJsonStructure() should be called on a response, but I don't have separate endpoint for getting just this list. Any workarounds?

02 Jul
1 year ago

pilat left a reply on Should I Use Laravel Notifications For This?

Ok, it seems I'd better create another kind of App\User, who has extra attributes: emails and phones and who will return these arrays in the routeNotificationFor($driver) function.

Activity class, on the other side, will have functions to return collections of these users. So, sending notifications, related to an activity, would look like this:

Notification::send($activity->getWorkerUsers(), new NotificationForAWorker());

Notification::send($activity->getCusomerUsers(), new NotificationForACustomer());