i created an Artisan Command that created Repository. A Repository File and an Interface.
when user run below command, this files will be generated.
php artisan make:repository RepositoryName
now, next step is to add bind code to RepositoryServiceProvider in register method.
How do I add the following code to that file?
$this->app->bind(
RepositoryNameInterface::class,
RepositoryName::class,
);
and generally, how to add custom code to the method of class in PHP?
You can keep the repositories interface as key and repository as the value in an array in a file.
First, add use Illuminate\Filesystem\Filesystem; and initialize it in the make:repository command constructor like :
protected $files;
public function __construct( Filesystem $files)
{
parent::__construct();
$this->files = $files;
}
Let's add a function in your make:repository command.
// Add a function in make:repository command
public function writeToRepoCacheFile($repoName, $repoInterface)
{
// Let's make sure cache folder exists
$cacheDirPath = base_path('bootstrap') . DIRECTORY_SEPARATOR . 'cache';
$this->files->ensureDirectoryExists($cacheDirPath);
// The full path we will be keeping the array of repos
$file = $cacheDirPath . DIRECTORY_SEPARATOR . 'repositories.php';
if (! is_writable(dirname($file))) {
throw new Exception('The '.dirname($file).' directory must be present and writable.');
}
// Merge with the previously added repo's if available
$repositories = [];
if ($this->files->exists($file)) {
$repositories = $this->files->getRequire($file);
}
$repositoryList = array_merge($repositories, [
$repoInterface => $repoName
]);
$this->files->replace($file, '<?php'. PHP_EOL . PHP_EOL .'return ' . var_export($repositoryList, true).';');
}
Now, whenever you create a new repo, the writeToRepoCacheFile method should be called with parameters of $repoName and $interface with their respective path. The method will simply create a cache file in our bootstrap/cache folder naming repositories and add your recently added repos as an array in the file which later will be called in our service provider to get register.
The final one is to bind the interface with our repository.
public function bindTheRepos()
{
$repositories = base_path('bootstrap/cache/repositories.php');
if ($this->files->exists($repositories)) {
Collection::make($this->files->getRequire($repositories))
->map(function ($repo, $repoInterface) {
// Make sure the files exists
// Interface exists
$interfaceExists = $this->app['files']->exists(
$repoInterface.'.php'
);
// Repo exists
$repoExists = $this->app['files']->exists(
$repo.'.php'
);
if (
$interfaceExists &&
$repoExists
))) {
$this->app->bind($repoInterface, $repo);
}
});
}
}
Add the above function in your service provider and call the function in the register method of the service provider.
Make sure the interface and repository classes path matches your structured path.
This will dynamically bind your newly created repository with its interface.
Related
The name is quite bad, but I really don't know what else to call it.
I'm trying to make a extendable and modular plugin system for my website. I need to be able to access plugin php files that exist in a plugin directory and get access to their classes to call functions such as getting the html content that the plugin should show and more.
Below is a semi-pseudo code example of what I am trying to achieve, but how to actually arbitrarily load the plugins is where I am stuck (PluginLoader.php).
-Max
//BasePlugin.php
abstract class BasePlugin
{
public function displayContent()
{
print "<p>Base Plugin</p>";
}
};
//ExamplePlugin.php -> In specific plugin directory.
require('../BasePlugin.php');
class ExamplePlugin extends BasePlugin
{
public static function Instance()
{
static $inst = null;
if ($inst === null) {
$inst = new ExamplePlugin();
}
return $inst;
}
public function displayContent()
{
print "<p>Example Plugin</p>";
}
}
//PluginLoader.php
foreach($pluginFile : PluginFilesInDirectory) { // Iterate over plugin php files in plugin directory
$plugin = GetPlugin($pluginFile); // Somehow get instance of plugin.
echo plugin->displayContent();
}
I'm guessing here, but it seems to me that you need to:
get a list of the plugins in the desired directory.
include or require the plugin's class file.
create an instance of the class.
call the plugin's displayContent() method.
So, you probably want to do something like
$pluginDir = 'your/plugin/directory/' ;
$plugins = glob($pluginDir . '*.php') ;
foreach($plugins as $plugin) {
// include the plugin file
include_once($plugin) ;
// grab the class name from the plugin's file name
// this finds the last occurrence of a '/' and gets the file name without the .php
$className = substr($plugin,strrpos($plugin,'/') + 1, -4) ;
// create the instance and display your test
$aPlugin = $className::Instance() ;
$aPlugin->displayContent() ;
}
There's probably a cleaner way to do it, but that will ready your directory, get the plugins' code, and instantiate each one. How you manage/reference them afterwards depends on how your plugins register with your application.
Class not found, apparently. I've tried various things but nothing works.
Composer:
"autoload": {
"psr-4": {
"App\\": "application/"
}
}
File structure:
https://i.imgur.com/h9wOEqI.png
<?php
namespace App\Library\Classes;
defined('START') or exit('We couldn\'t process your request right now.');
class Application
{
private static $libraries = array();
public static function get($library) {
if (isset(self::$libraries[$library]) && isset(self::$classes[$library])) {
return self::$libraries[$library];
}
$fixedLibrary = str_replace('.', '/', $library);
$file = ROOT . '/application/library/classes/' . strtolower($fixedLibrary) . '.php';
self::$libraries[$library] = $library;
$declared = get_declared_classes();
$workingClass = end($declared);
self::$libraries[$library] = new $workingClass();
return self::$libraries[$library];
}
}
?>
Error is on this line:
Application::get('test')->test();
Yet, if I change it to this, it works:
include ROOT . '/application/Library/Application.php';
App\Library\Classes\Application::get('test')->test();
The PSR4 is not built-in part or PHP, you need an implementation of autoloader to use this standard such as provided by the Composer.
When you install or update depedencies, composer generates the relevant code of autoloading, but you can directly update it by the command dump-autoload, as #jibsteroos said. Next you should explicitly include the file vendor/autoload.php in the entry point of your application.
Also, error message says about class Application, but you should add the use statement at first:
use App\Library\Classes\Application;
Application::get('test')->test();
Or use the fully qualified class name (class name with namespace prefix):
\App\Library\Classes\Application::get('test')->test();
The Sonata Media Bundle you have the thumbnail property on a provider in the config where you can specify either
sonata.media.thumbnail.format
sonata.media.thumbnail.liip_imagine
This all fine and the sonata.media.thumbnail.format one works fine for everything I want to achieve. My problem comes in with what happens within these files.
In the FormatThumbnail.php there is a function called generatePublicUrl where they generate the url of the media file and also the name of the formatted file. They use the media id within the name or url. If you have private files not everyone must be able to see this causes a problem with it is easy to manipulate the id to another id.
I know the public files that will be served will always stay public so if the url can be guessed the user will access the file. For this specific reason I want to try and replace that id with the unique reference that the bundle uses before they create the actual formatted files as this will not be as easy to just change.
I am aware that there are still risks of leaking out data.
I want to change this
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getId(), $format, $this->getExtension($media));
}
return $path;
}
to this
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getProviderReference(), $format, $this->getExtension($media));
}
return $path;
}
How do I override the file that the bundle just picks up the change?
I have followed the steps on Sonata's site on how to install and set up the bundle using the easy extends bundle. I have my own Application\Sonata\MediaBundle folder that is extending the original Sonata\MediaBundle.
For installation related information have a look through the documentation(https://sonata-project.org/bundles/media/master/doc/reference/installation.html)
However I tried to create my own Thumbnail folder and creating a new FormatThumbnail.php as follows
<?php
namespace Application\Sonata\MediaBundle\Thumbnail;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Provider\MediaProviderInterface;
use Sonata\MediaBundle\Thumbnail\FormatThumbnail as BaseFormatThumbnail;
class FormatThumbnail extends BaseFormatThumbnail
{
/**
* Overriding this to replace the id with the reference
*
* {#inheritdoc}
*/
public function generatePublicUrl(MediaProviderInterface $provider, MediaInterface $media, $format)
{
if ($format == 'reference') {
$path = $provider->getReferenceImage($media);
} else {
$path = sprintf('%s/thumb_%s_%s.%s', $provider->generatePath($media), $media->getProviderReference(), $format, $this->getExtension($media));
}
return $path;
}
}
But the bundle still generates everything using the id instead of the reference. Is there a more specific way to achieve extending this file and overriding the function?
After looking at a few different bundles and after looking in code I found that they physically have a parameter which is set to use Sonata\MediaBundle\Thumbnail\FormatThumbnail.
The solution is to override the parameter in the config aswell.
#As top level classification in app/config/config.yml
parameters:
sonata.media.thumbnail.format: Application\Sonata\MediaBundle\Thumbnail\FormatThumbnail
This way the custom FormatThumbnail class is injected everywhere it will be used within the bundle.
Wondering if anyone knows how or of a package that allows migrations to be done on multiple databases without having the additional connection in the config file. Currently I have two different connections, one for the default and another for the tenants. The tenant connection has the credentials input, but it doesn't have the database name since each tenant will have a different database name. Problem is when I'm doing the initial setup and need the migrations to be run it will do it for the default or the tenant(if I edit the file to include the database name). I had found a package that would do it but it is not currently compatible with laravel 5. Obviously if I have to do it manually it can be done. It would just be a pain. Thanks in advance for suggestions.
Looking through the tenancy related questions here, I stumbled upon your open one.
What I've done is override the default MigrateCommand provided by Laravel and add the --tenant option. This will allow me to migrate one, more or all tenants. You can check the implementation out at my repository. Knowing the code of conduct here, I'll also add an example implementation:
<?php
namespace Hyn\MultiTenant\Commands\Migrate;
use Hyn\MultiTenant\Traits\TenantDatabaseCommandTrait;
use Illuminate\Database\Migrations\Migrator;
use PDOException;
class MigrateCommand extends \Illuminate\Database\Console\Migrations\MigrateCommand
{
use TenantDatabaseCommandTrait;
/**
* MigrateCommand constructor.
*
* #param Migrator $migrator
*/
public function __construct(Migrator $migrator)
{
parent::__construct($migrator);
$this->website = app('Hyn\MultiTenant\Contracts\WebsiteRepositoryContract');
}
public function fire()
{
// fallback to default behaviour if we're not talking about multi tenancy
if (! $this->option('tenant')) {
$this->info('No running tenancy migration, falling back on native laravel migrate command due to missing tenant option.');
return parent::fire();
}
if (! $this->option('force') && ! $this->confirmToProceed()) {
$this->error('Stopped no confirmation and not forced.');
return;
}
$websites = $this->getWebsitesFromOption();
// forces database to tenant
if (! $this->option('database')) {
$this->input->setOption('database', 'tenant');
}
foreach ($websites as $website) {
$this->info("Migrating for {$website->id}: {$website->present()->name}");
$website->database->setCurrent();
$this->prepareDatabase($website->database->name);
// The pretend option can be used for "simulating" the migration and grabbing
// the SQL queries that would fire if the migration were to be run against
// a database for real, which is helpful for double checking migrations.
$pretend = $this->input->getOption('pretend');
// Next, we will check to see if a path option has been defined. If it has
// we will use the path relative to the root of this installation folder
// so that migrations may be run for any path within the applications.
if (! is_null($path = $this->input->getOption('path'))) {
$path = $this->laravel->basePath().'/'.$path;
} else {
$path = $this->getMigrationPath();
}
try {
$this->migrator->run($path, $pretend);
} catch (PDOException $e) {
if (str_contains($e->getMessage(), ['Base table or view already exists'])) {
$this->comment("Migration failed for existing table; probably a system migration: {$e->getMessage()}");
continue;
}
}
// Once the migrator has run we will grab the note output and send it out to
// the console screen, since the migrator itself functions without having
// any instances of the OutputInterface contract passed into the class.
foreach ($this->migrator->getNotes() as $note) {
$this->output->writeln($note);
}
}
// Finally, if the "seed" option has been given, we will re-run the database
// seed task to re-populate the database, which is convenient when adding
// a migration and a seed at the same time, as it is only this command.
if ($this->input->getOption('seed')) {
$this->call('db:seed', ['--force' => true, '--tenant' => $this->option('tenant')]);
}
}
/**
* Prepare the migration database for running.
*
* #return void
*/
protected function prepareDatabase($connection = null)
{
if (! $connection) {
$connection = $this->option('database');
}
$this->migrator->setConnection($connection);
if (! $this->migrator->repositoryExists()) {
$options = ['--database' => $connection];
$this->call('migrate:install', $options);
}
}
/**
* #return array
*/
protected function getOptions()
{
return array_merge(
parent::getOptions(),
$this->getTenantOption()
);
}
}
If you have any questions to solve this issue, let me know.
How can get filesystem path of composer package?
composer.json example:
{
"require" : {
"codeception/codeception" : "#stable",
"willdurand/geocoder": "*"
}
}
example:
$composer->getPath("\Geocoder\HttpAdapter\HttpAdapterInterface");
and return it as:
"/home/me/public_html/vendor/willdurand/geocoder/src/Geocoder/HttpAdapter"
All of this is based on the assumption that you are actually talking about packages and not classes (which are mentioned in the example but are not asked for in the question).
If you have the Composer object, you can get the path of the vendor directory from the Config object:
$vendorPath = $composer->getConfig()->get('vendor-dir');
$vendorPath should now contain /home/me/public_html/vendor/.
It shouldn't be too hard to construct the rest of the path from there, as you already have the package name.
If this feels too flaky or you don't want to write the logic, there is another solution. You could fetch all packages, iterate until you find the right package and grab the path from it:
$repositoryManager = $composer->getRepositoryManager();
$installationManager = $composer->getInstallationManager();
$localRepository = $repositoryManager->getLocalRepository();
$packages = $localRepository->getPackages();
foreach ($packages as $package) {
if ($package->getName() === 'willdurand/geocoder') {
$installPath = $installationManager->getInstallPath($package);
break;
}
}
$installPath should now contain /home/me/public_html/vendor/willdurand/geocoder
Try ReflectionClass::getFileName - Gets the filename of the file in which the class has been defined.
http://www.php.net/manual/en/reflectionclass.getfilename.php
Example:
$reflector = new ReflectionClass("\Geocoder\HttpAdapter\HttpAdapterInterface");
echo $reflector->getFileName();
Or you may use this:
$loader = require './vendor/autoload.php';
echo $loader->findFile("\Geocoder\HttpAdapter\HttpAdapterInterface");
The first method try to load class and return loaded class path. The second method return path from composer database without class autoload.
Here is my solution to get the vendor path without using the $composer object :
<?php
namespace MyPackage;
use Composer\Autoload\ClassLoader;
class MyClass
{
private function getVendorPath()
{
$reflector = new \ReflectionClass(ClassLoader::class);
$vendorPath = preg_replace('/^(.*)\/composer\/ClassLoader\.php$/', '$1', $reflector->getFileName() );
if($vendorPath && is_dir($vendorPath)) {
return $vendorPath . '/';
}
throw new \RuntimeException('Unable to detect vendor path.');
}
}
Not sure if the following is the correct way for this because composer is changing so fast.
If you run this command:
php /path/to/composer.phar dump-autoload -o
it will create a classmap array in this file
vender/composer/autoload_classmap.php
with this format "classname" => filepath.
So to find filepath of a given class is simple. If you create the script in your project's root folder, you can do this:
$classmap = require('vender/composer/autoload_classmap.php');
$filepath = $classmap[$classname]?: null;