Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert paths into Mapping objects #49

Open
wants to merge 3 commits into
base: 1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"gedmo/doctrine-extensions": "^2.4"
},
"autoload": {
"files": ["src/helpers.php"],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patrickbrouwers there's no other way to load functions, right? I couldn't find documentation of namespaced functions in composer docs.
Oh, this probably breaks in php 5.5, so we should bump anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commited bump in here so travis doesn't fail on phpunit run.

"psr-4": {
"LaravelDoctrine\\Fluent\\": "src/",
"Gedmo\\": "lib/"
Expand Down
57 changes: 39 additions & 18 deletions src/FluentDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ class FluentDriver implements MappingDriver
protected $fluentFactory;

/**
* Initializes a new FileDriver that looks in the given path(s) for mapping
* documents and operates in the specified operating mode.
* Initializes a new FluentDriver that will load given Mapping classes / objects.
*
* @param string[] $mappings
* @param string[]|Mapping[] $mappings
*/
public function __construct(array $mappings = [])
{
Expand Down Expand Up @@ -79,35 +78,33 @@ public function isTransient($className)
}

/**
* @param string[] $mappings
* Adds an array of mapping classes / objects to the driver.
*
* @param string[]|Mapping[] $mappings
*
* @throws MappingException
* @throws InvalidArgumentException
*/
public function addMappings(array $mappings = [])
{
foreach ($mappings as $class) {
if (!class_exists($class)) {
throw new InvalidArgumentException("Mapping class [{$class}] does not exist");
}

$mapping = new $class();

if (!$mapping instanceof Mapping) {
throw new InvalidArgumentException("Mapping class [{$class}] should implement ".Mapping::class);
}

foreach ($mappings as $mapping) {
$this->addMapping($mapping);
}
}

/**
* @param Mapping $mapping
* @param string|Mapping $mapping
*
* @throws MappingException
* @throws InvalidArgumentException
*
* @return void
*/
public function addMapping(Mapping $mapping)
public function addMapping($mapping)
{
$this->mappers->add($mapping);
$this->mappers->add($mapping instanceof Mapping ?
$mapping : $this->createMapping($mapping)
);
}

/**
Expand Down Expand Up @@ -139,4 +136,28 @@ protected function getFluent(ClassMetadata $metadata)
{
return call_user_func($this->fluentFactory, $metadata);
}

/**
* Create a mapping object from a mapping class, assuming an empty constructor.
*
* @param string $class
*
* @throws InvalidArgumentException
*
* @return Mapping
*/
protected function createMapping($class)
{
if (!class_exists($class)) {
throw new InvalidArgumentException("Mapping class [{$class}] does not exist");
}

$mapping = new $class();

if (!$mapping instanceof Mapping) {
throw new InvalidArgumentException("Mapping class [{$class}] should implement ".Mapping::class);
}

return $mapping;
}
}
75 changes: 75 additions & 0 deletions src/helpers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace LaravelDoctrine\Fluent;

use Doctrine\ORM\Mapping\MappingException;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RecursiveRegexIterator;
use ReflectionClass;
use RegexIterator;

/**
* Returns an array of Mapping objects found on the given paths.
*
* @param string[] $paths
* @param string $fileExtension
*
* @throws MappingException
*
* @return Mapping[]
*/
function mappingsFrom(array $paths, $fileExtension = '.php')
{
$includedFiles = [];

foreach ($paths as $path) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patrickbrouwers I'm pretty sure we should be able to replace this loop with another iterator, so that we iterate recursively over each path, instead of building the full iterator chain for each path in the array.

if (!is_dir($path)) {
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
}

$iterator = new RegexIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
),
'/^.+'.preg_quote($fileExtension, '/').'$/i',
RecursiveRegexIterator::GET_MATCH
);

foreach ($iterator as $file) {
$sourceFile = $file[0];

if (!preg_match('(^phar:)i', $sourceFile)) {
$sourceFile = realpath($sourceFile);
}

require_once $sourceFile;

$includedFiles[$sourceFile] = true;
}
}

$mappings = [];
$declared = get_declared_classes();
foreach ($declared as $className) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patrickbrouwers moved inner loop outside the $paths loop, so it recursively iterates all paths, then iterates over declared classes once.

$rc = new ReflectionClass($className);
$sourceFile = $rc->getFileName();
if ($sourceFile === false || !array_key_exists($sourceFile, $includedFiles)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patrickbrouwers switched to key check instead of in_array. Had to use the boolean check to avoid php native classes crashing the array_key_exists check.

continue;
}

if ($rc->isAbstract() || $rc->isInterface()) {
continue;
}

if (!$rc->implementsInterface(Mapping::class)) {
continue;
}

$mappings[] = $rc->newInstanceWithoutConstructor();
}

return $mappings;
}
48 changes: 48 additions & 0 deletions tests/FluentDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Tests\Stubs\Mappings\StubEmbeddableMapping;
use Tests\Stubs\Mappings\StubEntityMapping;
use Tests\Stubs\Mappings\StubMappedSuperClassMapping;
use function LaravelDoctrine\Fluent\mappingsFrom;

class FluentDriverTest extends \PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -229,6 +230,53 @@ public function test_allow_other_fluent_implementations()
$driver->getMappers()->addMapper('fake', new EntityMapper($mapping));
$driver->loadMetadataForClass('fake', new ClassMetadataInfo('fake'));
}

/**
* This is not "only" a FluentDriver test.
* This tests the `mappingsFrom` function, in conjunction with the driver's ability
* to receive both Mapping class names or Mapping objects.
*/
public function test_it_can_be_built_with_paths_using_the_provided_helper()
{
$driver = new FluentDriver(array_merge(
mappingsFrom([__DIR__ . '/Stubs/Mappings']),
[FakeClassMapping::class]
));

$classNames = $driver->getAllClassNames();
$this->assertContains(StubEntity::class, $classNames);
$this->assertContains(StubEmbeddable::class, $classNames);
$this->assertContains(StubMappedSuperClass::class, $classNames);
$this->assertContains(FakeEntity::class, $classNames);

$driver->loadMetadataForClass(
StubEntity::class, new ClassMetadataInfo(StubEntity::class)
);
$this->assertInstanceOf(
EntityMapper::class, $driver->getMappers()->getMapperFor(StubEntity::class)
);

$driver->loadMetadataForClass(
StubEmbeddable::class, new ClassMetadataInfo(StubEmbeddable::class)
);
$this->assertInstanceOf(
EmbeddableMapper::class, $driver->getMappers()->getMapperFor(StubEmbeddable::class)
);

$driver->loadMetadataForClass(
StubMappedSuperClass::class, new ClassMetadataInfo(StubMappedSuperClass::class)
);
$this->assertInstanceOf(
MappedSuperClassMapper::class, $driver->getMappers()->getMapperFor(StubMappedSuperClass::class)
);

$driver->loadMetadataForClass(
FakeEntity::class, new ClassMetadataInfo(FakeEntity::class)
);
$this->assertInstanceOf(
EntityMapper::class, $driver->getMappers()->getMapperFor(FakeEntity::class)
);
}
}

class FakeClassMapping extends EntityMapping
Expand Down