1<?php
2
3/**
4 * OAuth service factory.
5 *
6 * PHP version 5.4
7 *
8 * @category   OAuth
9 * @author     David Desberg <david@daviddesberg.com>
10 * @author     Pieter Hordijk <info@pieterhordijk.com>
11 * @copyright  Copyright (c) 2013 The authors
12 * @license    http://www.opensource.org/licenses/mit-license.html  MIT License
13 */
14
15namespace OAuth;
16
17use OAuth\Common\Service\ServiceInterface;
18use OAuth\Common\Consumer\CredentialsInterface;
19use OAuth\Common\Storage\TokenStorageInterface;
20use OAuth\Common\Http\Client\ClientInterface;
21use OAuth\Common\Http\Client\StreamClient;
22use OAuth\Common\Http\Uri\UriInterface;
23use OAuth\Common\Exception\Exception;
24use OAuth\OAuth1\Signature\Signature;
25
26class ServiceFactory
27{
28    /**
29     *@var ClientInterface
30     */
31    protected $httpClient;
32
33    /**
34     * @var array
35     */
36    protected $serviceClassMap = array(
37        'OAuth1' => array(),
38        'OAuth2' => array()
39    );
40
41    /**
42     * @var array
43     */
44    protected $serviceBuilders = array(
45        'OAuth2' => 'buildV2Service',
46        'OAuth1' => 'buildV1Service',
47    );
48
49    /**
50     * @param ClientInterface $httpClient
51     *
52     * @return ServiceFactory
53     */
54    public function setHttpClient(ClientInterface $httpClient)
55    {
56        $this->httpClient = $httpClient;
57
58        return $this;
59    }
60
61    /**
62     * Register a custom service to classname mapping.
63     *
64     * @param string $serviceName Name of the service
65     * @param string $className   Class to instantiate
66     *
67     * @return ServiceFactory
68     *
69     * @throws Exception If the class is nonexistent or does not implement a valid ServiceInterface
70     */
71    public function registerService($serviceName, $className)
72    {
73        if (!class_exists($className)) {
74            throw new Exception(sprintf('Service class %s does not exist.', $className));
75        }
76
77        $reflClass = new \ReflectionClass($className);
78
79        foreach (array('OAuth2', 'OAuth1') as $version) {
80            if ($reflClass->implementsInterface('OAuth\\' . $version . '\\Service\\ServiceInterface')) {
81                $this->serviceClassMap[$version][ucfirst($serviceName)] = $className;
82
83                return $this;
84            }
85        }
86
87        throw new Exception(sprintf('Service class %s must implement ServiceInterface.', $className));
88    }
89
90    /**
91     * Builds and returns oauth services
92     *
93     * It will first try to build an OAuth2 service and if none found it will try to build an OAuth1 service
94     *
95     * @param string                $serviceName Name of service to create
96     * @param CredentialsInterface  $credentials
97     * @param TokenStorageInterface $storage
98     * @param array|null            $scopes      If creating an oauth2 service, array of scopes
99     * @param UriInterface|null     $baseApiUri
100     *
101     * @return ServiceInterface
102     */
103    public function createService(
104        $serviceName,
105        CredentialsInterface $credentials,
106        TokenStorageInterface $storage,
107        $scopes = array(),
108        UriInterface $baseApiUri = null
109    ) {
110        if (!$this->httpClient) {
111            // for backwards compatibility.
112            $this->httpClient = new StreamClient();
113        }
114
115        foreach ($this->serviceBuilders as $version => $buildMethod) {
116            $fullyQualifiedServiceName = $this->getFullyQualifiedServiceName($serviceName, $version);
117
118            if (class_exists($fullyQualifiedServiceName)) {
119                return $this->$buildMethod($fullyQualifiedServiceName, $credentials, $storage, $scopes, $baseApiUri);
120            }
121        }
122
123        return null;
124    }
125
126    /**
127     * Gets the fully qualified name of the service
128     *
129     * @param string $serviceName The name of the service of which to get the fully qualified name
130     * @param string $type        The type of the service to get (either OAuth1 or OAuth2)
131     *
132     * @return string The fully qualified name of the service
133     */
134    private function getFullyQualifiedServiceName($serviceName, $type)
135    {
136        $serviceName = ucfirst($serviceName);
137
138        if (isset($this->serviceClassMap[$type][$serviceName])) {
139            return $this->serviceClassMap[$type][$serviceName];
140        }
141
142        return '\\OAuth\\' . $type . '\\Service\\' . $serviceName;
143    }
144
145    /**
146     * Builds v2 services
147     *
148     * @param string                $serviceName The fully qualified service name
149     * @param CredentialsInterface  $credentials
150     * @param TokenStorageInterface $storage
151     * @param array|null            $scopes      Array of scopes for the service
152     * @param UriInterface|null     $baseApiUri
153     *
154     * @return ServiceInterface
155     *
156     * @throws Exception
157     */
158    private function buildV2Service(
159        $serviceName,
160        CredentialsInterface $credentials,
161        TokenStorageInterface $storage,
162        array $scopes,
163        UriInterface $baseApiUri = null
164    ) {
165        return new $serviceName(
166            $credentials,
167            $this->httpClient,
168            $storage,
169            $this->resolveScopes($serviceName, $scopes),
170            $baseApiUri
171        );
172    }
173
174    /**
175     * Resolves scopes for v2 services
176     *
177     * @param string  $serviceName The fully qualified service name
178     * @param array   $scopes      List of scopes for the service
179     *
180     * @return array List of resolved scopes
181     */
182    private function resolveScopes($serviceName, array $scopes)
183    {
184        $reflClass = new \ReflectionClass($serviceName);
185        $constants = $reflClass->getConstants();
186
187        $resolvedScopes = array();
188        foreach ($scopes as $scope) {
189            $key = strtoupper('SCOPE_' . $scope);
190
191            if (array_key_exists($key, $constants)) {
192                $resolvedScopes[] = $constants[$key];
193            } else {
194                $resolvedScopes[] = $scope;
195            }
196        }
197
198        return $resolvedScopes;
199    }
200
201    /**
202     * Builds v1 services
203     *
204     * @param string                $serviceName The fully qualified service name
205     * @param CredentialsInterface  $credentials
206     * @param TokenStorageInterface $storage
207     * @param array                 $scopes
208     * @param UriInterface          $baseApiUri
209     *
210     * @return ServiceInterface
211     *
212     * @throws Exception
213     */
214    private function buildV1Service(
215        $serviceName,
216        CredentialsInterface $credentials,
217        TokenStorageInterface $storage,
218        $scopes,
219        UriInterface $baseApiUri = null
220    ) {
221        if (!empty($scopes)) {
222            throw new Exception(
223                'Scopes passed to ServiceFactory::createService but an OAuth1 service was requested.'
224            );
225        }
226
227        return new $serviceName($credentials, $this->httpClient, $storage, new Signature($credentials), $baseApiUri);
228    }
229}
230