How to load seeders from Laravel packages, without publishing them? - php

I'm developing some packages for Laravel (for internal use).
It is logical for a package, to have its own seeders.
For example, let's suppose we are creating a package called Company\Geo that has the data of countries and continents and flags, etc.
It's a good UX that this package should have 100% self-contained seeder classes to populate the database tables with the data.
However, I'm stuck at running seeders from packages. This is my package directory strcuture:
- host => (a laravel app to test the package)
- WebSite
- packages
- Company
- FirstPackage
- database
- migrations
- seeders
- FirstSeeder.php
- src
- Models
- Http
- Controllers
And here is my FirstSeeder.php code:
<?php
namespace Company\FirstPackage\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\App;
class FirstSeeder extends Seeder
{
public function run()
{
// code to seed
}
}
And here is my composer.json of the Company\FirstPackage package:
"autoload": {
"classmap": [
"/database/migrations"
],
"psr-4": {
"Company\\FirstPackage\\": "src/",
"Company\\FirstPackage\\Database\\": "database/"
}
},
Yet when I run php artisan db:seed --class=FirstSeeder I get this error:
Target class [Database\Seeders\FirstSeeder] does not exist.
How can I call seeders of my package, without publishing them to the host package?

I have just read the source code. I believe you got to do this:
// Pls use double backward slashes.
php artisan db:seed --class=php artisan db:seed --class=Company\\Package\\Database\\Seeder\\FirstSeeder
You might want to take a look at the Illuminate\Database\Console\Seeds\SeedCommand::getSeeder() method.

You can use the same logic that is used in ServiceProvider. This way you can simply run: php artisan db:seed and it should run any seeders you have in your packages.
protected function loadSeeders($seed_list){
$this->callAfterResolving(DatabaseSeeder::class, function ($seeder) use ($seed_list) {
foreach ((array) $seed_list as $path) {
$seeder->call($seed_list);
// here goes the code that will print out in console that the migration was succesful
}
});
}
then you can create a list using a function that gets every class name you are using in your package. And put it inside, like this
$seed_list[] = 'Vendor/Name/Directory/ClassName'
// inside your service provider
boot()
{
$this->loadSeeders($seed_list)
}
this is just a copy past please see
https://laracasts.com/discuss/channels/laravel/best-way-to-load-seeds-and-routes-from-a-package

Related

Adjust Seeders integration for Package Development in Laravel 8

I developed a package for an api in laravel.7.x. That api is delivered as an package for laravel. Now I want to upgrade it to laravel 8. Unfortunately I cannot get the seeders to work.
all package Seeders should executed after the
php artisan migrate:fresh --seed
command.
Appearantly the Seeder classes are not found. for Example Target class [PostSeeder] does not exist.
But it does exist, it's even in the same namespace.
Now I'm trying to start in with a DatabaseSeeder and this one IS found. But from there the other Seeders can't be triggered.
Does anyone has an Idea what can be tried or has a hint or code snippet for this problem?
Many thanks in advance.
alright, the issue is solved.
Use the new way of including factories in laravel 8 i.e Models have this new function:
use Acme\YourPackage\Database\Factories\PostFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
protected static function newFactory()
{
return PostFactory::new();
}
and make sure to call it like:
enter $this->call('Acme\YourPackage\Database\Seeders\PostSeeder');
after that make your sure your namespace and class paths are set correctly.

Laravel default helper method and facade doesn't work on custom package

I am developing a custom package where I need an access to the config data. I was able to pull data from config through my blade files, however, when I tried to call it from any custom classes I made, it's throwing an error:
Error: Call to undefined function Acme\Package\config()
What's interesting though, is that, when I tried using the facade Illuminate\Support\Facades\Config, it cannot find the class.
Is there any way I could retrieve data from the config (from the package and/or the app)?
<?php
namespace Acme\Package;
class MyClass {
public function test() {
config('app.name');
}
}
UPDATE: It works when running in the browser (package installed in a Laravel project) but fails when running package's test
UPDATE: If this helps, my package can be found here
Call to the config() is from here
And the test case that fails can be found here
<?php
namespace Acme\Package;
class MyClass {
public function test() {
\Illuminate\Support\Facades\Config::get('app.name');
}
}
Try like this, to use the full namespace to the Config Facade. You could also make a use statement under your namespace to inject the facade and then use Config::get('app.name). The reason it is not working is that your package cannot resolve the namespace of that facade as it is outside of the IoC container
You should try this:
<?php
namespace Acme\Package;
use Config;
class MyClass {
public function test() {
Config::get('app.name');
}
}
Updated answer
Please add below line in config/app.php in aliases section
'Config' => Illuminate\Support\Facades\Config::class,
then run below command
php artisan config:cache
php artisan cache:clear

'There are no commands defined in the "baum" namespace' error when using "baum" for implementing Nested Set

For implement of a Nested Set for my news category I want to use Baum package.
After installing this Package via Composer and add BaumServiceProvider to config/app.php providers, I try to install a new Model named Category via Below command:
php artisan baum:install MODEL
But I faced to the following error :
[InvalidArgumentException]
There are no commands defined in the "baum" namespace.
Of course I ran php artisan command and But there were no command named Baum on generated commands list.
What should I do?
First you have to add the code below in config/app.php:
Baum\Providers\BaumServiceProvider::class
If the problem still persists configuration files may be cached. Just call:
php artisan config:clear
Make sure that you added
Baum\Providers\BaumServiceProvider::class
To the providers array in the app.php file located in config/app.php
Whenever I know that I have added something into Laravel's core files, and it's not working from the command line, I typically just run
php artisan optimize
to regenerate all of the classes, and it will typically work. Running this command is something that I find myself doing fairly often.
In AppServiceProvider.php (app\Providers) you can modify register function: public function register()
{
$this->app->register(\Baum\Providers\BaumServiceProvider::class);
}
Not sure why 5.2 have error, just tweak a bit myself.
Try add this on APP/Console/kernel.php
protected $commands = [
//Commands\Inspire::class,
// Register Baum nestedset command
Commands\BaumCommand::class,
Commands\InstallCommand::class
];
and create two files and copy BaumCommand.php and InstallCommand in /vendor/baum/baum/src/Baum/console.
then change a namespace at the top.
namespace Baum\Commands;
to
namespace App\Console\Commands;

Creating a new ServiceProvider / Facade as a package in Laravel 5

Introduction
I've never worked with a framework before (Zend, CakePHP, etc) and finally decided to sit down and learn one. I'm starting with Laravel because the code looks pretty and unlike some other frameworks I tried to install, the "Hello, World!" example worked on the first try.
The Goal
For the time being, I want my app to do something very simple:
User submits a request in the form of: GET /dist/lat,lng
The app uses the remote IP address and MaxMind to determine $latitude1 and $longitude1
This request path is parsed for $latitude2 and $longitude2
Using these two positions, we calculate the distance between them. To do this I'm using Rafael Fragoso's WorldDistance PHP class
Since I plan to re-use this function in later projects, it didn't seem right to throw all of the code into the /app directory. The two reusable parts of the application were:
A service provider that connects to MaxMind and returns a latitude and longitude
A service provider that takes two points on a globe and returns the distance
If I build facades correctly then instead of my routes.php file being a mess of closures within closures, I can simply write:
Route::get('dist/{input}', function($input){
$input = explode( "," , $input );
return Distance::getDistance( GeoIP::getLocation(), $input );
});
What I've tried
Initial Attempt
For the first service provider, I found Daniel Stainback's Laravel 5 GeoIP service provider. It didn't install as easily as it should have (I had to manually copy geoip.php to the /config directory, update /config/app.php by hand, and run composer update and php artisan optimize) however it worked: A request to GET /test returned all of my information.
For the second service provider, I started by trying to mimic the directory structure and file naming convention of the GeoIP service provider. I figured that if I had the same naming convention, the autoloader would be able to locate my class. So I created /vendor/stevendesu/worlddistance/src/Stevendesu/WorldDistance\WorldDistanceServiceProvider.php:
<?php namespace Stevendesu\WorldDistance;
use Illuminate\Support\ServiceProvider;
class WorldDistanceServiceProvider extends ServiceProvider {
protected $defer = false;
public function register()
{
// Register providers.
$this->app['distance'] = $this->app->share(function($app)
{
return new WorldDistance();
});
}
public function provides()
{
return ['distance'];
}
}
I then added this to my /config/app.php:
'Stevendesu\WorldDistance\WorldDistanceServiceProvider',
This fails with a fatal error:
FatalErrorException in ProviderRepository.php line 150:
Class 'Stevendesu\WorldDistance\WorldDistanceServiceProvider' not found
Using WorkBench
Since this utterly failed I figured that there must be some other file dependency: maybe without composer.json or without a README it gives up. I don't know. So I started to look into package creation. Several Google searches for "create package laravel 5" proved fruitless. Either:
They were using Laravel 4.2, in which case the advice was "run php artisan workbench vendor/package --resources"
Or
They were using Laravel 5, in which case the docs were completely useless
The official Laravel 5 docs give you plenty of sample code, saying things like:
All you need to do is tell Laravel where the views for a given namespace are located. For example, if your package is named "courier", you might add the following to your service provider's boot method:
public function boot()
{
$this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');
}
This makes the assumption that you have a service provider to put a boot method in
Nothing in the docs says how to create a service provider in such a way that it will actually be loaded by Laravel.
I also found several different resources all of which assume you have a repository and you just want to include it in your app, or assume you have "workbench". Nothing about creating a new package entirely from scratch.
PHP Artisan did not even have a "workbench" command, and there was no "workbench.php" file in /config, so anything I found related to workbench was worthless. I started doing some research on Workbench and found several different questions on StackOverflow.
After a long time and some experimentation, I managed to get laravel/workbench into my composer.json, composer update, composer install, manually build a workbench.php config file, and finally use the PHP Artisan Workbench command to make a new package:
php artisan workbench Stevendesu/WorldDistance --resources
This created a directory: /workbench/stevendesu/world-distance with a number of sub-directories and only one file: /workbench/stevendesu/world-distance/src/Stevendesu/WorldDistance/WorldDistanceServiceProvider.php
This service provider class looked essentially identical to the file I created before, except that it was in the /workbench directory instead of the /vendor directory. I tried reloading the page and I still got the fatal error:
FatalErrorException in ProviderRepository.php line 150:
Class 'Stevendesu\WorldDistance\WorldDistanceServiceProvider' not found
I also tried php artisan vendor:publish. I don't really know what this command does and the description wasn't helpful, so maybe it would help? It didn't.
Question
How do I create a new service provider as a package so that in future projects I can simply include this package and have all the same functionality? Or rather, what did I do wrong so that the package I created isn't working?
After two days of playing with this I managed to find the solution. I had assumed that the directory structure mapped directly to the autoloader's path that it checked (e.g. attempting to access a class Stevendesu\WorldDistance\WorldDistanceServiceProvider would look in vendor/stevendesu/world-distance/WorldDistanceServiceProvider)... This isn't the case.
Reading through the composer source code to see how it actually loads the files, it builds a "classmap" - essentially a gigantic array mapping classes to their respective files. This file is built when you run composer update or composer install - and it will only be built correctly if composer knows the details of your package. That is - if your package is included in your project's composer.json file
I created a local git repository outside of my app then added my package to my app's composer.json file then ran composer update -- suddenly everything worked perfectly.
As for the:
It didn't install as easily as it should have
the secret sauce here was first add the service provider to /config/app.php then, second run php artisan vendor:publish

Adding a custom Artisan command with Behat

I've registered a custom Artisan command:
Artisan::add(new MigrateAll);
The class resides in app/commands (default location)
However when I run Behat I get the error:
Class 'MigrateAll' not found
Artisan is called in Behat for setting up the DB:
/**
* #static
* #beforeSuite
*/
public static function setUpDb()
{
Artisan::call('migrate:install');
//...
}
Do I need to give it a namespace? (I could not find the correct way to call the Artisan::add command with a namespaced class)
This is somewhat related to your earlier question. Your Behat test suite runs in a separate process independently of your app and knows nothing about the configuration. This also applies to the autoloading in your bootstrap and the autoloading would be the most likely reason why classes don't get found. This should be easily fixed by using Composer to autoload your own sources and vendor packages (both in your app and in your test suite).
# composer.json
{
"require": {
"…": "…"
},
"autoload": {
"psr-0": {
"": "../src"
}
}
}
// Include composer's autoloader in your `setUp()` / bootstrap / index.php.
include __DIR__ . '../vendor/autoload.php';
Take that process separation as a rule and keep in mind that Laravel like any other framework requires a whole bunch of other configuration. Since you are trying to use the database component, your next issue will be with that, because it won't be configured in your test suite.
The best approach is to create separate bootstrap file for Behat, which would inherit most lines from your normal bootstrap, where you need to pass the necessary configuration and do this:
/**
* #static
* #beforeSuite
*/
public static function setUp()
{
include_once('bootstrap.php');
}
If you configured your behat environment with this tut (Laravel, BDD And You: Let’s Get Started), after you added a new command, you need to $ composer dump-autoload to make behat to know the command.

Categories