cbj4074

cbj4074

Member Since 4 Years Ago

Experience Points 4,675
Experience Level 1

325 experience to go until the next level!

In case you were wondering, you earn Laracasts experience when you:

  • Complete a lesson — 100pts
  • Create a forum thread — 50pts
  • Reply to a thread — 10pts
  • Leave a reply that is liked — 50pts
  • Receive a "Best Reply" award — 500pts
Lessons Completed 4
Lessons
Completed
Best Reply Awards 2
Best Reply
Awards
  • start-engines Created with Sketch.

    Start Your Engines

    Earned once you have completed your first Laracasts lesson.

  • first-thousand Created with Sketch.

    First Thousand

    Earned once you have earned your first 1000 experience points.

  • 1-year Created with Sketch.

    One Year Member

    Earned when you have been with Laracasts for 1 year.

  • 2-years Created with Sketch.

    Two Year Member

    Earned when you have been with Laracasts for 2 years.

  • 3-years Created with Sketch.

    Three Year Member

    Earned when you have been with Laracasts for 3 years.

  • 4-years Created with Sketch.

    Four Year Member

    Earned when you have been with Laracasts for 4 years.

  • 5-years Created with Sketch.

    Five Year Member

    Earned when you have been with Laracasts for 5 years.

  • school-session Created with Sketch.

    School In Session

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

  • welcome-newcomer Created with Sketch.

    Welcome To The Community

    Earned after your first post on the Laracasts forum.

  • full-time-student Created with Sketch.

    Full Time Learner

    Earned once 100 Laracasts lessons have been completed.

  • pay-it-forward Created with Sketch.

    Pay It Forward

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

  • subscriber-token Created with Sketch.

    Subscriber

    Earned if you are a paying Laracasts subscriber.

  • lifer-token Created with Sketch.

    Lifer

    Earned if you have a lifetime subscription to Laracasts.

  • lara-evanghelist Created with Sketch.

    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 Created with Sketch.

    Chatty Cathy

    Earned once you have achieved 500 forum replies.

  • lara-veteran Created with Sketch.

    Laracasts Veteran

    Earned once your experience points passes 100,000.

  • 10k-strong Created with Sketch.

    Ten Thousand Strong

    Earned once your experience points hits 10,000.

  • lara-master Created with Sketch.

    Laracasts Master

    Earned once 1000 Laracasts lessons have been completed.

  • laracasts-tutor Created with Sketch.

    Laracasts Tutor

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

  • laracasts-sensei Created with Sketch.

    Laracasts Sensei

    Earned once your experience points passes 1 million.

  • top-50 Created with Sketch.

    Top 50

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

06 May
3 months ago

cbj4074 left a reply on Rental Management DB Schema

My inclination is that you should in fact sum the negative values when calculating the cost of maintenance. Anything that is an expense should be stated as a negative value, in my opinion, even when displayed on a report. If for whatever reason you want to display it as a positive value, then just multiply it by -1 for display purposes, on a per-report basis, as needed.

In any case, positive-vs-negative values is not a particularly important consideration, given the use-case you describe, and you could certainly store the type (Income or Expense), but taking that approach will complicate your queries unnecessarily, in my opinion.

My advice is to start building this. You can stare at the schema all day, but until you scaffold-out some models, pencil in the relationships, and play with it in Tinker, it'll be difficult to see any shortcomings in your logic.

01 May
3 months ago

cbj4074 left a reply on Rental Management DB Schema

The polymorphic relationship seems okay upon first glance, although, I'm not sure you even need the type to track Income vs. Expense.

Why not use positive values to represent income and negative values to represent expense? That would make the computations a lot simpler, too.

02 Jul
1 year ago

cbj4074 left a reply on Testing Delete Method In Phpunit, "Expected Status Code 200 But Received 419."

@sutherland is correct in that the VerifyCsrfToken middleware is disabled automatically when running unit tests (and has been since Laravel 5.2).

So, if you receive a TokenMismatchException when conducting HTTP tests, the likely explanation is that Laravel's runningUnitTests() method is returning false, which will happen if your application environment is not set to testing.

In my case, I had set the env to testing using a phpunit.xml file, and then run a test directly, e.g., by right-clicking the test class in an IDE, and the wrong environment was used.

28 Jun
1 year ago

cbj4074 left a reply on How Best To Copy A Row Of Data From One Table To Another?

Eloquent and events are two different things.

I know that. My point was that using Eloquent to "copy" the row from one table to another provides the ability to take advantage of Eloquent-specific events.

Suppose I need to perform some other arbitrary task any time I copy a row in this manner. That functionality would come standard if I used Eloquent, whereas I would have to build it manually if I use a raw query (or Query Builder).

Then eloquent wouldn't work since eloquent converts to normal sql at runtime. It's a shortcut language.

Again, I know this. My point was that Laravel's SQL grammar implementation will evolve over time to accommodate changes in database-specific SQL syntax. It provides a level of abstraction that prevents me from having to worry about quoting style or other database-specific nuances that may evolve over time.

Speaking of quoting syntax, what if I later need to switch from MySQL to PostgreSQL (or any other DB)? That's another good reason not to use raw SQL. I'm guessing you've not used PostgreSQL in your projects, or are never likely to switch to it, because quoting style is one of the most significant departures from MySQL, and presents a real problem for raw queries.

In any case, I'll mark my own reply as Accepted, because, clearly, there's no "better" method for this than those already discussed. Why Taylor saw it fit to include a replicate() method but not the cross-table equivalent is anyone's guess.

27 Jun
1 year ago

cbj4074 left a reply on How Best To Copy A Row Of Data From One Table To Another?

@Cronix I "figured it out" before I started the thread. I wanted to know if there is a more "appropriate" method with respect to my specific needs, similar to replicate(), but that works across tables. Apparently, there wasn't then, and there isn't now.

Sure, a raw SQL query might be more "efficient", but it lacks the benefits of a DBAL (which we get with Query Builder), and the benefits of Events (which we get with Eloquent).

I know I'm boring you here, but to illustrate to other less knowledgeable users the reasons for which Query Builder or Eloquent might be preferable in certain scenarios as compared to raw SQL:

  1. What happens if my database's query syntax changes over time? I'd have to update any affected raw queries. Had I used Query Builder or Eloquent, a single Laravel update would bring my queries into compliance.
  2. What if I rename my table? A real-world example might be moving the table into a different schema. Then I'd have to hunt-down every raw query that uses the literal table name and update it.
  3. What happens if I add a new column to my users table (and my deleted_users table)? I'd have to hunt-down and edit any raw queries, whereas had I used a model-driven approach, I wouldn't have to do anything.

I'm sure there are other drawbacks to using raw SQL for this.

Regarding the Eloquent approach vs raw SQL, the lack of support for Events is self-explanatory.

Tangentially, what's the rub here? That I commented on a two-year-old thread? Or that I haven't chosen an Answer?

If it's resurrecting an old thread, the passage of time doesn't make something any less relevant, especially given that there still isn't a move() or clone() method (or whatever name makes sense) for copying or cloning a model from one table to another.

Closing or otherwise discouraging posts in "old threads" is one of the most annoying practices on the Internet. It forces users to duplicate topics that have been discussed at length, often with valuable (and still relevant) contributions that are sidelined when the existing thread is closed. And that's to say nothing of automatic notifications that contributors to the "old" thread may have received had the new/duplicate thread not been created.

If "resurrecting" bothers people, then Jeff should add a "Don't bump thread with my reply" checkbox.

@jlrdw Thanks for the useful contribution... that's exactly what I intend to do. :) I'll keep you posted.

cbj4074 left a reply on How Best To Copy A Row Of Data From One Table To Another?

@mehedi101 But won't that work only when duplicating rows in the same table?

I want to retrieve a model and save it to a different table.

So far, the only way I've found to do this is (as @chileno suggested) by retrieving the model instance and passing it into Query Builder via DB::table('users_deleted')->insert($user->toArray()) or into Eloquent using Mass Assignment, via DeletedUser::create($user->toArray()).

To be clear, neither of these is a "bad approach". I can live with either one. :)

31 May
1 year ago

cbj4074 left a reply on Accessing HTTPS Homestead Sites With Laravel Dusk

I've reduced the issue noted in my previous post to the fact that the HOME=/home/vagrant environment variable appears not to be set when chromedriver is started using the startChromeDriver() method.

I had hoped that adding the following to the relevant phpunit.xml would fix the issue:

<php>
    <env name="HOME" value="/home/vagrant"/>
</php>

But no such luck. And I don't like having to hard-code that path in several places.

I ended-up leaving that line commented-out, installing chromedriver globally, and then adding it to the Supervisor config, in /etc/supervisor/conf.d/chromedriver:

[program:chromedriver]
environment=HOME=/home/vagrant
process_name=%(program_name)s_%(process_num)02d
command=/usr/local/bin/chromedriver
autostart=true
startretries=99
autorestart=true
user=vagrant
numprocs=1
redirect_stderr=true

Note the environment=HOME=/home/vagrant. This is the special sauce that enables chromedriver to find the user-trusted certificates in $HOME/.pki/nssdb.

With all of this in place, my Dusk tests pass with HTTPS enabled, even on a freshly-provisioned Homestead instance. :)

A couple points of note:

I have no idea why it is insufficient simply to add the self-signed Homestead CA cert (/etc/nginx/ssl/ca.homestead.homestead.crt) to the operating system's trusted CA store. Chrome seems not to look there... so, where from is it getting its "built-in" list of trusted CAs?

Similarly, adding the aforementioned file to the user-specific certificate store doesn't do the job, either; it is necessary to add each individual certificate to be trusted, even though they are all issued by the Homestead CA.

My understanding, based on https://chromium.googlesource.com/chromium/src/+/lkcr/docs/linux_cert_management.md , is that a CA can be trusted with the C type (in contrast to P). For example:

certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n /etc/nginx/ssl/ca.homestead.homestead.crt -i /etc/nginx/ssl/ca.homestead.homestead.crt

But that doesn't seem to do the job.

If either of these measures could be made to work, it would be simpler to trust every Homestead-CA-issued certificate on the box.

In the meantime, I've added the following to my Vagrant provisioning process:

#!/bin/sh

# Install the latest Chrome version to prevent "Chrome version must be >= X"
# when running Dusk tests.

sudo curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add
sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt-get -y update
sudo apt-get -y install google-chrome-stable

# Install the latest chromedriver globally; using the Composer-installed version
# from any given project is less reliable if using a mounted filesystem.

CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`
wget -N -nv http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/
unzip -qq ~/chromedriver_linux64.zip -d ~/
rm ~/chromedriver_linux64.zip
sudo mv -f ~/chromedriver /usr/local/bin/chromedriver
sudo chown root:root /usr/local/bin/chromedriver
sudo chmod 0755 /usr/local/bin/chromedriver

# Add all Homestead-generated certificates to the "vagrant" user's trusted
# certificate store for Chrome. For more info:
# https://chromium.googlesource.com/chromium/src/+/lkcr/docs/linux_cert_management.md

sudo apt-get -y install libnss3-tools

# We don't want a password on the certificate database; see:
# https://bugzilla.redhat.com/show_bug.cgi?id=1401606

if [ -d "$HOME/.pki/nssdb" ]; then
    rm -rf $HOME/.pki/nssdb
fi

mkdir -p $HOME/.pki/nssdb
certutil -N -d $HOME/.pki/nssdb --empty-password

# Iterate through all Homestead-generated certificates and add them to the trusted
# store. Many of the certificates may be identical (because Homestead generates
# wildcards), in which case "certutil -d sql:$HOME/.pki/nssdb -L" command will
# show only one instance of each identical cert.

for file in /etc/nginx/ssl/*.crt; do
    [ -e "$file" ] || continue
    
    certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n "$file" -i "$file"
done

cbj4074 left a reply on Accessing HTTPS Homestead Sites With Laravel Dusk

Hmm, I'm not completely satisfied with this solution just yet.

For whatever reason, the certificate overrides are effective only when chromedriver is started from the command line, manually, e.g. /home/vagrant/code/laravel/vendor/laravel/dusk/bin/chromedriver-linux, and not when it's started from within Laravel via this method:

    /**
     * Prepare for Dusk test execution.
     *
     * @beforeClass
     * @return void
     */
    public static function prepare()
    {
        static::startChromeDriver();
    }

As yet, I have no idea why this is, given that PHP is running as the vagrant user when the Dusk test is invoked, and should therefore have access to the certificate database.

To workaround this bizarre limitation, I've commented-out the above line and am starting chromedriver in the background before running my Dusk tests.

I'd love to nail-down this odd anomaly!

cbj4074 left a reply on Accessing HTTPS Homestead Sites With Laravel Dusk

@Cronix Thank you! I'm not the OP, so I can't mark the thread as Resolved, but maybe @ORRD will. :D

30 May
1 year ago

cbj4074 left a reply on Accessing HTTPS Homestead Sites With Laravel Dusk

I finally figured this out.

Apparently, on Ubuntu, Chrome looks to an obscure and completely non-obvious location for TLS certificate authorization overrides. Basically, the trick here was to determine how to replicate trusting a self-signed certificate in the GUI version of Chrome, but via the CLI.

(See this helpful article for more information: https://leehblue.com/add-self-signed-ssl-google-chrome-ubuntu-16-04/ )

For self-signed certificates, such as those that Homestead generates during provisioning, it is necessary to add them to this arcane sqlite database in the effective user's (in this case, the vagrant user's) Home directory before Chrome (and in turn, the standalone chromedriver) will trust the certificates.

Chromium's (and therefore Chrome's) certificate handling is described in moderate detail at https://chromium.googlesource.com/chromium/src/+/lkcr/docs/linux_cert_management.md .

Firstly, certutil must be installed:

$ sudo apt-get install libnss3-tools

The following commands must be executed as the vagrant user, assuming a Homestead environment (otherwise, as whomever the chromedriver will be running).

On a new system, it's possible that the certificate database does not yet exist, in which case it is necessary to create it before performing the subsequent steps:

$ mkdir -p $HOME/.pki/nssdb

$ certutil -N -d $HOME/.pki/nssdb --empty-password

(the --empty-password switch is necessary to automate this process)

To add a given certificate:

$ certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n /etc/nginx/ssl/site.example.com.crt -i /etc/nginx/ssl/site.example.com.crt

To confirm the addition (list all "accepted" overrides):

$ certutil -d sql:$HOME/.pki/nssdb -L

And if for any reason one desires to remove an override:

$ certutil -D -d sql:$HOME/.pki/nssdb -n /etc/nginx/ssl/site.example.com.crt

Now, my tests pass when the APP_URL is https://site.example.com!

In your case, given that you have a valid wildcard that is not self-signed, it must be that your certificate does not contain the SubjectAltName field, which is now required as of Chrome 58, or you're missing an intermediate CA or two in the trust chain (can occur with less common CAs).

If your certificate does include SubjectAltName, then you should use curl from the CLI in Homestead (or whatever environment you're running Dusk from within) to determine which intermediate CA certificate is missing from the trust chain.

cbj4074 left a reply on Accessing HTTPS Homestead Sites With Laravel Dusk

Thanks for the reply! I really appreciate it.

After further testing, I concur. It seems that the standalone "chromedriver" that ships with Dusk either lacks support for TLS entirely or lacks awareness of Ubuntu's CA certificate store (it may be using a separate Java store). It's also possible that the binary includes only common CA certificates, which would, of course, preclude the use of a self-signed certificate.

My hunch is that it will be necessary to install Selenium and chromedriver manually, or perhaps even build one or both from source, to add support for custom CAs.

I have no choice but to figure it out, so I'll post back whenever that may be.

cbj4074 left a reply on Accessing HTTPS Homestead Sites With Laravel Dusk

@ORRD Were you ever able to resolve this? I'm having the exact same problem and it's proving rather frustrating. I'm surprised that more people don't have this problem, given that Homestead uses TLS for all sites by default currently.

I, too, am using a wildcard certificate, and while it is self-signed, it is trusted at the operating system level in Homestead (I add the CA to the trusted store upon provisioning).

04 May
1 year ago

cbj4074 left a reply on Laracasts Forum Does Not Handle CSRF Token Expiry Gracefully; Login Fails Silently

@jlrdw That all seems like an irrelevant corollary. We're talking about coming to this site, doing something else for a couple of hours (or however long the CSRF token expiry might be), and then trying to log in and the process failing without explanation.

cbj4074 left a reply on Laracasts Forum Does Not Handle CSRF Token Expiry Gracefully; Login Fails Silently

@somnathsah That won't solve the problem, because in the use-case I describe, I had never logged-in to begin with. I had come to the site, left it open overnight, and then tried to login the next day without refreshing the page first. It should not be the user's responsibility to "always remember to refresh the page before you try to login!"

Furthermore, the lack of an error message when login fails due to an expired CSRF token is unacceptable. This seems obvious, but some kind of error message should accompany any form submission failure in a web application.

This is easy to fix. And there is no need even to show an error message.

As I said in the OP, when the user submits the login form, check the response in JS for a token expiry error. If such an error is present, make another AJAX request to the server to request a new CSRF token, and then in the callback, update the login form with that new token, and re-submit the form automatically.

This fixes the issue, in all scenarios, and it's completely transparent to the user.

cbj4074 left a reply on Laracasts Forum Does Not Handle CSRF Token Expiry Gracefully; Login Fails Silently

@jlrdw This is a usability problem; the fact that I hit "Login" and "nothing happens" is inexcusable, in my opinion. Whatever actions I may or may not have taken on the site prior to attempting to login are irrelevant. Whether or not login functions correctly should not be contingent upon my browsing habits and for how long my browser has been open during any given session.

If you had built this website for a client, and the client told you, "Hey, people are trying to login and when they click the Login button, nothing happens," would you tell your client, "Oh, yeah, don't worry about that, just tell people to close their browsers every time they finish using their computers and this won't happen." I hope not.

@somnathsah That advice is sound, in general, but using redirect()->back()->withInput() in this instance would be inappropriate, and provide a poor user experience, because the login form is presented in a modal dialog. A proper fix for this issue must utilize AJAX.

cbj4074 started a new conversation Laracasts Forum Does Not Handle CSRF Token Expiry Gracefully; Login Fails Silently

Suppose that I'm visiting the laracasts.com website and I leave it open after reading an article. The next day, I come back to the computer and decide I'd like to login and participate in the thread I'm viewing.

When I click Login, and enter my credentials, the login form submission fails silently, with no visual feedback to the user whatsoever.

I had to open the browser's DevTools console to see that the underlying cause is an expired CSRF token.

I find this puzzling, especially on an otherwise well-built website, presumably built and maintained by Laravel experts. :)

For what it's worth, I would avoid using something like https://github.com/GeneaLabs/laravel-caffeine because it will keep a tremendous number of sessions alive for no good reason.

While it may be tempting to say "Your token has expired, please refresh the page", it would be a lot more elegant to modify the JS that processes the authentication response such that if the token is expired, a new one is requested and used in a subsequent request that is sent automatically, and completely transparently where the user is concerned.

This has been a problem for quite some time (maybe since launch); it would be nice to see it fixed.

12 Jan
1 year ago

cbj4074 left a reply on Group Forge Changed To Www-data, Can't Fix

Okay, so, php-fpm is running as the www-datauser, and nginx is running as the forge user.

You didn't specify whether either of these users belongs to a group, and if so, which one. ;) Given your comments, however, it seems that the forge user likely belongs to a group of the same name, as does the www-data user. Please confirm.

Also, it is important to note that both of these users must have appropriate access to the filesystem for the site to function as intended, because both php-fpm and nginx need a certain level of access in this configuration.

Try this (and do the same to any other directories, as necessary):

$ sudo chown -R forge:www-data ./storage
$ sudo chmod -R 770 ./storage

What is the result?

The best way to troubleshoot this further is to create a shell script that you can edit and refine between executions, until you have it "just right".

Something like this, from within the top-level Laravel application directory:

$ vim ./set-perms.sh

And then paste-in the following:

#!/bin/sh

chown -R forge:www-data ./storage
chmod -R 770 ./storage

To execute it:

$ sudo ./set-perms.sh

You can then edit the contents of the script, re-run it, see if it works as you require, and if not, edit and run it again. Rinse and repeat until it's dialed-in!

cbj4074 left a reply on Group Forge Changed To Www-data, Can't Fix

1.) As which user does php-fpm run, and does this user belong to a group? If so, which group?

2.) As which user does nginx run, and does this user belong to a group? If so, which group?

Generally speaking, your entire project tree, on the filesystem, should be owned by the same user and group.

For example, my storage directory looks like this (I'm not using Forge, but the point still stands):

drwxr-x---  6 web1 client1 4.0K Jan  5 15:31 storage

In my case, php-fpm runs as the web1 user, and nginx runs as the www-data user. web1 is in the client1 group, and client1 (which, again, is a group) is in the www-data group.

(I'm running Ubuntu 16.04 LTS in a relatively "vanilla" configuration.)

As you can see from the permissions set on my storage directory, 0750 should be sufficient, if you have configured everything appropriately.

The subdirectories look the same:

drwxr-x---  6 web1 client1 4.0K Jan  5 15:31 .
drwxr-x--- 13 web1 client1 4.0K Jan  5 15:32 ..
drwxr-x---  3 web1 client1 4.0K Jan  5 15:31 app
drwxr-x---  2 web1 client1 4.0K Jan  5 15:31 files
drwxr-x---  5 web1 client1 4.0K Jan  5 15:31 framework
drwxr-x---  2 web1 client1 4.0K Jan  5 15:31 logs

My advice is to untangle this problem once and for all, determine which ownership and permissions are necessary, and create a simple shell script that is capable of "fixing" both at any time, should this occur again in the future.

If you're able to answer the two questions I asked, above, I'm happy to provide additional guidance!

28 Nov
1 year ago

cbj4074 left a reply on Failed To Load Resource: The Server Responded With A Status Of 500 (HTTP/2.0 500)

Those permissions don't necessarily look problematic. You should be able to run composer commands in production (or any other environment), provided you run them as an appropriate user; that is, the user under whom PHP runs for the host in question (and not "root"!).

I wouldn't change the permissions until you have concrete evidence to suggest that they are problematic as is. If you are seeing "Access denied" or similar in a log, then please post the specific message.

I concur with @lostdreamer_nl in that you should see evidence of the 500 error in your web-server logs. You can tail the log while you hit the site with your iPhone and see what activity is logged for the IP address in question:

$ sudo tail -f -n 80 /var/log/nginx/error.log

(adjust the path to the log as necessary)

06 Feb
2 years ago

cbj4074 left a reply on Trouble With Blade Section Inheritance

Thank you, @poppabear (who helped me through a side channel)!

I should have known to go back to the Laravel 4.2 documentation, as it explains the @overwrite directive, which is exactly what I was missing:

template-2.blade.php

    @extends('template-1')

    @section('myMarkup')
        OVERRIDE markup goes here.
    @overwrite

This produces the following, as expected:

    <div>
        DEFAULT markup goes here.
        OVERRIDE markup goes here.
    </div>

For whatever reason, this information was expunged from the documentation between versions 5.0 and 5.1.

This is a recurring pattern in the Laravel documentation. Crucial information is expunged between versions, and repeated attempts to have it re-added (i.e., PRs on GitHub) are ignored. Extremely frustrating.

And, aside from that, the current documentation is flatly incorrect in that it states the following at the bottom of https://laravel.com/docs/master/blade#extending-a-layout :

In this example, the sidebar section is utilizing the @parent directive to append (rather than overwriting) content to the layout's sidebar.

The implication is that the default behavior, when extending a template and defining a section with the same name as a parent template, is to overwrite the section completely. That assertion is provably untrue, as evidenced above.

03 Feb
2 years ago

cbj4074 started a new conversation Trouble With Blade Section Inheritance

Hello!

Blade template inheritance seems so straightforward, yet when I cobble-together the most basic scenario, inheritance doesn't behave the way I had expected.

Take, for example, these three Blade templates:

template-1.blade.php

@section('myMarkup')
    DEFAULT markup goes here.
@endsection

@yield('myMarkup')

template-2.blade.php

@extends('template-1')

@section('myMarkup')
    OVERRIDE markup goes here.
@endsection

template-3.blade.php

<div>
    @include('template-1')
    @include('template-2')
</div>

Route:

Route::get('/test', function () \\{
    return view('template-3');
});

Requesting this route produces the following output:

<div>
    DEFAULT markup goes here.
    DEFAULT markup goes here.
</div>

I had expected the following instead:

<div>
    DEFAULT markup goes here.
    OVERRIDE markup goes here.
</div>

What might I be missing here?

Thanks for any help!

06 Sep
2 years ago

cbj4074 left a reply on Class Log Does Not Exist

I just encountered this in Laravel 5.1.

As @jbloomstrom and @jhoff suggest earlier in the thread, modifying Illuminate\Container\Container.php temporarily enables one to catch the underlying error that occurs prior to the logging error.

In my case, the underlying problem turned-out to be:

Symfony\Component\Debug\Exception\FatalThrowableError: Call to undefined method Closure::__set_state() in /var/www/example.com/laravel/bootstrap/cache/config.php:66

The problem seemed to occur suddenly, but I came to realize that it began when I executed php artisan config:cache, which creates the file bootstrap/cache/config.php, and this is the file in which the offending call, to an apparently-undefined function, Closure::__set_state(), is made.

I still don't know which library is at fault, but will update this post should I discover the culprit.

UPDATE:

The underlying problem in this instance is that one or more configuration files defines a closure. Laravel does not allow for this (see: https://github.com/laravel/framework/issues/9625 ), but the problem manifests only after the configuration is cached.

To fix the issue, hunt-down the offending key in bootstrap/cache/config.php, determine which configuration file the line is associated with, and remove any closures from the source file.

18 Aug
2 years ago

cbj4074 left a reply on Set Password On Uploaded Files

I'm not familiar with any of those libraries, in particular, but why not simply use 7-zip to create the ZIP archive with encryption and a password?

Provided you have the ability to install 7-zip, or request that it be installed for you, it should be as simple as calling the command-line executable and passing it appropriate arguments.

In 7-zip, the -p switch is used to add a password to the archive.

You can find examples of the specific syntax about halfway down this page (search for the word "secret"):

http://www.dotnetperls.com/7-zip-examples

07 May
3 years ago

cbj4074 left a reply on Disable Xdebug

For those running PHP 7 on Debian or Ubuntu, the commands vary slightly:

$ sudo phpdismod xdebug
$ sudo service php7.0-fpm restart
08 Mar
3 years ago

cbj4074 left a reply on Cartalyst Platform Still Maintained?

Hello, everyone!

I am pleased and honored to introduce myself as Cartalyst's Community Liaison, effective today. I am grateful that my eagerness and enthusiasm towards Cartalyst's products attracted the company's attention and lead it to reach-out to me in this capacity.

As a brief introduction, I've been using Cartalyst's products in a professional capacity for about 18 months. A little more than halfway through that period, I felt as though my familiarity with the products was sufficient to offer my candid opinion in a similar thread. As a then-newish-user, my hope is that this post will hearten and encourage others who may be interested but still "on the fence":

https://laracasts.com/discuss/channels/laravel/does-the-subscribtion-to-cartalyst-worth-it?page=1#reply-86738

Since writing that post, my appreciation for Cartalyst's work has only grown stronger. The more familiar I become with the code-base, the more I appreciate the effort and expertise that belies the Cartalyst Arsenal. Similarly, the more I interact with the amazing user community and the talented developers who reach for the Cartalyst toolbox every day, the more I enjoy being a part of the group. It's a true pleasure to learn from these individuals -- both the Cartalyst developers and the ever-growing group of subscribers -- and give-back wherever I'm able.

I recognize several usernames in the above-mentioned thread as individuals who later subscribed to the Cartalyst Arsenal, which I mention only as testament to the fact that Cartalyst's products speak for themselves. Perhaps some of those individuals would be willing to provide an update as to their respective experiences.

As noted above, one of the challenges that Cartalyst has faced, historically, is keeping the user-community up-to-date and apprised of all the goings-on behind the scenes. As so many growing businesses are, Cartalyst is torn between staying afloat with work that earns immediate returns and work that strives to satisfy future needs. Rest assured that development is as active as ever and that, in my humble opinion, Cartalyst sits atop a veritable gold-mine of awesome, yet-to-be-seen products that promise to reshape the ways in which many of us build applications on the Web.

To offer a bit about myself, I've been working in PHP professionally since 2003. In the spirit of Einsteinian wisdom, the more I learn, the more I realize just how little I know. And Cartalyst's developers' code has been rather influential in cementing that bit of age-old wisdom for me. :) Truly, I am humbled. The code is super-clean, well-organized, and impressive in every respect. As someone who has written a framework himself, of perhaps nearly equal scope, I can say that these developers are well-qualified.

I look forward to providing everyone with regular updates in the future and assisting Cartalyst in all of the areas noted in this thread.

Please don't hesitate to reach-out to me directly or to visit me on Cartalyst's Gitter channel, where I spend most of my life! :)

Your replies and questions are welcome, and I, like @drsii, will do my best to respond quickly and candidly.

Thanks so much!

29 Feb
3 years ago

cbj4074 left a reply on Bug? Changing A Column Of Table That Has An Enum Type.

This bit me today, too.

The fix is simple. (Finding it, much less so.) Credit to http://stackoverflow.com/a/32860409/1772379 .

In the database migration file, add a constructor method, like so:

public function __construct()
{
    DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
}

Problem solved! At least in Laravel 5.1 (I haven't tried this in 5.2).

I should note that, in my specific case, I'm attempting to rename a different column on the table (not one that is of the ENUM() type), and I don't see any unwanted fallout from this approach. That's why this "bug" is so weird; it prevents one from modifying any column on a table that contains one or more ENUM() columns.

For anyone who is trying to modify an actual ENUM() column, this approach will likely change the column type to VARCHAR() or similar and the column will then accept any string.

18 Feb
3 years ago

cbj4074 left a reply on How Best To Copy A Row Of Data From One Table To Another?

@JoeDawson Hello, and thanks for taking a look!

I'm not sure I understand your question. Are you asking how I am adding the row to be copied to the source table in the first place?

To explain a bit more, I have two tables: users and users_deleted. When a user is deleted, I need to copy the user record from users to users_deleted. (I'm aware of Eloquent's soft-delete capability, but it's not appropriate for this specific situation.)

Currently, the tables share identical structures, but that may not be the case in the future. It is possible that users_deleted may contain a different number of columns, in which case the model-driven approach may not be viable longer-term (unless it's possible to add/remove properties [columns] on the model dynamically, before calling save() against it).

I'm not opposed to doing this in two steps, e.g., select the data and then insert it with Query Builder. I'm just trying to understand what options are available before settling on an approach.

Thanks again! Happy to answer any other questions (and please do let me know if I didn't answer your previous question adequately).

cbj4074 started a new conversation How Best To Copy A Row Of Data From One Table To Another?

I have a need to copy a row of data from one database table to another.

What is the best means by which to accomplish this in Laravel?

The question at http://stackoverflow.com/questions/25043944/copying-one-rows-data-to-another-row-with-laravels-eloquent-or-fluent is very similar, but the accepted answer seems to assume that some version of the record already exists in both tables (whereas in my case, the record exists only in the first table).

I found the /Illuminate/Database/Eloquent/Model::setTable() method, and it seems to "work" in that when I inspect the model after calling it, the #table property reflects the new value, but when I call save() on the model, the data is not written to the DB. Yet, the save() call returns true.

Further, I notice that if I pass an invalid/non-existent table name, e.g., setTable('table_does_not_exist'), the call still returns true.

Any assistance in this regard would be much appreciated!

Thanks in advance!

05 Feb
3 years ago

cbj4074 left a reply on Migrate To Laravel With Old Hashed User Passwords.

There is already some sound advice here (and cheers for the actual code-snippet, @MikeHopley ), but I'd like to underscore a couple of crucial points that are not Laravel-specific (even if that means they are slightly off-topic).

1.) At no time should the old hash be stored in a separate column (even if the plan is to destroy it within some reasonable time-frame); doing so introduces significant risk without justification. This is exactly how Ashley Madison passwords were cracked ( http://cynosureprime.blogspot.com/2015/09/how-we-cracked-millions-of-ashley.html ):

The recommended approach is to store the algorithm along with the hash e.g. MD5:hash:salt or bcrypt12:hash:salt. This allows you to easily identify what algorithm to use on a per-user basis. When you deem an encryption strategy obsolete you can still protect your existing users by wrapping their existing hash in a new algorithm; in this case that would be bcrypt-ing the existing md5 hashes and storing something like md52bcrypt:hash:salt.

2.) In an ideal implementation (and I haven't done the research required to know if Laravel qualifies), there is no need to have a separate has_migrated field, or similar. This is for two reasons: a) this type of migration should be an ongoing process that happens every time that significant risk against a given hashing algorithm emerges publicly, thereby rendering such a column illogical, and b) it is redundant, because in an ideal implementation, the algorithms that have been used to compute a hash are embedded in the stored value.

3.) There is no need to send out an email blast at any point, because in an ideal implementation, every user's password is wrapped in the newest/strongest hashing algorithm, even if there are several hashing "layers" for any given user's password. One might visualize this strategy with the following pseudo-code: scrypt(bcrypt(sha1(md5('theuserspassword')))).

One of the more comprehensive road-maps that I've seen regarding this subject may be found at:

@uther_bendragon/sustainable-password-hashing-8c6bd5de3844" target="_blank">https://medium.com/@uther_bendragon/sustainable-password-hashing-8c6bd5de3844

Stay safe out there! ;)

19 Jan
3 years ago

cbj4074 left a reply on How Does URL::setRootControllerNamespace() Work In Versions >= 5.1?

Thanks to both of you. That's an excellent point; I see no reason not to use route() instead of action(), given that we assign a name to every route.

I'm content with that approach; it more or less obviates the need to use URL::setRootControllerNamespace(), and keeps the syntax brief.

Much appreciated!

cbj4074 left a reply on How Does URL::setRootControllerNamespace() Work In Versions >= 5.1?

Thanks for taking a look, @martinbean . I appreciate it.

You make a worthwhile point about the ambiguity that URL::setRootControllerNamespace() may introduce.

I am already using route group namespaces in the same way you demonstrate. But doing so still does not make it possible to abbreviate the controller namespace that is passed to the action() helper.

For example, if I define one of my package's routes as such

Route::group([
        'namespace' => 'MyVendor\MyPackage\Http\Controllers'
    ], function ()
    {
        Route::get('/locations', array('as'=>'locations','uses'=>'LocationController@index'));
    }
});

and then I try to do this in a Blade template

{{action('LocationController@index', ['locationId' => $location->id])}}

then an ErrorException is thrown.

I would really prefer to avoid the need to do this instead:

{{action('MyVendor\MyPackage\Http\Controllers\LocationController@index', ['locationId' => $location->id])}}

Despite the potential ambiguity that modifying the root controller namespace introduces, its benefits can be significant.

Is my assessment accurate? Or have I missed your point entirely?

Thanks again for your help!

cbj4074 started a new conversation How Does URL::setRootControllerNamespace() Work In Versions >= 5.1?

I noticed that Taylor scrubbed the section of the Controller documentation that describes the URL::setRootControllerNamespace() method. He also scrubbed the documentation for the action() helper function.

I requested that he restore the documentation ( https://github.com/laravel/docs/pull/1832 ) and he agreed to do so.

Shortly thereafter, he removed it again ( https://github.com/laravel/docs/commit/d9fab42cffb0dce74322b7bb7b94c8828c156aae ).

I've been relying on this method, in conjunction with the action() helper function, to simplify the generation of URLs to controller actions. These two functions seem rather useful, and I'm at a loss as to why there is this push to expunge them from the documentation.

One possible explanation is that calling URL::setRootControllerNamespace() explicitly simply isn't necessary anymore. Were this to be the case, I'd accept it and move on with life.

However, when I comment-out the call to URL::setRootControllerNamespace('MyVendor\MyPackage\Http\Controllers'); in my custom service provider, some controller-routes continue to resolve correctly, while others do not; they fail with something like:

ErrorException in UrlGenerator.php line 590: Action App\Http\Controllers\MyController@myMethod not defined.

(that "automatically" generated namespace is wrong; it should be MyVendor\MyPackage\Http\Controllers).

I find this behavior curious because all of the my application's controllers utilize the same namespace, and they all extend the same base controller.

Ultimately, my question is this: Does anybody know how to generate a URL to a controller action while using only the portion of the class name relative to one's controller namespace, in Laravel >= 5.1, without calling URL::setRootControllerNamespace() explicitly first?

Taylor states in https://github.com/laravel/docs/pull/1832 that this method is "called automatically for you in 5.1", but refuses to explain how it works.

Thanks for any assistance!

18 Jan
3 years ago

cbj4074 left a reply on Where Do You Set The Public Directory In Laravel 5?

@warmwhisky

With this "new"/"better" method, you don't put that in /public/index.php. In fact, you don't put that function anywhere.

In the simplest terms possible (basically, duplicating the correct [though, not accepted] answer on StackOverflow):

1.) In /bootstrap/app.php (not /public/index.php!), change the $app variable's declaration to this:

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

To be clear, in an unmodified installation, the lines to be replaced with the above snippet are here: https://github.com/laravel/laravel/blob/master/bootstrap/app.php#L14-L16

2.) Create the file /app/Application.php. Populate it with the following contents:

<?php namespace App;

class Application extends \Illuminate\Foundation\Application
{

public function publicPath()
{
    return $this->basePath . '/../../web';
}

}

Be sure to change the return value in the above method to reflect the actual path to the desired public directory on your system.

That's all there is to it! Happy to answer any questions if you're still stuck.

15 Jan
3 years ago

cbj4074 left a reply on Where Do You Set The Public Directory In Laravel 5?

@shanecp Thank you for sharing this tip! I'm delighted to find that this does, in fact, do the job! Very simple and elegant as compared to some of the alternatives posited (including my own).

I still had to modify index.php because my relative directory structure (between Laravel's "base path" and its "public path") deviates from the default, but this approach is definitely the best I've seen to date.

Thanks again for taking the time to let us know!

23 Nov
3 years ago

cbj4074 left a reply on How Can I Disable The Xdebug?

@EliyaCohen Bashy's solution is rather helpful because it is more generic, but for the sake of posterity, the preferred means by which to disable xdebug on Debian (or Ubuntu), which you appear to be using, is:

 php5dismod xdebug

This simply deletes the symbolic links to xdebug.ini (most commonly in /etc/php5/fpm/conf.d and /etc/php5/cli/conf.d).

To renable xdebug, it's the opposite:

 php5enmod xdebug

Conversely, this creates symbolic links for xdebug.ini in the effective conf.d directories (for FPM, CLI, etc.).

It's necessary to restart the PHP daemon (if using one) to render the changes effective.

19 Nov
3 years ago

cbj4074 left a reply on Where Do You Set The Public Directory In Laravel 5?

@inyansuta My best guess is that Laravel is using PHP's __DIR__ constant, so as long as your relative directory structure is the same, everything "just works".

It's when the relative structure changes that employing the measures discussed in this thread becomes necessary.

12 Nov
3 years ago

cbj4074 left a reply on [L5] Nginx Error Instead Of Json Response

What's the URI that you're requesting? Are you sure that you are, in fact, hitting the associated route?

In other words, if you put a dd(); just before if(!$article){, is it printed?

11 Nov
3 years ago

cbj4074 left a reply on Laracasts Painfully Slow?

Getting the occasional 504 Gateway Timeout, but I'm sure Jeffrey knows that all too well. And I'm helping by creating frivolous traffic. :)

19 Oct
3 years ago

cbj4074 left a reply on Where Do You Set The Public Directory In Laravel 5?

@ferrolho , I apologize for the long-delayed reply. I could have sworn that I had enabled notifications for this thread, but, apparently, I had not.

I don't use PHP's built-in HTTP server via Laravel, so I had not tried php artisan serve, but I just tried it on my development server and the HTTP server at least starts:

# php artisan serve
Laravel development server started on http://localhost:8000/

I receive a 500 error when I try to browse to http://localhost:8000, but that could be for so many different reasons. I have a particularly complex stack configuration (out of necessity), so I'm not at all surprised that PHP's built-in HTTP server doesn't work for me out-of-the-box. I probably need to pass it a path, at a minimum, but, unfortunately, I can't spare the time to troubleshoot it further.

I won't belabor the issues that Taylor (and others) raise in this discussion, but I urge you to consider using Homestead, plain-old VirtualBox, or something better suited to the task:

https://github.com/laravel/laravel/commit/80fb944e45801cec81b459f73892dbfc80c39de6

But let's try to work through your issue, nonetheless.

Firstly, this bit should be placed at the top of index.php, not the bottom:

function public_path($path = '')
{
    return realpath(__DIR__);
}

But in looking back at my notes regarding this subject, I appear to have gone a slightly different direction some time after my initial post in this thread.

I removed that function in favor of what seems to be a slightly more involved, although less error-prone, approach.

I've prepared a GitHub Gist of the changes that I typically make to index.php:

https://gist.github.com/cbj4074/9bb210706f8f2fdd61b0

All one should have to do is change lines 23 and 24 to suit his environment. The default values are intended to work with a "stock" Laravel configuration.

Regarding the Service Provider, your code looks okay to me.

You remembered to add the Service Provider to the 'providers' array in /config/app.php, correct?

If the revisions to your index.php don't solve the problem, I'd need additional details regarding the error that you receive:

[ErrorException]
chdir(): No such file or directory (errno 2)

I suspect that this is related to using PHP's built-in HTTP server without specifying a path to the document root. Because I don't use it, I'm not sure how php artisan serve sets the web-server's document root, but you might experiment with starting the server manually and see if you can get it to behave. Something like this:

php -S localhost:8000 -t public/

where public/ represents the correct path on your particular system. If you manage to get that to work, then we may have yet one more place within the Laravel source to override the public path definition.

Please do let us know what you find! Again, sorry for the late reply!

19 Aug
3 years ago

cbj4074 left a reply on Where Do You Set The Public Directory In Laravel 5?

@mtvs_dev Good point about not using App::bind() in index.php. And this reason is in addition to the reason I describe in my first post within this thread (item 4).

@iboinas There are problems with the approach you describe above. Most notably, the problem that I address in item 3 above: this approach is incompatible with environment-detection via the .env file.

04 Aug
4 years ago

cbj4074 left a reply on Is A Subscription To Cartalyst Worth It?

My predecessor found Cartalyst, so I can't take credit for the sound decision to subscribe.

We've had a multi-developer subscription for about a year and I must agree that it's worth every penny. I'm not in any way affiliated; I'm just a very satisfied subscriber.

In our particular case, the most compelling arguments to be made in its favor are:

1.) The product is excellent.

I've learned more about Laravel from reading Cartalyst's code than I have from reading any book or following any tutorial. The code is as well-written as any I've seen. Many large, popular products have horrible, unsightly code hidden under the hood. After nearly a year with Cartalyst, I haven't seen a single line of code that made me shake my head.

I have the most experience with Platform and Sentinel (the non-FOSS successor to Sentry), which I consider to be Cartalyst's "flagship" packages. These two packages, coupled with the auxiliary components that contribute to their make-up, have thus far been sufficient for all of our application development needs (and we are a sizable corporate enterprise). These two packages alone provide content management, robust authentication, role-based authorization, a theme engine with asset-queuing and complete template and asset inheritance (with fallbacks), and more. Extremely powerful.

2.) The entire product-line is designed with extensibility at the forefront.

It's of utmost importance that we're able to override functionality to suit our specific needs. Platform is especially powerful in this regard because of its Extension system. Platform provides a GUI-driven tool for spawning new Extensions, which makes them effortless to create and configure. Each Extension is a mini-Laravel installation, which is portable, self-contained, and features dependency-management (one Extension can require another, for example), and allows the developer to override quite literally any aspect of the base application's behavior -- including logic, templates, assets, localization strings... they've thought of everything.

3.) The support is top-notch. Cartalyst is extremely responsive to support inquiries, bugs/issues, and Pull Requests on GitHub.

Support is not limited to clarifying finer points of the documentation; they never let themselves off easy. If the team can't solve your problem off-the-cuff (and they usually do), the developers will roll-up their sleeves and dig into your code, which is unprecedented in my experience. Even when it's "operator error", Cartalyst will show you where you've gone wrong, make the necessary changes, and return your code in working condition. Now that's support.

As somebody who has opened many bug reports of varying severity, I've been rather impressed with Cartalyst's ability to fix issues and tag the corresponding releases quickly.

4.) Cartalyst worries about semantic versioning, release tagging, release cycles, etc., so I don't have to.

Anybody who has ever had to manage a large, non-private code-base knows all too well the monumental effort required. Laravel's refusal to follow semantic versioning thus far has made the job even more cumbersome, yet the Cartalyst team ensures that its code is released in lockstep with changes in Laravel and that there are no "upstream surprises". Offloading this responsibility to Cartalyst has been worth the cost alone.

5.) The cost is incredibly reasonable and effortless to justify to management.

Cartalyst should (and probably will) charge more in the future. I have to assume that the current rate is "introductory" and designed to build the user-base. Even if the cost doubled, we wouldn't hesitate.

Given that the subscription costs as little as an hour of one's billable time each month, the support alone will pay for the subscription in a matter of days. It's all too easy to waste several non-billable hours struggling with some snag that the Cartalyst team would be able to address quickly and expertly.

6.) The private Gitter chat channel.

I've learned as much from the Gitter chat channel as I have from any other learning aid. There is a social aspect to the chat channel that makes learning Cartalyst fun. There is a sense of companionship, camaraderie, and mutual best-interest that fosters creativity and productivity. Inevitably, somebody in that chat channel has the answer (and oftentimes it's another subscriber, not even a Cartalyst staff-member).

7.) The license is fair and reasonable.

The ability to continue operating websites that are built upon Cartalyst components in the event that we terminate the relationship for any reason is imperative to our company. The licensing terms allow for this and are entirely reasonable in every other respect. Many organizations take this ability for granted when dealing with proprietary software licenses.

All of that said, the other assessments in this thread are fair and accurate. The documentation is good, but it does lag behind the code-base. But, I would rather have mature code with decent documentation than young, poorly-vetted code with exceptional documentation. My understanding is that they are fully-aware of the need for more tutorials, more walk-throughs, and more examples in the manual, and are working diligently to meet that need.

Knowledge worth acquiring is difficult to obtain. The learning curve is quite steep, and as davernz noted, trying to learn Laravel and Cartalyst's packages at the same time is a considerable undertaking. As someone who did precisely that, it took me several months of 8-hour days to feel like I could more or less build anything I might need (and I'm a senior developer with over 10 years of full-time development experience, mostly in PHP). But once your skills are dialed-in, the sky is the limit.

30 Apr
4 years ago

cbj4074 left a reply on Ho To Access Config Variables In Laravel 5?

1.) There's no need to use the Config facade to get configuration values; there is now an equivalent helper-method, config().

2.) In Laravel 5, configurations are no longer "namespaced", which is critical to understand when creating a configuration file (and particularly when merging configuration files).

In Laravel 4, the configuration options for a specific package were nested under a namespaced array, like so:

return [
    'myextension' => [
        'host' => 'localhost',
        'port' => 443,
    ],
];

In Laravel 5, the equivalent is:

return [
    'host' => 'localhost',
    'port' => 443,
];

3.) Rather than store your custom configuration values in /config/app.php, a cleaner approach is to store them in your own configuration file, e.g., /config/myname-mypackage.php. You would then retrieve all values with something like:

$config = config('myname-mypackage');

To retrieve only an individual value, simply use dot-notation:

$config = config('myname-mypackage.host');

It is possible to nest configuration files within subdirectories (within /config/), too, which can be necessary in order to prevent conflicts ( @sdebacker asked about this -- great question ).

If you create your configuration file at, for example, /config/myname/mypackage.php, you would access the values using dots in place of the directory separators:

$config = config('myname.mypackage');

4.) It's simple to merge configuration files using a service provider's register() method:

public function register()
{
    $this->mergeConfigFrom(
        __DIR__.'/../config/config.php', 'myname-mypackage'
    );
}

(note that we omit the .php extension in the second argument)

5.) If you're building your own packages, you can specify any number of configuration files to be published when the new vendor:publish artisan command is executed:

public function boot()
{
    $this->publishes([
        __DIR__.'/../config/config.php' => config_path('myname-mypackage.php'),
    ]);
}

For more information, refer to the relevant doc at http://laravel.com/docs/master/packages#configuration .

03 Apr
4 years ago

cbj4074 left a reply on Where Do You Set The Public Directory In Laravel 5?

As a new Laravel user, I find it staggering that there is no simple, elegant, centralized, and environment-aware means by which to define both the "private" and the "public" filesystem paths that are referenced throughout the application.

While it may make sense to nest the public directory beneath the application root for source-control and packaging/distribution purposes, that is an unrealistic structure in a real-world deployment scenario.

The scenario that the OP describes is far more realistic, wherein the "public" directory is actually the web-server's "document root", and the rest of the application resides above and outside of the document root.

The default directory structure inspires less capable users to dump the entire application (including everything outside of the public directory) into the web-server's document root. This practice should be discouraged, as it exposes a broad attack-vector: the ability to access application resources via HTTP.

One might argue that a properly-configured server does not expose the application to unnecessary risk when Laravel is deployed as-is, but there is no guarantee as to the environment's viability. If PHP is upgraded and the process goes awry, thereby causing the web-server to present .php files as raw file downloads, an active attacker could conceivably "scrape" most or all source code. This type of mis-configuration has the potential to expose application details, ranging from filesystem paths to various credentials (cringe).

My research on this subject has yielded five possible approaches to using a more sensible "public" path (and I'm sure there are others):

1.)

Using a symbolic link (or junction point, on NTFS volumes)

This method is not portable. While it may be an acceptable solution for a solo developer, in a collaborative development environment, it's a non-option because it doesn't travel with the application source-code and is therefore cumbersome to implement consistently.

2.)

Using IoC container in /app/Providers/AppServiceProvider.php

This is not a bad approach, but as @patrickbrouwers notes, it suffers from a considerable flaw: the public_path() helper function remains unaffected in the context of configuration files, which causes the public path to be defined incorrectly in many third-party packages.

Also, if I were going to implement this approach, I would create a new Service Provider, rather than modify the included AppServiceProvider class (only because doing so reduces the likelihood of having to merge-in upstream changes).

3.)

Using IoC container in /bootstrap/app.php

This works well enough -- until environment-detection via the .env file is necessary. Any attempt to access $app->environment() at this stage yields, Uncaught exception 'ReflectionException' with message 'Class env does not exist'. Furthermore, in order to minimize the impact of upstream changes, my preference is to limit customization to as few files as possible, which brings me to the next method.

4.)

Using IoC container in /index.php

Given that /public/index.php already requires modifications to function out-of-the-box on many systems (the fact that realpath() is not used in the require and require_once statements causes the application to fail fatally in environments in which PHP enforces open_basedir restrictions, which do not allow nor resolve relative path notations, such as ../), my preference would be to make this change (to the public path definition) in the same file.

But, as with the above method, attempts to perform environment-detection cause a fatal error in this context, because the required resources have not yet been loaded.

5.)

Using custom IoC container in /app/Providers/*.php, coupled with overriding public_path() helper function

I settled on this method because it is the only method that solves the third-party package configuration problem (mentioned in method #2) and accounts for environment-detection.

The public_path() helper function (defined in /vendor/laravel/framework/src/Illuminate/Foundation/helpers.php) is wrapped in if ( ! function_exists('public_path')), so, if a function by the same name is defined before this instance is referenced, it becomes possible to override its functionality.

Given that I have already had to modify index.php (due to the open_basedir problems that I explained in #4), I elected to make this change in index.php, too.

//Defining this function here causes the helper function by the same name to be
//skipped, thereby allowing its functionality to be overridden. This is
//required to use a "non-standard" location for Laravel's "public" directory.

function public_path($path = '')
{
    return realpath(__DIR__);
}

Next, I created a custom Service Provider (instead of using AppServiceProvider.php, the reasons for which I explain in #2):

php artisan make:provider MyCustomServiceProvider

The file is created at /app/Providers/MyCustomServiceProvider.php. The register() method can then be populated with something like the following:

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        if (env('PUBLIC_PATH') !== NULL) {
            //An example that demonstrates setting Laravel's public path.
            
            $this->app['path.public'] = env('PUBLIC_PATH');
            
            //An example that demonstrates setting a third-party config value.
            
            $this->app['config']->set('cartalyst.themes.paths', array(env('PUBLIC_PATH') . DIRECTORY_SEPARATOR . 'themes'));
        }
        
        //An example that demonstrates environment-detection.
        
        if ($this->app->environment() === 'local') {
            
        }
        elseif ($this->app->environment() === 'development') {
            
        }
        elseif ($this->app->environment() === 'test') {
            
        }
        elseif ($this->app->environment() === 'production') {
            
        }
    }

At this point, everything seems to be working over HTTP. And while all of the artisan commands that I've tried have worked as expected, I have not exhausted all possible CLI scenarios.

I welcome any comments, improvements, or other feedback regarding this approach.

Thanks to everyone above for contributing to this solution!