Figuring out Laravel 5 app namespace from a package - php

I'm developing a package for Laravel 5 and need to know the application namespace, how can I get it?
I saw there is Illuminate\Console\AppNamespaceDetectorTrait but it needs some Foundation helpers as well as an instance of the application container to make them work and it's tricky to test during the development of a package.
Edit: for the time being I'm wrapping that trait into an interface to create isolation, but wondering if there is a better solution.

I don't know what's supposed to not work with the AppNamespaceDetectorTrait. Just use it and call getAppNamespace():
class Foo {
use Illuminate\Console\AppNamespaceDetectorTrait;
public function bar(){
echo $this->getAppNamespace();
}
}

Try copying in getAppNamespace() function into a generic helpers file that can be accessed anywhere in your app. Then, you don't need to tie the AppNamespaceDetectorTrait trait to your class.
helpers.php
function getAppNamespace()
{
$composer = json_decode(file_get_contents(base_path().'/composer.json'), true);
foreach ((array) data_get($composer, 'autoload.psr-4') as $namespace => $path)
{
foreach ((array) $path as $pathChoice)
{
if (realpath(app_path()) == realpath(base_path().'/'.$pathChoice)) return $namespace;
}
}
throw new RuntimeException("Unable to detect application namespace.");
}
And then in YourClass.php
$namespace = getAppNamespace(); // App\

Related

How to properly extend the Laravel 5.7 Router?

This question has been discussed many times here, here or here but no elegant solutions were mentioned.
One particular use case would be to allow to load and route old PHP files with Laravel. I am for instance migrating a very old (> 20 years) code base into Laravel and most pages are regular PHP files that I would like to render into a particular Blade template.
To do this it would be elegant to do:
Router::php('/some/route/{id}', base_path('legacy/some/page.php'));
Behind the scenes all I need is to pass the captured variables to the PHP page, evaluate and grab the content of it and eventually return a view instance.
As Laravel claims itself to be a SOLID framework, I thought extending the Router is trivial so I wrote this:
namespace App\Services;
class Router extends \Illuminate\Routing\Router
{
public function php($uri, $filename, $template='default') {
...
return view(...
}
}
Then I tried to extend my Http Kernel with this:
namespace App\Http;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use App\Services\Router;
class Kernel extends HttpKernel
{
public function __construct(Application $app, Router $router) {
return parent::__construct($app, $router);
}
}
But it is not working it seems the Application is building the Kernel with the wrong dependency. In Application#registerCoreContainerAliases I see the core alias router is hard coded and since this method is called in the Application's constructor, I am doomed.
The only solution that remains is to override the router before loading the Kernel as follow:
$app = new Application($_ENV['APP_BASE_PATH'] ?? dirname(__DIR__));
$app->singleton('router', \App\Services\Router::class);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
But this looks a bit ugly. Is there a better way to achieve this?
Since the Router class is macroable, you may be able to do something like:
Router::macro('php', function ($uri, $filepath) {
return $this->addRoute(['GET', 'POST', etc...], $uri, function () use ($filepath) {
// here you might use the blade compiler to render the raw php along with any variables.
//
// See: https://laravel.com/api/5.7/Illuminate/View/Compilers/Concerns/CompilesRawPhp.html
//
$contents = file_get_contents($filepath);
// return compiled $contents...
});
});

Symfony 4 : Override public services in container

I am migrating our project to Symfony 4. In my test suites, we used PHPUnit for functional tests (I mean, we call endpoints and we check result). Often, we mock services to check different steps.
Since I migrated to Symfony 4, I am facing this issue: Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "my.service" service is already initialized, you cannot replace it.
when we redefine it like this : static::$container->set("my.service", $mock);
Only for tests, how can I fix this issue?
Thank you
Replacing is deprecated since Symfony 3.3. Instead of replacing service you should try using aliases.
http://symfony.com/doc/current/service_container/alias_private.html
Also, you can try this approach:
$this->container->getDefinition('user.user_service')->setSynthetic(true);
before doing $container->set()
Replace Symfony service in tests for php 7.2
Finally, I found a solution. Maybe not the best, but, it's working:
I created another test container class and I override the services property using Reflection:
<?php
namespace My\Bundle\Test;
use Symfony\Bundle\FrameworkBundle\Test\TestContainer as BaseTestContainer;
class TestContainer extends BaseTestContainer
{
private $publicContainer;
public function set($id, $service)
{
$r = new \ReflectionObject($this->publicContainer);
$p = $r->getProperty('services');
$p->setAccessible(true);
$services = $p->getValue($this->publicContainer);
$services[$id] = $service;
$p->setValue($this->publicContainer, $services);
}
public function setPublicContainer($container)
{
$this->publicContainer = $container;
}
Kernel.php :
<?php
namespace App;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function getOriginalContainer()
{
if(!$this->container) {
parent::boot();
}
/** #var Container $container */
return $this->container;
}
public function getContainer()
{
if ($this->environment == 'prod') {
return parent::getContainer();
}
/** #var Container $container */
$container = $this->getOriginalContainer();
$testContainer = $container->get('my.test.service_container');
$testContainer->setPublicContainer($container);
return $testContainer;
}
It's really ugly, but it's working.
I've got a couple of tests like this (the real code performs some actions and returns a result, the test-version just returns false for every answer).
If you create and use a custom config for each environment (eg: a services_test.yaml, or in Symfony4 probably tests/services.yaml), and first have it include dev/services.yaml, but then override the service you want, the last definition will be used.
app/config/services_test.yml:
imports:
- { resource: services.yml }
App\BotDetector\BotDetectable: '#App\BotDetector\BotDetectorNeverBot'
# in the top-level 'live/prod' config this would be
# App\BotDetector\BotDetectable: '#App\BotDetector\BotDetector'
Here, I'm using an Interface as a service-name, but it will do the same with '#service.name' style as well.
As I understood it, it means that class X was already injected(because of some other dependency) somewhere before your code tries to overwrite it with self::$container->set(X:class, $someMock).
If you on Symfony 3.4 and below you can ovverride services in container regardless it privite or public. Only deprication notice will be emmited, with content similar to error message from question.
On Symfony 4.0 error from the question was thown.
But on Symfony 4.1 and above you can lean on special "test" container. To learn how to use it consider follow next links:
https://symfony.com/blog/new-in-symfony-4-1-simpler-service-testing
https://dev.to/nikolastojilj12/symfony-5-mocking-private-autowired-services-in-controller-functional-tests-24j4

How to resolve auto generated classes in Symfony3

In a Symfony3 project, which is using psr 4, I am storing configuration data by auto generating a php class in the 'var/cache/dev' folder.
Here is a simplified copy of the generated file:
<?php
use Project\Bundle\Metadata\AbstractMetadata;
class LocalizedMetadata extends AbstractMetadata
{
protected function populate ()
{
}
}
I know I can include or require the file, but the 'dev' directory changes between development and production, and I use the class in several places. Also, I'm hoping to use this bundle in other projects, so I don't want to hard code anything outside of the bundle. How are these files usually loaded? Is there anything within the Symfony core to assist?
Any classes created in the 'dev' directory cannot be auto loaded, at least not without configuring files outside of the bundle (eg. composer.yml).
I took a look into how Symfony manages it's router files, and the solution was actually quite simple.
I made a manager class which can be called from anywhere in the code. This class contains a method which returns an instance of the class in the cache folder.
public function getLocalizedRoutes ()
{
if ( null !== $this->localizedRoutes ) {
return $this->localizedRoutes;
}
$cache = $this->getConfigCacheFactory()->cache(
$this->options['cache_dir'].'/'.CachingMetadata::CACHE_FILE,
function (ConfigCacheInterface $cache) {
$content = CachingMetadata::getInstance()->dump();
$cache->write($content);
}
);
require_once $cache->getPath();
return $this->localizedRoutes = LocalizedMetadata::getInstance();
}
It uses Symfony's built in ConfigCache class, simply because it handles the file write and file permissions. Also, it will call the closure if the file doesn't exist. In this case the closure calls into the class which handles the loading of data and converting it into PHP code. The cache file is then loaded with require_once and an instance of LocalizedMetadata, which is the cached class name, is returned.
The above method can be modified to also pass resource files to ConfigCache, that way if one of your files is modified, the cache will be rewritten. In my case it wasn't required.
Making this work with a cache warmer simply requires a call to the above method:
class RouteCacheWarmer implements CacheWarmerInterface
{
private $_manager;
public function __construct ( LocaleManager $manager )
{
$this->_manager = $manager;
}
public function isOptional ()
{
return true;
}
public function warmUp ( $cacheDir )
{
$this->_manager->getLocalizedRoutes();
}
}
I would also like to note, it's possible to use namespaces within the cache file.

Laravel get instance in a model

First please be gentle i am a beginner and im only coding for practise.
I try to pass an instance to the model but i always get this error
Argument 1 passed to Store::__construct() must be an instance of Illuminate\Filesystem\Filesystem, none given
my model
<?php
use Illuminate\Filesystem\Filesystem as File;
class Store extends Eloquent
{
public $timestamps = false;
public function __construct(File $file)
{
$this->file = $file;
}
}
Could please somebody tell me what i am doing wrong?
thank you
EDIT
I just used simply like this in my Controller
public function index()
{
$store = new Store;
return View::make('store', $store);
}
The File class is one of Laravels Facades, which means you do not need to pass it into your models construct.
You can access it from anywhere in Laravel using File::someMethod(). If you use namespaces then you have to access via the root namespace \File::someMethod().
Within your store view you can access the File facade directly with the aforementioned method.
Take a look at the documentation on the file system here http://laravel.com/api/class-Illuminate.Filesystem.Filesystem.html
So you can use File::copy() without having to instantiate a class as it is called from a static method.

Laravel 4: use Facade in custom Class

I'm developing an app using Laravel 4 and I have a question I'd like to be asked before fully commit to it.
I've created some custom classes and facades that has been added with success to laravel's configuration file.
For example:
namespace Helpers;
class Ftp {
public function connect($data)
{
// Do something
}
}
I'm actually using the php's use statement to access to the facades as I do commonly in Laravel:
namespace Helpers;
use Illuminate\Support\Facades\File;
class Ftp {
public function Connect($data)
{
$file = File::get('text.txt');
...
}
}
Now what's the correct way to use laravel's facades inside a custom class? I don't feel that this is a good choice, expecially thinking about the testability. Any suggestion is appreciated!
Just use File. In app/config/app.php the facades gets aliases.
<?php namespace Helpers;
class Ftp {
public function Connect($data)
{
$file = \File::get('text.txt');
...
}
}

Categories